Skip to main content

Monitor in-call video quality

Video quality on Orbit is a loop with three stages, each with its own endpoint surface:
  1. Before the call — the preflight check verifies the joiner’s devices and network path before they ever enter the room.
  2. During the call — each participant’s SDK reports its own connection stats, and you read a live per-room snapshot to see who is having a bad call right now.
  3. After the call — the QoE report aggregates the session’s samples into p50/p95/p99 latency, jitter, and packet-loss, per-participant rollups, and drop-offs, retained for 24 hours after the last sample.
Use the preflight as a gate in your join flow, the live snapshot as your operations monitor, and the report as your after-call troubleshooting and SLA evidence.

Where each stage fits

All routes are under https://api.orbit.devotel.io/api/v1. Room names are scoped to your tenant automatically — a caller can never read another tenant’s room snapshot.

Step 1 — Gate the join with a preflight check

Fetch the preflight configuration and run it on the joiner’s device before you let them into the room:
The response hands the browser everything it needs to exercise the path:
  • ice_servers — STUN entries plus short-lived TURN and TURNS (TLS) credentials, so the check validates the exact relay path the real call will use, including restrictive-NAT cases where only TURN succeeds. Credentials expire after 60 minutes; fetch preflight close to join time rather than caching it.
  • signaling_url — the media signaling endpoint to probe.
  • checks — the pass/fail thresholds for each check: a 150 kbps send and receive floor and a 400 ms RTT ceiling for a camera-on join, over a 10-second network test.
Run the checks and present a deterministic “mic OK / camera OK / network OK” result in your join sheet. A participant who fails the network check should be offered audio-only join instead of discovering the problem mid-call. If the endpoint returns 503 SERVICE_UNAVAILABLE, the video service is not configured on the cluster — surface that as a configuration problem, not a failed joiner.

Step 2 — Report stats during the call

The media SFU does not expose per-participant transport stats — those live only in each participant’s browser. So the integration model is: every participant’s SDK rolls up its own RTCPeerConnection.getStats() sample and POSTs it on a cadence (every 5–10 seconds is the usual interval).
The response carries an advisory block: when a sample shows congestion, the advisory names the concrete encoding the publisher should fall back to — suggested bitrate, recommended resolution, framerate, and degradation preference — so a constrained or mobile participant degrades deterministically instead of freezing. Apply the advisory to the sender’s encoding parameters immediately; pass content_hint: "screen_share" when the stats are for a screen-share track so the screen ladder (sharpness over framerate) is picked instead of the camera ladder.
The reporting call needs a video:write-scoped credential — the same scope your SDK already holds to join a room. Reads (the snapshot and report below) accept either video:read or video:write.

Roll up getStats() in the browser

The browser-side shape is a short rollup over one getStats() pass. Pick the outbound-rtp entry for the video sender and the candidate-pair entry for the transport:
Derive quality_score from your own thresholds (jitter, loss, and RTT against the preflight bounds are a workable formula) and clamp it to the 0–5 range. The server clamps every metric to a sane range anyway, so a buggy SDK build cannot poison the monitor with NaN or runaway values.

Step 3 — Watch the live snapshot during a call

GET /video/connection-quality/:name is the “who is having a bad call right now” view — one row per still-reporting participant:
The snapshot is a recency signal: a participant’s row lives for 30 seconds after their last sample. A participant who stopped reporting — left, crashed, or lost connectivity entirely — drops out of the snapshot within 30 seconds, so the view always reflects the live room. An empty participants list means the room is idle or nobody has reported recently, not that everyone is healthy. quality_label buckets the score into excellent (4–5), good (2–3.99), poor (above 0, below 2), and lost (0). Poll this endpoint from your operations dashboard every 10–30 seconds while a supervised call is live. For a single participant, GET /video/connection-quality/:name/participants/:identity/stats returns that one row — useful for an SLA probe against a specific caller. It returns 404 when the participant is not currently reporting (never joined, left, or their sample aged out past the live TTL).

Step 4 — Read the post-session QoE report

After the call, GET /video/connection-quality/:name/report aggregates the session’s retained samples into the after-call view:
Read it this way:
  • Room-level percentilesrtt_ms, jitter_ms, packet_loss_pct, and quality_score each carry p50 / p95 / p99 over every sample in the session. The p95 and p99 tails are where “the call felt bad for a few minutes” shows up after the p50 looks fine.
  • Per-participant rollups — each participant gets the same percentile block plus a quality_score_min, a worst_quality_label, and a dropped_off flag. dropped_off: true means the participant stopped reporting well before the session’s last sample — they left abruptly or lost connectivity; drop_off_count sums those across the room.
  • Retention — samples are retained for 24 hours after the last sample of the session, then the report expires. Pull and archive any report you need as SLA evidence within that window. With no retained samples you get an empty-but-valid report (sample_count: 0), not an error.

Step 5 — Wire the operations playbook

Quality telemetry earns its keep when it drives an action. The landed patterns:
  • Subscribe to the quality-drop webhook. When a participant’s sample crosses into poor or lost from a healthier bucket, Orbit fires a video.participant.qos event to your tenant’s webhook endpoints. It is edge-triggered — one event per transition, not one per heartbeat — so it is safe to page on. The payload carries the room, identity, both quality labels, the raw metrics, and the same encoding advisory the reporting SDK received.
  • Alert on the live snapshot. Poll GET /video/connection-quality/:name during supervised sessions and raise an alert when any participant sits at poor or lost for two consecutive polls — a single transient dip is normal; a sustained one is a supervisor page.
  • Act, don’t just watch. The advisory block on both the report response and the snapshot row names the fallback encoding. For a managed experience (telehealth, support), apply it to the participant’s sender automatically; for a meeting, surface “Bob’s connection is unstable — try audio-only” to the host.
  • Correlate against cost and usage. A chronically degraded room is also a billable-usage question — pair the QoE report with the room-usage analytics in Read the cost-intelligence dashboards when a tenant disputes video spend on quality grounds.
  • Never block the call on monitoring. The whole surface is best-effort: when the monitoring store is unavailable, POST and GET return 503 SERVICE_UNAVAILABLE — never 500, and never a reason to fail a join or hang up a call. Treat a 503 from these routes as “skip monitoring this interval and keep the call,” and alert on the 503 itself only if it persists.
Do not gate POST /video/rooms-scheduled/:id/join on a connection-quality or preflight response. A monitoring outage must never stop people from joining; run both surfaces as side inputs to the call, in parallel with it.

Where to go next