Troubleshooting: video room and call quality — WebRTC, recording QC, and SIP dial-out
A video-quality complaint is different from a video room that never connects: the room opens, participants join, and then the experience is broken — the video freezes while audio carries on, one participant drops out, the recording lands flagged as degraded, or a PSTN dial-out into the room never bridges. None of those are fixed by re-creating the room. Each quality class maps to a specific layer — WebRTC media negotiation, the participant’s network path, the track publish grant, the recording pipeline, or the SIP dial-out leg — and this page works each one as a decision path. Video rooms run over WebRTC on Orbit Media, not over SIP RTP like a trunk call: media setup is ICE/STUN/TURN election instead of SDP address advertisement, and the dominant codec family is H.264/H.265 video rather than telephone audio. That makes the diagnosis paths different from Troubleshooting: voice call quality — read that page only for the PSTN leg of a dial-out, never for the in-room media. For the create/join/record contract itself, start with the Video channel page and the Video API reference.Symptom → layer index
Find the symptom row first, then go to the section below that works that layer.Cause map
Every symptom above reduces to one of these causes. Work the symptom row first; the sections below give the fix path per cause.Recording QC verdicts
Orbit scores every completed recording after the room ends. When the artifact lands — or fails to — with an ambiguous quality signal, avideo.recording.degraded webhook fires alongside
video.recording.completed:
status is "missing" when no usable artifact was produced at all and
"degraded" when an artifact exists but is suspect. The scorer is
deliberately pessimistic — it prefers a false alarm over a silent loss.
Read the reasons array first; it maps directly onto the fix:
One structural rule from the table:
no_participants_joined and
short_duration are usually room-usage signals, not platform faults —
the platform exactly reported what happened. The first three reasons are
different: they mean the recording pipeline said it would produce a file
and did not. Those are the escalate cases.
The QC verdict is also persisted on the room: fetch the room and read
recording_qc off the completed session to see exactly what fired, with
its score time — you do not need the webhook receiver to be live to audit
a past meeting.
Participant loss and buffer stalls
A participant freezes, drops, or their tile darkens mid-room — or the video stalls in bursts while audio carries on. WebRTC recovers continuously, so a video freeze with live audio is almost always the network path, not a broken client: the video layers get shed first. Work the decision path in order:- Is it one participant, or everyone? One participant’s network is
the cause when the rest of the room stays clean. Read the live
per-participant QoS telemetry before changing anything:
video.participant.qosfires when a participant’s connection quality crosses into a degraded bucket mid-session, and the same data is readable on demand atGET /api/v1/video/connection-quality/:name/participants/:identity/stats— each sample carriesquality_label(excellent/good/poor/lost),quality_score(0–5),rtt_ms, andjitter_ms. A participant pinned atpoororlostwith rising RTT is a network problem; everyone in the room degrading together points at the shared path. - Does it only happen on one network? A room that is fine everywhere except one corporate network is the classic firewall signature: UDP media blocked, the STUN/TURN election never completes to a workable path, and ICE either falls back to a slow path or dies. The NAT glossary entry covers why real-time media needs a reachable address after translation. Two tenant-side fixes close nearly every case: disable SIP ALG on the site firewall (it rewrites media-address information “to help” and breaks WebRTC-adjacent flows the same way it breaks SIP RTP — the same fix the voice page calls out for one-way audio), and allow the SFU’s published TCP/TLS fallback port so the client can relay when UDP is blocked. Forcing relay through TURN is the deliberate, correct fallback — a working relay path beats a broken direct path every time.
- Does it stall exactly when the picture is busiest? Then the
bandwidth floor is unmet: the simulcast ladder picked a video layer
the link cannot hold, the queue backs up, and the video freezes while
audio (which needs a fraction of the bitrate) keeps flowing. No
rejoin fixes an undersized uplink — the participant needs the link,
or a smaller send profile. The QoS sample’s
quality_scoredropping in step with the stalls confirms this before any platform change. - Is an ICE policy pinned in your client? If you embed with your
own LiveKit client configuration rather than the prebuilt embed, an
over-restrictive ICE election policy (relay-only when no relay is
reachable, or a pinned candidate family the network blocks) produces
rooms that connect in the test lab and fail in the field. Correct the
ICE election policy to let the client contest all candidate types;
the prebuilt
<orbit-video-room>element ships defaults that already do.
No video but audio present
The participant speaks, everyone hears them, and their tile shows nothing. Unlike a stall, this is a track problem: the video track was never published, or the endpoint cannot produce it.Echo
One side hears itself returned a moment after speaking. Echo is almost never introduced mid-path: a far-end device (speakerphone, handset, headset) or the bridged PSTN leg is reflecting the stream, and the SFU is passing it through faithfully. The dialled-in PSTN participant is the highest-echo suspect — a phone on speaker in the room behaves exactly like the speakerphone case on the voice side. Isolate by changing one endpoint variable at a time — headset instead of speakerphone, a different dialled-in number — and escalate with the room evidence below when echo follows one specific destination or device.SIP dial-out failures
POST /api/v1/video/rooms-scheduled/:id/dial-out originates a PSTN or
SIP leg and bridges the callee into the room. The failures divide
cleanly: the request fails synchronously (a 4xx/503 response — read the
error code on the Video channel page), or the leg
starts and then fails asynchronously, firing video.dial_out.failed:
- Trunk rejection — the softswitch rejected the INVITE. Work it with
Troubleshooting: SIP trunk registration, health, and failover:
an unregistered or failing trunk rejects every dial-out that routes
over it, and a H.264/H.265 mismatch on a
type: "sip"destination surfaces here too — the far end declines the offered video profile. Pin thevideo_*encode fields to a profile the destination accepts instead of retrying the same offer. - Callee busy or unreachable — the destination never answers into a bridgeable state. Verify the number by calling it directly; a busy or dead destination is not a room problem.
- Ring timeout before answer — the
ringing_timeout_secbudget ran out. Auto-answer chains (IVRs, call queues, voicemail) routinely outlast a short budget; raiseringing_timeout_secwhen the callee is expected to take time to reach a human. When the callee answers and the room then feels downstream congestion, throttle the outbound leg with thevideo_bitrate_kbpsprofile rather than redialling.
sip_headers, the failure webhook
echoes the same map back unchanged — correlate the failure against your
own ticket or account id from the event alone.
What not to do
- Do not restart partial recordings in a loop. When a degraded
verdict arrives, restarting the recording on the same ended room gains
nothing — the session is over and the media is gone. Read the
reasonsarray against the QC table, fix the named cause, and let the next room record cleanly. - Do not re-create the room for a media problem. A new room id changes nothing about ICE election, the participant’s uplink, or a firewall — the same join lands in the same network. Diagnose the one participant’s QoS sample instead.
- Do not keep redialling a failed SIP leg against the same trunk state. A trunk that is rejecting INVITEs rejects the tenth dial the same as the first; fix the trunk or the codec profile, then dial once.
- Do not paste raw SDP/ICE dumps into a first ticket. Escalate to the SDP and ICE traces only when the platform-side checks above come back clean — the QoS sample, QC verdict, and checklist below resolve most reports without a packet-level dive.
What to send support
Work the relevant section first; if the symptom persists, open a ticket with the full set below — it is what lets support pull the exact room and session without a back-and-forth:- Your tenant / organization ID (Settings → Organization, or
organizationIdfromGET /api/v1/me). - Room id (
room_idfrom the room — or off anyvideo.*webhook) and the server-sideroom_sidfrom the join response or thevideo.room.started/video.room.endedpayload. - The broken window in UTC — when the room ran and when the symptom appeared, so the session and QoS samples line up.
- The participant identity (
identityfrom the join token) of the affected participant, and their last-knownquality_labelfrom the QoS telemetry if you captured it. - Whether a SIP dial-out is involved — and if so, the
video.dial_out.failedpayload (the echoedsip_headersincluded), thetype(pstnorsip), and the destination you dialled. - For recording complaints, the full
video.recording.degradedpayload —status, thereasonsarray, and thesession_id.
See also
- Video channel: rooms, embeds, recording, and broadcast — room kinds, join tokens and grant tiers, recording, limits, and the dial-in/dial-out overview.
- Video API reference — the request/response contract for rooms, recording, connection-quality, and dial-out.
- Webhook events reference — payload
contracts for
video.recording.degraded,video.participant.qos,video.dial_out.failed, and the room lifecycle family. - Troubleshooting: voice call quality — the SIP/RTP counterpart for the PSTN leg of a dial-out; one-way audio and dead air on a bridged phone participant live there.
- Troubleshooting: SIP trunk registration, health, and failover — trunk rejection and failover for the dial-out leg.
- Glossary — the NAT, STUN/TURN, and ICE definitions this page builds on.