Cross-platform desktop and mobile viewer for OpenIPC IP cameras. Built with .NET 9 / 10 and Avalonia 12.
Status: release polish — feature-complete for the first beta. Pending packaging (installers / in-app auto-update), code signing, and the
v0.1.0-betatag. Targets: Windows / Linux / macOS desktops + Android + iOS.See the Roadmap for phase status and what's left before the betas.
- Live RTSP — h264 / h265, software + hardware decode (D3D11VA / VAAPI / VideoToolbox / Android MediaCodec), auto-reconnect.
- Multi-camera grid — up to 25 streams, custom layouts, drag-reorder.
- Single-camera view — PTZ joystick + presets, telemetry overlay, digital zoom (pinch / Ctrl+wheel), snapshot to disk + share.
- Recording — segmented MP4 via
-c copy(desktop ffmpeg subprocess; Android in-process libavformat behind a foreground service). - Camera discovery — ONVIF probe + WS-Discovery, manual add, QR-code add.
- Majestic integration — read / apply config with diff preview, raw JSON editor, RTMP push, day / night / auto mode.
- Events — motion ingestion + persisted log.
- Camera groups, English / Russian UI (runtime switch), responsive layout (sidebar ↔ bottom tab strip).
| Library + ONVIF discovery | Add camera |
|---|---|
![]() |
![]() |
| Events | Settings |
|---|---|
![]() |
![]() |
| Live grid 2×2 | Live grid 3×3 |
|---|---|
![]() |
![]() |
Pre-built standalone binaries are attached to each GitHub release. No installer is required for the beta — download, extract, run:
| OS | Asset | Run |
|---|---|---|
| Windows x64 | openipc-viewer-win-x64.zip |
extract, run OpenIPC.Viewer.Desktop.exe (accept the SmartScreen prompt — builds are unsigned for now) |
| Linux x64 | openipc-viewer-linux-x64.tar.gz |
extract, chmod +x, run ./OpenIPC.Viewer.Desktop |
| macOS arm64 | openipc-viewer-osx-arm64.tar.gz |
extract, right-click the app → Open (Gatekeeper blocks unsigned builds on first launch) |
| Android arm64 | *-Signed.apk |
sideload (debug-signed, not Play-signed) |
Each build is self-contained (bundles the .NET runtime), so there's nothing to install alongside it. Native installers and in-app auto-update may come in a later release.
- Windows — none. FFmpeg DLLs ship inside the archive.
- Linux — system FFmpeg + libsecret:
VAAPI hardware decode needs
sudo apt install ffmpeg libavcodec-extra libsecret-1-0 libsecret-tools/dev/dri/renderD128and your user in therender(orvideo) group. Credentials usesecret-toolagainst the GNOME/KDE keyring, with an AES-GCM file fallback if D-Bus is unavailable. - macOS — Homebrew FFmpeg (
brew install ffmpeg). VideoToolbox HW decode works on any Mac 12+. Credentials live in the Keychain viasecurity.
Validation caveat. Linux / macOS / Android / iOS code paths build + link in CI but aren't yet end-to-end tested on real devices for every commit. Feedback is welcome — open an issue with OS version, what you did, and what happened.
dotnet restore OpenIPC.Viewer.slnx
dotnet build OpenIPC.Viewer.slnx
dotnet test OpenIPC.Viewer.slnx --no-build
dotnet run --project src/OpenIPC.Viewer.DesktopBuild runs with TreatWarningsAsErrors=true; any warning fails the build.
On Windows, run tools/fetch-ffmpeg.ps1 once to download the bundled FFmpeg
shared-build DLLs (n7.1 ABI) from BtbN/FFmpeg-Builds into
runtimes/win-x64/native/. Linux / macOS use the system FFmpeg (see
Runtime requirements).
dotnet workload install android
dotnet build src/OpenIPC.Viewer.Android/OpenIPC.Viewer.Android.csproj -c ReleaseCI cross-compiles FFmpeg n7.1 for android-arm64 via NDK r27c on every
build (cached when version/script unchanged). Recording uses in-process
libavformat + a foreground service (foregroundServiceType=dataSync) and
needs POST_NOTIFICATIONS on Android 13+, prompted on first record.
Credentials use an AES-GCM file keyed off Settings.Secure.AndroidId.
dotnet workload install ios
dotnet build src/OpenIPC.Viewer.iOS/OpenIPC.Viewer.iOS.csproj -c Release # Mac-only for the link stepiOS recording is foreground-only — Apple doesn't grant 24/7 background
captures to surveillance apps. Credentials use an AES-GCM file keyed off
UIDevice.identifierForVendor. CI builds an unsigned .app/.ipa;
TestFlight signing arrives in the release-polish phase.
src/
OpenIPC.Viewer.Core/ netstandard2.1 — domain, no IO, no UI, no package deps
OpenIPC.Viewer.Infrastructure/ net9.0 — SQLite, secrets, decoder factories
OpenIPC.Viewer.Video/ net9.0 — FFmpeg pipeline (FFmpeg.AutoGen + SkiaSharp)
OpenIPC.Viewer.Devices/ net9.0 — ONVIF, Majestic HTTP
OpenIPC.Viewer.App/ net9.0 — Avalonia views and viewmodels (cross-platform)
OpenIPC.Viewer.Composition/ net9.0 — shared DI registrations (used by every head)
OpenIPC.Viewer.Desktop/ net9.0 — Win/Lin/Mac host, classic-window lifetime
OpenIPC.Viewer.Android/ net10.0-android — Android host (min API 31), foreground-service recording
OpenIPC.Viewer.iOS/ net10.0-ios — iOS host (min 16), foreground-only recording
tests/
OpenIPC.Viewer.Core.Tests/ xUnit
OpenIPC.Viewer.Video.Tests/ xUnit + MediaMTX integration
App references Core only. Infrastructure, Video, Devices and the platform
trio (IFileSystem / ISecretsStore / IHwDecoderFactory) are wired into
each head via OpenIPC.Viewer.Composition.SharedComposition.AddSharedServices().
The video integration test and manual smoke depend on a local RTSP source.
A MediaMTX container synthesises a 1280×720@25 h264 test pattern on demand at
rtsp://localhost:8554/test.
docker compose -f tools/mediamtx/docker-compose.yml up -d
# ... do your testing ...
docker compose -f tools/mediamtx/docker-compose.yml downThe integration test auto-skips itself if the container isn't reachable.
Per-platform AppData root:
| OS | Path |
|---|---|
| Windows | %LOCALAPPDATA%\OpenIPC.Viewer\ |
| Linux | $XDG_DATA_HOME/openipc-viewer/ (default ~/.local/share/openipc-viewer/) |
| macOS | ~/Library/Application Support/OpenIPC.Viewer/ |
| Android | /data/data/org.openipc.viewer/files/ (app-private; uninstall wipes) |
| iOS | ~/Library/Application Support/OpenIPC.Viewer/ (sandbox; Files-app visible via UIFileSharingEnabled) |
Inside the root:
logs/openipc-viewer-{date}.log rolling daily, 7-day retention
appsettings.json optional override over the one shipped with the app
usersettings.json settings written by the in-app Settings page
openipc-viewer.db SQLite (cameras, recordings metadata, events)
secrets.bin / secrets.salt encrypted credential fallback (used when no native keystore)
snapshots/{camera}/*.jpg manual snapshots
recordings/ MP4 segments (Linux/macOS may override via XDG_VIDEOS_DIR / ~/Movies)
Credentials live in the native keystore when available (Windows DPAPI / macOS Keychain / Linux libsecret); the encrypted-file fallback is used otherwise.
MIT. Bundled FFmpeg DLLs and self-built FFmpeg .so are LGPL — shipped as
side-by-side shared libs, replaceable by the user.






