Releases: OpenIPC/viewer
Release list
v0.3.8-rc2
Builds on the React SPA web console and closes most of the gap with the desktop head. Everything here is server-side + SPA only — no change to the video pipeline, the desktop UI, or the mobile heads.
Live grid
- Stills mode — periodic snapshots instead of live streams, refresh interval
2–60 s. A 25-cell wall costs one HTTP GET per camera per interval instead of an
ffmpeg session each; the camera encodes the JPEG itself when it runs Majestic. - Digital zoom in a tile: mouse wheel, drag to pan, buttons for touch. The
transform lives on the tile's slot, never on the pooled<video>, so zoom
can't follow a camera into another cell. - Kiosk mode — chrome-free full-viewport wall with optional page cycling.
Uses the Fullscreen API when granted and falls back to a fixed overlay when it
isn't (embedded/iframed pages refuse it outright). - Wall preferences live in the browser, not the install: the same layout is
watched from a control-room screen and from someone's laptop.
Archive
- Recording start/stop from the browser, written into the same folder and table
the desktop head uses (-c copy, segmented,qon stdin so the MP4 trailer is
written). - Calendar + date filtering, paging, and clip export (fast
-c copyor precise
re-encode), streamed as a download without leaving a copy on the server. - Playback is a ranged file response, so seeking is the browser's own.
Snapshot library
- Stills taken from the browser can be kept: same folder layout, same
.thumbs/<id>.jpgnaming and the same table as the desktop's
SnapshotService, so a still taken from a phone shows up in the desktop
gallery and vice versa. - Thumbnails are produced with ffmpeg — already a dependency of this head —
rather than pulling the Video layer's Skia thumbnailer into the server. - Pixel dimensions are read from the JPEG frame header; no imaging library
needed to record what the desktop records. - Gallery page with lightbox, download and delete, under the Archive tab.
Camera settings (Majestic)
- Read the live
config.json, edit fields, post raw JSON, set day/night, read
/metrics. Schema-driven like the desktop editor (IMajesticConfigSchema), so
it renders whatever knobs the firmware actually exposes instead of a fixed form
that drifts between builds. - ISP fields get their own panel: Majestic re-reads those live, so tuning the
picture does not restart the encoder. Fields that do restart it are flagged. - Edits travel as
(path, value)and are applied against a freshly read copy of
the camera's config, so a stale tab can't revert someone else's change.
Two-way audio
- Listen: audio rides in the existing live stream (transcoded to AAC, ~1% of
the video bitrate) rather than a second session, so nobody's picture restarts
when they unmute. - Push-to-talk: the browser sends 16-bit PCM over a WebSocket; G.711
encoding and RTP packetizing happen server-side in the same Core code the
desktop talks with. One talker per camera — a speaker is exclusive. - Held, not toggled, and the camera is released when the route changes.
Permissions
- New
Talkflag, separate fromPtz/Manage: it is the one action that
reaches people standing in front of the camera. ViewArchivenow gates the archive and the snapshot library;Exportgates
clip export. Both flags previously existed without gating anything.- Camera settings are
Manage-only including reads —config.jsoncarries
the camera's own logins and, on many builds, the Wi-Fi PSK.
Fixes
- The live WebSocket was write-only, so a viewer's close frame went unnoticed and
the shared ffmpeg process outlived its last viewer. Verified: 8 streams → 0
after the last tile releases. - A camera that never answered a Majestic call was reported as success (204): our
own call deadline surfaced as the same cancellation as an abandoned request.
Now 504. - Player: a viewer joining an already-running stream got a buffer starting at
t=90s withcurrentTime=0and sat paused forever. A watchdog now jumps to the
live edge. ViewArchivewas missing from the users page, which quietly made the archive
ungrantable to anyone created from the web console.- Recording a camera that delivered nothing left a 0-byte file and an archive row.
What's Changed
- Web console: archive, snapshots, camera settings and two-way audio by @keyldev in #60
- web: OpenIPC logo in the console by @keyldev in #61
Full Changelog: v0.3.8-rc1...v0.3.8-rc2
v0.3.8-rc1
Web console: React SPA, PTZ, discovery, and user permissions
Grows the --server-only web console from the Phase 20 foundation into a
self-contained browser client for OpenIPC cameras. The Razor UI it shipped
with is removed — the SPA reached parity and then passed it.
What's new
React SPA replaces the server-rendered UI. Client-routed, so navigating
never reloads the page — and, crucially, never restarts video. Live tiles are
keyed by camera id and survive a grid resize (1 → 9 → 1 keeps the shared
cameras playing), a layout switch, a page flip, and now a route change: the
PTZ from the browser. Pan/tilt/zoom with presets, for cameras that report
PTZ and have a probed ONVIF profile. Movement is stateless on the server: each
move carries an ONVIF self-stop timeout that the client refreshes while a
button is held, so a closed tab cannot leave a camera panning.
Network discovery. ONVIF WS-Discovery, mDNS and an opt-in subnet sweep,
using the same aggregator as the desktop app. A scan is a background job polled
by id, so it survives navigation and a second viewer joins the running scan
instead of starting a rival. Adding is probe → review → add: ONVIF is the only
source of the real RTSP URI and it can be wrong behind NAT, so nothing is
written until the user has seen it.
Users and permissions. Accounts with permission flags
(ViewLive / ViewArchive / Ptz / Export / Manage) and an optional
per-user camera subset. The roster is a JSON file next to the database holding
PBKDF2 hashes only; the built-in admin keeps working as the way back in if the
last Manage account is lost. Authorization is enforced per endpoint — the UI
hides what you may not do, but hiding is not the boundary. A camera outside
your subset answers 404 rather than 403, so ids can't be probed for existence.
Also: saved grid layouts with paging (page = gridSize², like the desktop
head), a single-camera page, mobile layout at the 700px breakpoint, in-app
modals instead of prompt/confirm, and Inter bundled as a self-hosted woff2
(full variable file, not the latin subset, which drops Cyrillic).
Fixes found along the way
Live video never actually played past the first seconds. fMP4 carries the
ffmpeg session's timestamps, so a viewer joining a running stream got a buffer
starting minutes ahead of currentTime, outside every buffered range —
autoplay never fired and the tile sat frozen. A watchdog now seeks to the live
edge and resumes. Pre-existing, not caused by the pooling above (verified
against an element that is never reparented).
Unmatched /api paths returned index.html with 200 text/html via the
SPA fallback; they now 404 with JSON.
Adding a non-ONVIF camera sat through the full ONVIF timeout for nothing.
Breaking
The Razor UI under /app is gone; / serves the SPA. Its form-post endpoints
(/app/config/export|import, /app/sessions/revoke-all) moved to /api/v1
and answer JSON instead of redirects.
OpenIPC.Viewer.Web is a plain SDK project again (no .razor left to
compile); the SPA is embedded in the assembly, so dotnet publish still
produces one self-contained binary. Node is a build-time-only dependency and
CI installs it.
Verification
Exercised live against a real 82-camera database via --server-only:
grid pagination (10 pages, correct windowing, ends disabled), cross-route
persistence (same DOM nodes, currentTime advanced through the absence,
server stream count unchanged), retention expiry (streams 8 → 0, no leak);
layout editing on a middle page preserves the pages before and after
byte-for-byte (checked on a throwaway layout, then deleted);
permissions: a view-only user with two cameras sees exactly those two and gets
403 on camera CRUD, users, discovery, export, PTZ, layouts and revoke-all; an
operator gets 404 (not 403) for PTZ and live on a camera outside their subset;
discovery scan/cancel/409-while-running, probe fallback for a non-ONVIF host,
and add creating a camera with ONVIF metadata persisted;
config export/import and session revoke through the UI.
Builds with 0 warnings (TreatWarningsAsErrors is on repo-wide); eslint and
tsc are clean.
Not verified: PTZ against a physical PTZ camera, and discovery against
cameras on a real LAN — the dev machine has neither. Both paths are exercised
only through their error/fallback branches and a stubbed scan payload.
What's Changed
Full Changelog: v0.3.7...v0.3.8-rc1
v0.3.7
What's Changed
Full Changelog: v0.3.6...v0.3.7
v0.3.6
What's Changed
- chore: simplify issue forms by @keyldev in #56
- feat: opt-in single-instance mode (desktop) by @keyldev in #57
Full Changelog: v0.3.5...v0.3.6
v0.3.5
v0.3.4
What's Changed
Full Changelog: v0.3.3...v0.3.4
v0.3.3
v0.3.2
What's Changed
Full Changelog: v0.3.1...v0.3.2
v0.3.1
What's Changed
Full Changelog: v0.3.0...v0.3.1
v0.3.0
What's Changed
- feat: animated camera-eye splash loader by @keyldev in #37
- feat: wire CameraStatusRegistry into grid, library, Health Center by @keyldev in #38
- feat: Discovery v2 foundation — model + source abstraction + aggregator by @keyldev in #39
- feat: firmware-lite backend — reboot / time / logs over SSH (Slice A) by @keyldev in #40
- feat: majestic control center by @keyldev in #42
- feat: grid pager, fullscreen kiosk, network config sync by @keyldev in #43
- fix: ONVIF add fallback, mobile dialog polish, Majestic editor, SSH host-key trust by @keyldev in #44
- feat(onvif): trim-safe SOAP client (HttpClient + XDocument), replace by @keyldev in #45
- feat(stills): grid-wide snapshot mode by @keyldev in #46
Full Changelog: v0.2.2...v0.3.0