How live view reaches your browser
NovaVMS sends live video from your cameras to your browser (or phone) through three transports that share the same pipeline:
- WebRTC — the default for desktop browsers and for H.264 mobile clients. Sub-second latency.
- MSE/fMP4 over WebSocket — the desktop-browser fallback when the browser cannot decode the camera’s codec via WebRTC. ~1-3 second latency.
- LL-HLS — the dedicated path for H.265 on mobile (and any other client where WHEP+WebRTC is not viable). 2-25 second cold start, then near-live.
The gateway handles the camera; the SFU in the cloud relays packets to everyone watching; the browser (or AVPlayer / ExoPlayer on mobile) decodes (per D5).
Why this works this way
There is no single video transport that works for every codec in every client. WebRTC gives sub-second latency and is browser-native, but a browser only plays a WebRTC stream if its decoder accepts the exact codec the camera is sending. H.264 works almost everywhere. H.265 (HEVC) works in Chrome 136+, Edge 136+, and Safari 18+ on the desktop, but not in Firefox. On mobile, the react-native-webrtc stack does not support HEVC, so H.265 cameras must use a different transport entirely. Some older cameras speak only H.265, and transcoding them to H.264 burns gateway CPU we would rather spend on recording.
We resolved this by picking three transports and routing per codec and per client type.
- WebRTC is the default path for browsers. The gateway (or the cloud’s embedded go2rtc, for direct-RTSP cameras per D41) terminates RTSP from the camera, repackages into RTP, and hands packets to the cloud SFU. The cloud SFU relays RTP to every viewer’s PeerConnection without re-encoding. The cloud SFU is always in the media path; the “P2P” badge in the player refers to the ICE candidate type (
host/srflx) between your browser and the cloud SFU, not between you and the gateway. TURN is embedded in the cloud backend (per D42) so strict NAT or corporate firewalls still complete the handshake. H.265 is passed through natively when the SFU is built withSFU_H265_ENABLED=true(per D22-REV and D45) — no transcode tax. - MSE/fMP4 is the desktop-browser fallback. When the browser cannot decode the camera’s codec in WebRTC, the gateway tunnels a fragmented MP4 stream to the SFU over WebSocket, the SFU forwards it to the browser via a WebSocket bridge (
/api/v1/streams/{cameraId}/mse), and the browser plays it using the Media Source Extensions API. Latency is higher (1-3 seconds), but Firefox users watching H.265 cameras still see video. - LL-HLS is the mobile path for H.265. Mobile clients (iOS / Android) get a signed manifest URL via
GET /api/v1/hls/{cameraId}/signed-url, fetch the LL-HLS master + media playlists, and play through AVPlayer / ExoPlayer. The cloud server writes CMAF parts to disk from the same fMP4 tunnel the MSE path uses; the writer code incloud/internal/hls/writer.gocarries three explicit fixes for cold-start reliability (window scaled to 60 seconds of wall-clock buffer, per-sample duration derived from the trun box of the first fragment, 45-second file grace TTL so cached playlists keep playing). Cold start is bounded byHLS_COLD_START_WAIT(default 25s) to cover slow ONVIF cameras like the Dahua LC-TA3R.
The gateway is also the place where we refused to depend on FFmpeg. Per R3, the ring buffer used for recording and the cloud-side LL-HLS writer and MSE relay are all pure-Go code paths. That keeps the gateway a single static binary and avoids the long tail of FFmpeg version bugs.
This design matches what Verkada does in principle (their live view is WebRTC-first, MSE-fallback) but differs in detail: Verkada transcodes everything to H.264 in the cloud. We do not, because our self-hosters cannot be asked to pay that compute bill on every camera.
What this means for you
- Live view starts fast on WebRTC. Time-to-first-frame for warm WebRTC streams sits under three seconds for most cameras from a cold start. LL-HLS cold-cold mounts on slow ONVIF cameras can take up to 25 seconds — that’s normal and bounded by the cloud cold-start cap.
- If the tile shows “Waiting for video” and eventually turns into MSE playback with a one-to-two-second delay, your browser rejected the WebRTC offer. This is usually H.265 in Firefox. Try Chrome, Edge, or Safari instead.
- Grids of many cameras use the same transport per tile. A mix of WebRTC and MSE tiles in one grid is normal.
- The connection-quality badge in the player shows whether you are on P2P (host/srflx ICE candidate to the cloud SFU) or RELAY (TURN-relayed). Click the badge to open the stats panel; the
Transportrow tells you whether the tile is WebRTC, MSE, or LL-HLS. - If live view never starts at all, it is almost never the transport — it is usually the gateway, the camera, or an ONVIF credential issue. Start at the troubleshooting pages linked below, not at the browser.
Where to go next
- Live view does not show video — the symptom-level fix path
- Ports and protocols — exact UDP/TCP ports WebRTC and TURN use
- Why NovaVMS uses a single AI call — the analysis pipeline that runs alongside, not inside, the live-view path