Skip to content

phrame-webrtc-session — one viewer, one stream

phrame-webrtc-session — one viewer, one stream

Section titled “phrame-webrtc-session — one viewer, one stream”

Where output-processor fans one production out to many destinations, this does the opposite: one process serves one browser, directly, with the shortest path the technology allows.

  • Serves: exactly one authenticated viewer, over WHEP
  • Reads: a video and an audio stream from the Phrame bus
  • Returns: the viewer’s microphone (talkback) into a Phrame bus audio stream

The egress page describes output-processor publishing WebRTC over WHIP. That is the right tool for distribution: encode once, hand it to something that fans it out.

A monitor is a different problem. There is one viewer, watching one compositor, and what matters is how far behind the glass is — not how many people can watch. Sending that through a relay adds a hop, a buffer and a re-packetisation to a path whose entire purpose is to be short. The relay’s fan-out and muxing buy nothing when the audience is one.

So this service is a direct WHEP server: the browser connects to it, and there is one media hop between the compositor and the picture. It replaced an earlier stack — a C++ sender, Flask signalling, an MQTT SDP bus and a relay — that did the same job with four moving parts instead of one.

One process, one stream, one viewer. That is a security boundary, not a performance tuning choice.

A second authorised offer arriving while a viewer is connected is refused with 409 Conflict, protecting the viewer already watching — unless it comes from the same user, which is treated as a genuine reconnect replacing its own stale session.

When a viewer leaves — an explicit WHEP DELETE, a dropped ICE connection, or an unconnected offer whose token expired — the lock is released and the WebRTC state is reset, but the process stays up, still bound to its long-lived compositor. Reconnects are therefore warm rather than a cold start.

The service verifies a bearer token before it will accept an SDP offer.

Tokens are signed asymmetrically, and the service holds only the public key — so it can verify a token and cannot mint one. A compromised media node cannot issue itself access to anything.

The token is minted by the layer that already knows who the user is, and it is deliberately narrow: short-lived, single-use, scoped to one audience and bound to the specific stream id this process serves. The claims are all checked — audience, expiry, not-before, subject, stream id, and a nonce cached for the token’s lifetime so a captured token cannot be replayed.

No token, or an invalid one, is a 401. A valid token for a different stream is a 403.

The reasoning behind that shape is worth stating, because it is a trade: an opaque token checked against a central service on every connect would cost a round trip and a hard runtime dependency at exactly the moment a viewer is waiting. A token that lives seconds is effectively revocable anyway. Forwarding the session cookie instead would mean this media service had to understand the session store — a much bigger thing to hold, and a much bigger thing to leak.

A viewer joining should see a picture immediately, and the honest obstacle is that a decoder cannot start on an arbitrary frame.

Three things address it. The GOP is short — about a second by default — so a natural key frame is never far away. When the browser asks for one, on connect or after loss, the encoder forces the next frame to be a key frame rather than waiting. And a low playout-delay hint is advertised so the browser is discouraged from buffering more than it needs.

The middle one does most of the work: it gives near-instant lock on connect without paying the bitrate cost of a permanently tiny GOP.

The path runs both ways. The service accepts the browser’s microphone and writes it back into a Phrame bus audio stream, so a monitor is also a talkback position without a second system to configure.

Any surface that needs one person watching one compositor with as little delay as possible: an operator preview, a confidence monitor, an editing monitor. Two instances can serve two monitors of the same production independently, each on its own port.

The service is keyed on a Phrame bus stream id rather than on anything compositor-specific, so adding a viewer to a new stream is configuration, not code.