WebRTC recovery test protocol

Turn a recovery claim into reproducible evidence. Record the failure, the recovery step, and whether decoded media actually returned.

Method for staging tests ยท last revised 26 October 2026

This is a method, not a benchmark result. A green connection badge is an intermediate state; a new decoded frame is evidence that the viewer recovered.

Scope

Use this protocol for one specific failure path, not as a general WebRTC benchmark. Record these fields before running a case:

FieldExample
Product and commitmeet-app@abc123
Publisher / viewerChromium 142 on Windows / Safari 26 on iOS
WebRTC stackNative browser APIs, Pion, LiveKit, Janus
SignalingWebSocket, WHIP, SDK-managed
TURNrelay.example.com:3478 via UDP
Network setupWi-Fi to 4G transition; 5% packet loss
Publishing topologyOne publisher, one viewer
Failure seen in productionRemote video frozen while ICE remains connected
Production frequency1 in 20 reconnects under CPU load

Treat secrets and production user data as out of scope. Use a staging endpoint and test accounts. The server may run in Docker or on bare metal, but record the exact image tag or commit.

Instrumentation

Sample every second for the whole run. Record the publisher and viewer clocks so events can be lined up after a failure.

LayerFieldWhy it matters
TransportconnectionStateShows whether the browser considers signaling and ICE up.
TransporticeConnectionStateSeparates an ICE failure from connected-but-frozen media.
Transportselected candidate typesShows whether the run used direct, STUN, or TURN relay.
TransportRTT and packets lostCaptures the network path at the failure boundary.
Inbound RTPframesReceivedProves packets arrived even when nothing was rendered.
Inbound RTPframesDecodedProves the decoder made progress. This is the primary health signal.
Inbound RTPfreezeCount where supportedRecords renderer-level freezes without relying on them alone.
Playbacktime since last callbackUses requestVideoFrameCallback as a secondary check in a visible tab.
Recoverytier and attempt numberShows whether wait, ICE restart, or full rejoin ran.
Recoveryrecovery start timeAnchors the deadline for the next escalation.
Recoverytime to first decoded frameMeasures whether recovery actually finished.

Do not use DOM presence, a connected badge, or a single currentTime sample as the definition of recovery. A background tab may stop painting while still decoding, so on unattended runs prefer framesDecoded deltas over render callbacks.

Case 1: connected but frozen

  1. Establish one publisher and one viewer and wait for at least 30 decoded frames.
  2. Prevent new media from reaching the viewer without closing the transport. On a controlled test source, pause the outbound video track for 8 seconds.
  3. Keep the transport up and observe the viewer for 4 seconds.
  4. Record a failure if framesDecoded stops increasing for 2 seconds while iceConnectionState is still connected or completed.
  5. Allow the first recovery attempt. Mark its start time.
  6. Pass only when a new decoded frame arrives and framesDecoded keeps increasing for the 10-second stability window.

Case 2: TURN-only path

  1. Create the peer connection with iceTransportPolicy: "relay".
  2. Confirm the selected candidate pair contains a relay candidate.
  3. Wait for 30 decoded frames.
  4. Interrupt the relay path, then bring it back.
  5. Record the ICE state and candidate pair on both sides of the interruption.
  6. Pass only when relay allocation succeeds again, a new frame is decoded, and frame progress continues for 10 seconds.

Run this case without relying on a host candidate as a hidden fallback. A session that only recovered because it silently switched to a direct path does not pass the relay-only test.

Case 3: restart without frames

  1. Establish a healthy viewer and preserve its last good RTT, frames decoded, and candidate pair.
  2. Break media forwarding while leaving signaling available.
  3. Trigger an ICE restart and record the event.
  4. If ICE returns to connected but no new frame is decoded within the agreed deadline, record restart_no_frames.
  5. Require the next recovery step to be a single full rejoin. Repeating ICE restart without a new frame does not count as progress.
  6. Pass only when the rejoin produces a decoded frame and frame progress continues for 10 seconds.

The default deadline can be 5 seconds after ICE returns to connected. Write a different value into the test record if the product needs one.

Case 4: full rejoin

  1. Establish a healthy session and capture its last good frame timestamp.
  2. Close the peer connection, or terminate the relevant server-side media session.
  3. Start a full rejoin and record the start time.
  4. Verify that the old peer connection and track handlers are detached. A stale remote track must not be reused as the recovered track.
  5. Pass only when a fresh session reaches connected, a new frame is decoded, and frame progress continues for 10 seconds.

Treat a successful SDP exchange or a green connection badge as intermediate state, not a pass.

Failure record

Copy this block into the issue or pilot report:

Case:
Product / commit:
Server / image:
Publisher:
Viewer:
Network and TURN:
Started at:
Production frequency:

Last good frame:
failure detected:
connectionState:
iceConnectionState:
candidate pair:
RTT / loss before failure:
framesDecoded before / after:
recovery tier started:
restart return to connected:
full rejoin started:
first new frame:
stable for 10 seconds: yes / no

Visible symptom:
Client log:
Server log:
Artifacts:

Attach the raw client and server logs, the statistics samples, browser and platform versions, and a short recording if the visualization is relevant. Redact tokens, credentials, personal data, and internal hostnames before sharing.

Coverage and limits

Run every case three times in each agreed browser and network profile. Record all runs, including passes, rather than reporting only the failing replay. A harness that changes browser, network, or server versions between runs cannot attribute the result to the recovery change.

This protocol measures the recovery path. It does not prove codec quality, capacity, recording correctness, or a production SLA. Those require separate tests.

Have a real failure case? Describe it for a scope check. Need help adapting this protocol to your stack? Email us.