Skip to content
On this pageOPEN
  1. 01Goal
  2. 02Plain-language concept
  3. 03Interactive diagram
  4. 04Behind the scenes
  5. 05Code walkthrough
  6. 06Do & don't
  7. 07Practice
  8. 08Quick quiz
  9. 09Key takeaways
  10. 10Common errors
LESSON 08Core26 min read

The RTCPeerConnection lifecycle

Master connection, ICE, signaling, and gathering states so you can diagnose exactly where a call is stuck.

#RTCPeerConnection#ICE state#connection state#renegotiation
01

Goal

  • 01Read signaling, connection, ICE, and gathering state as four separate timelines.
  • 02Locate the first broken transition instead of staring at the final 'failed' label.
  • 03Implement reconnect, restart, and cleanup paths that do not duplicate sessions.
02

The concept, plainly

`RTCPeerConnection` exposes four state machines. **signalingState** tracks the offer/answer protocol: stable, have-local-offer, have-remote-offer, and closed. **iceGatheringState** tracks whether local candidates are still being discovered: new, gathering, complete. **iceConnectionState** tracks connectivity checks: new, checking, connected, completed, disconnected, failed, closed. **connectionState** aggregates ICE and DTLS into new, connecting, connected, disconnected, failed, closed.

A connection is not a single event. The browser may spend time gathering candidates while signaling is already complete. ICE can report checking for several seconds while getStats shows pairs attempting checks. DTLS runs after a candidate pair succeeds. Media appears only after all of that. If you log only `onconnectionstatechange`, you miss where the time went.

SignalingState is the one developers most often misread. `have-local-offer` means an offer exists and the answer has not been applied yet. `have-remote-offer` means an offer arrived and the local answer is pending. Applying an answer in stable or applying a second answer is a protocol error, not a network failure. Reset the peer and start a fresh generation rather than force stale descriptions.

ICE disconnect is not always fatal. A temporary path change can move to disconnected while the browser tries to recover. ICE failed means checks are exhausted for this generation; an ICE restart is appropriate. `connectionState === "failed"` usually means the overall transport has failed. Listen for both, back off, and tell the user what is happening. Do not spin in an immediate reconnect loop.

Gathering can complete with zero candidates when a network, permission, or policy prevents discovery. That is different from ICE failure. Inspect candidate counts and `icecandidateerror`. A completed gathering state with only host candidates may still connect on a LAN; the same state across the internet will not. The state names describe process, not adequacy.

Design a session as a generation: a room id, a local connection id, and a peer connection instance. When the generation ends, ignore late answers, candidates, or tracks from it. On reconnect, create a new generation, rejoin signaling, and negotiate again. Close the old peer connection and stop any orphan tracks. State is not cleanup; a closed signaling socket does not close media, and a closed peer connection does not stop the camera.

03

See it move

PLAY / PAUSE / STEP

Interactive diagram

Four state machines, one call

createOffersetLocalDescriptioncandidatesnominatenetwork changetimeoutrecovercleanup oldPROCESSnewconnection createdPROCESSoffer / answersignaling statePROCESSgatheringcandidates appearNETWORKcheckingICE pair checksPEERconnectedDTLS + media pathNETWORKdisconnectedtemporary lossPROCESSfailedchecks exhaustedSERVERrestart / resetnew generationSTORAGEclosedresources released
STEP 01 / 08new

New is not connected

A fresh peer connection has no candidates and no descriptions. Nothing is broken yet; no work has started.

Track each timeline separately. The first state that fails to advance is the layer to debug.

04

Behind the scenes

01

Four events fire in interleaved order

A log that prints every state change often looks like this: signaling have-local-offer, gathering gathering, ICE checking, signaling stable, connection connecting, ICE connected, gathering complete, connection connected. None of those sequences is guaranteed. Build your diagnostics around the state values, not an assumed ordering.

02

connectionState aggregates ICE and DTLS

