The video room model
Video is a first-class Orbit channel: a meeting joins an inbox conversation, triggers the same recording lifecycle as a call, and feeds the same analytics and QA stack as voice. This page explains the room model itself — how rooms are created, how they live and die, how participants get in, and where the observability surfaces hang off it. The endpoint-by-endpoint reference lives in the Video API reference; this page is the model those endpoints implement.Scheduled versus ad-hoc rooms
A room is created scheduled or ad-hoc. The difference is a timed access window versus an open one.
The split is structural, not cosmetic: a scheduled room carries an intent
record that can recur across many sessions (a recurring meeting re-uses the
same room row each time it convenes), while an ad-hoc room is the session
itself. History lives in
video_room_sessions — one row per realised room
lifetime, scheduled or ad-hoc — so the “Recent rooms” panel and the
retention sweep see both uniformly.
Room templates are the middle case: a saved configuration you instantiate
either live or scheduled. A template has no session history of its own until
it is instantiated.
Lifecycle: created → active → closed
A scheduled room moves a status workflow: pending → ready → live → ended. Ad-hoc rooms arelive as soon as the first participant joins.
- created — the room row exists, and the intent (if scheduled) is persisted. No media is running yet.
- active (live) — at least one participant is in the room. For scheduled rooms the create-step auto-recording hook deliberately skips “future” rooms and arms on first join instead, because the media server reaps unjoined scheduled rooms.
- closed (ended) — the room is closed when the host ends it, when the
empty-room reap fires, or when a broadcast egress is stopped. The
room_finishedwebhook persists the final state (max participants, join events, totals, recording IDs) intovideo_room_sessions. Closing is a host action, not a participant one.
video_room_sessions row rather than mutating the intent. Retention is
per-workspace and sweeps soft-then-hard-delete session rows after the
retention window — the same scheduler that handles call recordings.
Join tokens
Joining a room returns an access token — a short-lived JWT scoped to one room and one identity. It is an SDK credential, passed toRoom.connect(url, token); it is never an HTTP Authorization header.
Two tiers matter in the response:
- host token — the room creator’s join token (minted by the create
endpoint as
host_token). - participant token — minted by
POST /video/rooms-scheduled/{id}/joinfor a named identity, or purchased by a guest who redeems an invite link viaPOST /video/invites/{inviteToken}/redeem.
expires_in_seconds + an absolute expires_at); re-mint a
fresh token for the same identity before expiry to avoid dropping
mid-session. The response also carries the effective grant — a
participant_tier (viewer, panelist, host, hidden_supervisor) plus
permissions after any host downgrade or waiting-room clamp — which your
client must respect before showing publish controls. The full flow, the
exact connection call, and the guest-invite alternative live in
Video room access tokens.
Recording and broadcast
Recording is opt-in per room (recording_enabled on the create body).
Auto-start is deferred for scheduled-future rooms, and the egress handle is
recorded on the session row when the room closes. Composite (one mixed room
render) and per-participant track egress are separate paths —
per-participant egresses are a fan-out, so they persist on the room’s
settings rather than the session’s single recording columns.
Broadcast egress is a separate discipline from in-room participation:
- RTMP/HLS egress (
POST /:id/egress/rtmp,POST /:id/egress/hls) pushes the live stream to a CDN or an external endpoint. - Broadcast viewers consume over HLS/WHEP through a CDN — they are
not in-room participants. Their audience signal is each viewer
player’s own heartbeat (
POST /:name/heartbeaton/video/broadcast-viewership), maintaining a sliding-window concurrent-viewer count in Redis. - Registrants are the people who signed up before the room went live —
check-in and approval/decision flows live on
/video/rooms-scheduled/{id}/registrants. A registrant becomes an in-room participant only at join; a viewer may never join at all.
Analytics model
Three aggregates split video analytics by scope. Knowing which one to read avoids a cross-window fan-out:- Room usage (
/video/room-usage-analytics) — org-level usage over time: sessions, peak/average concurrent participants, room-minutes, recording success rate, join failures. Answers “how is my org using video.” This aggregatesvideo_room_sessions. - Engagement (
POST/GET /rooms/:name/sessions/:sessionId/engagement) — per-session, per-participant: talk-time share, screen-share duration, hand-raises, merged dominant-speaker timeline. The host browser buffers active-speaker / screen-share / hand-raise signals during the meeting and flushes them at room-end — which keeps the hot ~10 Hz SFUspeakers_changedwebhook path write-free. - Chapter analytics (
/video/chapter-analytics) — cross-recording theme aggregation: “Pricing objection”, “Demo walk-through” — per-theme average duration across a date window. Read-only; chapter rows are written by the chaptering pipeline onrecordings.chapters.
Live interpretation and captions
Live interpretation is the spoken interpreter channel — the audio analog of the translated caption line. A participant’s client posts one finalized caption utterance plus a target language to/video/live-interpretation,
and the platform returns the spoken audio; captions/translate and this
route both derive from the caption/STT pipeline, but one renders on-screen
text while the other renders speech. Captions themselves are buffered by the
host client during the meeting and flushed at room-end along with engagement
— the same buffer-then-flush shape — so a post-meeting transcript and a
post-meeting summary can be built without touching the hot SFU webhook.
Video QA scoring
QA scoring mirrors the voice coaching pipeline for the video pillar. The flow, on request for a session:- Cache check on
video_room_sessions.metadata.qa_score— a cached scorecard is returned unlessregenerateis set. - Resolve the session row (tenant-scoped; soft-deleted rows return
NOT_FOUND). - Resolve the transcript —
transcript_override→ metadata caption frames → persistedvideo_room_transcripts(same fall-through the post-meeting summary uses). - Compute talk-balance deterministically from
video_participant_engagement(no LLM): per-participant talk share plus a normalised-entropy balance score. - Run an LLM judge over the redacted transcript (PII stripped first, mirroring voice QM scoring) to score compliance and per-dimension quality, and emit coaching tips.
- Persist the merged scorecard back into
metadata.qa_scoreand return.
has_data:false on the engagement read,
never a 500, and the QA step itself never initiates outbound signalling —
reporting only.
Diagram: scheduled versus ad-hoc
Related
Video — channel overview
The channel-level page: what video meetings, webinars, and broadcasts do.
Video meetings — guide
How to schedule, join, invite guests, and run a meeting end to end.
Embed a video-consultation button
Put a “video call us” button on your own site — the embed consumes the
same join-token flow.
Recording lifecycle
The egress lifecycle webhook family recordings land in.
Room access tokens
Full token scope, expiry, and guest-invite semantics.
Video API reference
The endpoint-by-endpoint contract this model is implemented by.