Cross-platform desktop and mobile viewer for OpenIPC IP cameras. Built with .NET 9 / 10 and Avalonia 12.
Status: beta — releases ship as standalone builds for Windows / Linux / macOS / Android from the releases page. Native installers, code signing and in-app auto-update come later.
See the Roadmap for phase status.
- Live RTSP — h264 / h265, software + hardware decode (D3D11VA / VAAPI / VideoToolbox / Android MediaCodec), auto-reconnect, auto SD/HD switching.
- Multi-camera grid — up to 25 streams, tabbed layouts with their own grid size, drag-reorder that sticks per layout, fullscreen kiosk mode, and a Live | Stills switch for a low-cost mode (periodic HTTP snapshots instead of video).
- Single-camera view — PTZ joystick for sweeping plus a step keypad for framing (one nudge per press, home position, move speed), presets, telemetry overlay, snapshot to disk + share. Which PTZ controls appear is read from the camera, so a device that cannot step or go home is not offered buttons that would fail.
- Digital zoom — up to 8× in live view and in the recording player: wheel or pinch zooms at the cursor, drag pans, Shift+drag (or the zoom-to-area button) draws a box to zoom into, with a minimap while zoomed. Detection boxes follow the zoom.
- Adding cameras — type an address and login, press Connect: the editor
detects OpenIPC / ONVIF, fills in streams, ports and name and shows a live
frame — or says plainly what failed (unreachable, wrong login, no video). A
pasted
rtsp://link is split into fields; everything else sits under collapsible Advanced rows. QR-code add from an image. - Camera discovery — Find on network starts a quick ONVIF + mDNS scan as soon as it opens, recognises OpenIPC by its web fingerprint, and adds each result with one click; an opt-in deep scan sweeps subnets and IP ranges for cameras that announce nothing.
- Camera library — dense list with search by name / IP / group, status filters with live counts, a pager, and a per-row menu (edit, device tools, files, which layouts the camera is in).
- Recording & archive — segmented MP4 via
-c copy. Recordings are grouped by day with camera / period / motion-only filters and an activity calendar. The player has 0.5×–8× speed, frame stepping, sound, fullscreen, clip export, save-frame-to-snapshots and auto-continue into the next recording, all with keyboard shortcuts. - Events — motion and AI detections filtered by camera / type / period, bursts folded into one episode, what was detected shown as class icons, and a jump straight to the recording at the moment of the event.
- AI detection (local) — ONNX object detection on-device (person / car / bike / animal…), boxes in the grid and single view, auto-record on detection. The AI page shows engine status, per-camera switches and a 24-hour summary. No cloud: the model runs in-process.
- Audio — one-tap listen (AAC/PCM) and push-to-talk to cameras with an RTSP backchannel.
- Majestic integration — read / apply config with diff preview, raw JSON editor, RTMP push, day / night / auto mode.
- Device tools — SSH terminal, remote file manager, reboot / clock / log snapshots over SSH.
- Health & status — unified per-camera verdict (online / attention / offline) across grid, library and a Health Center overview.
- Notifications — system notifications for motion and detections, with quiet hours.
- Desktop niceties — tray icon with quick actions, optional close-to-tray, choice of start page (Library or Live) and startup layout, light/dark themes with accent colors.
- Camera groups, English / Russian UI (runtime switch), responsive layout (sidebar on desktop, bottom tab strip and bottom sheets on phones).
- Web console (self-host) — run the same binary with
--server-onlyfor a browser UI to your cameras over the LAN: live grid with saved layouts, PTZ, network discovery, camera management, config backup, and accounts with per-user permissions and camera subsets. No cloud. See the self-hosting guide.
| 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 | org.openipc.viewer-Signed.apk |
sideload on Android 12+ (not on Google Play yet) |
| Android x64 | org.openipc.viewer-x64-Signed.apk |
the same for emulators and x86-64 Android devices — phones want the arm64 one |
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 — just libsecret for the keyring:
FFmpeg ships inside the archive (matching
sudo apt install libsecret-1-0 libsecret-toolsn7.1.so) — do not rely onapt install ffmpeg. The app binds the FFmpeg 7.x ABI (libavcodec.so.61), but no current Ubuntu LTS packages FFmpeg 7 (24.04 has 6.1 →libavcodec.so.60, 22.04 has 4.4), so a system FFmpeg would fail to load with "FFmpeg native libraries failed to load for runtime linux-x64". The bundled libs sidestep the distro version entirely. VAAPI hardware decode still needs/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.
The same binary can run headless as a small web server, serving a React single-page UI for your cameras to any browser on your network — live grid with saved layouts, PTZ, network discovery, and accounts with per-user permissions and camera subsets. No cloud, no extra services:
export OPENIPC_WEB_ADMIN_PASSWORD='choose-a-strong-one'
./OpenIPC.Viewer.Desktop --server-only --lan --port 8787
# then open http://<this-machine-ip>:8787 and log in as adminLocalhost-only by default; --lan opts into network exposure. For an HTTPS
domain put it behind a reverse proxy. Full instructions — flags, adding and
finding cameras, users and permissions, ffmpeg, Caddy/nginx + systemd examples —
are in the self-hosting guide (English ·
Русский).
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.
The web console's React SPA is built by the same dotnet build (npm ci +
npm run build, then embedded into OpenIPC.Viewer.Web.dll), so Node is a
prerequisite if you want the browser UI (CI builds it with Node 22). Without
Node the build still succeeds and the server runs API-only; pass
-p:BuildWebClient=false to skip it deliberately.
Run the FFmpeg fetch script for your OS once — it downloads the bundled
shared-build (n7.1 ABI) from BtbN/FFmpeg-Builds into runtimes/<rid>/native/:
tools/fetch-ffmpeg.ps1 (Windows → win-x64) or tools/fetch-ffmpeg-linux.sh
(Linux → linux-x64). macOS uses the system Homebrew 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.Analytics/ net9.0 — ONNX object detection (Microsoft.ML.OnnxRuntime)
OpenIPC.Viewer.Devices/ net9.0 — ONVIF, Majestic HTTP, SSH, discovery sources
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.Web/ net9.0 — Kestrel host + REST API for the self-hosted console
OpenIPC.Viewer.Web.Client/ — — React + Vite SPA, built and embedded into the Web dll
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.Devices.Tests/ xUnit — ONVIF, Majestic, discovery
OpenIPC.Viewer.Infrastructure.Tests/ xUnit — SQLite, migrations, secrets
OpenIPC.Viewer.Analytics.Tests/ xUnit — detection pre/post-processing
OpenIPC.Viewer.Web.Tests/ xUnit — API, auth, permissions
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.
OpenIPC Viewer is free and open source. If it is useful to you, you can support its development on Open Collective.
MIT. Bundled FFmpeg DLLs and self-built FFmpeg .so are LGPL — shipped as
side-by-side shared libs, replaceable by the user.