connectionState is convenient for UI, but it hides which subsystem is stuck. If ICE is connected while connectionState is still connecting, DTLS is the place to look. If ICE is checking and no candidates exist, signaling or gathering is the place to look. Log both values together.

03

Restart is not reset

ICE restart keeps the same RTCPeerConnection and negotiates fresh transport credentials. A full reset creates a new peer connection and new signaling generation. Restart is cheaper and preserves data channels and tracks; reset is safer when the session itself is corrupt. Choose intentionally and clean up the previous generation either way.

05

Code walkthrough

COPY-READY

Record every timeline with timestamps and candidate counts. This log is the difference between 'it failed' and 'it failed before gathering produced a relay candidate.'

lib/peer-diagnostics.ts
export function logPeerState(pc: RTCPeerConnection) {
const started = performance.now();
const log = (event: string, detail?: unknown) =>
console.info(`[peer +${Math.round(performance.now() - started)}ms] ${event}`, detail ?? {});
pc.onsignalingstatechange = () =>
log("signaling", { state: pc.signalingState });
pc.onicegatheringstatechange = () =>
log("gathering", { state: pc.iceGatheringState });
pc.oniceconnectionstatechange = () =>
log("ice", { state: pc.iceConnectionState });
pc.onconnectionstatechange = () =>
log("connection", { state: pc.connectionState });
pc.onicecandidate = (event) =>
log(event.candidate ? "candidate" : "gathering-complete", {
candidate: event.candidate?.candidate,
});
pc.onicecandidateerror = (event) =>
log("candidate-error", {
code: event.errorCode,
url: event.url,
text: event.errorText,
});
}
06

Do and don't

Do

  • Log all four states with timestamps

    This turns a vague stuck call into a timeline you can compare between peers.

  • Treat generations explicitly

    A session id on offers, answers, and candidates lets old messages be ignored safely after a reset.

  • Stop tracks when closing

    Peer connection cleanup and camera cleanup are separate steps. Both are required.

  • Bound retries and explain failure

    Two ICE restarts or a rejoin is often enough. Then show a useful message and diagnostic details.

Don't

  • Don't infer connection from signaling

    Signaling stable means the offer/answer exchange settled, not that ICE found a path or DTLS succeeded.

  • Don't treat disconnected as closed

    A transient network change can recover. Closing immediately destroys a call that might have survived.

  • Don't reuse a failed peer blindly

    After a corrupt session or wrong-state error, stale descriptions make recovery worse. Close and create a new generation.

  • Don't leave timers behind

    A reconnect timer that survives teardown can resurrect a closed session or send an offer to a peer that left.

07

Practice

Hands-on lab

Find the first stuck state

The 1:1 lab shows signaling, peer, ICE, gathering, and data-channel state together. Use the peer-event log to identify the first transition that fails.

  1. 1Connect two tabs and record the order of gathering, checking, connected, and data-channel open.
  2. 2Drop all candidates, renegotiate, and identify which state never advances.
  3. 3Kill signaling while connected and note which states change and which do not.
  4. 4Reset and rejoin to create a new generation, then compare the logs.
Open the lifecycle lab

Solo? Loopback exercises the same four state machines without a second device. Filter all candidates to observe a checking state that never reaches connected.

08

Quick quiz

Knowledge check

0 of 4 answered

0%
  1. 01

    Signaling state is stable. What does that guarantee?

  2. 02

    ICE state is connected but connection state is still connecting. What layer is most likely pending?

  3. 03

    What is the key difference between restartIce and closing and creating a new peer?

  4. 04

    A temporary network change causes disconnected. What is a reasonable first response?

Pick one answer per question to see instant feedback.
09

Key takeaways

  1. Signaling, gathering, ICE, and connection are separate state machines; log them together.01
  2. Gathering complete does not mean a candidate pair will succeed.02
  3. Disconnected is a warning, failed is an exhausted path, and closed requires real cleanup.03
  4. Use ICE restart for path recovery and a new generation for corrupt sessions; bound both.04
10

Common errors and fixes

Terms in this lesson

Done with this lesson?

Mark it complete, then keep the thread.