In WebRTC, how is RTCPeerConnection's connectionState derived from its ICE and DTLS transports, and how should an application react to 'disconnected', 'failed' and 'closed'?
answer
- four state attributes, one aggregate
- ICE and DTLS combined
- disconnected may heal itself
- failed: whose failure?
- closed fires no event
basics
~20 sconnectionState aggregates ICE and DTLS: 'failed' if ICE or any DTLS transport failed, 'disconnected' if ICE is, 'connected' when both are up. Wait out 'disconnected', restart ICE on ICE failure, treat DTLS failure as a fault; 'closed' follows only your own close().
solid answer
~50 sAn `RTCPeerConnection` reports four states: `signalingState` for the offer/answer exchange, `iceGatheringState`, `iceConnectionState` for ICE alone, and `connectionState`, the aggregate of ICE and every `RTCDtlsTransport`. Its values are checked in order: `closed` when the connection is closed; `failed` if ICE failed **or any DTLS transport failed**; `disconnected` if ICE is disconnected; `new`; `connected` when ICE is connected and every DTLS transport is connected or closed; otherwise `connecting`. `disconnected` is transient and may heal, so show 'reconnecting', watch traffic for a few seconds, then restart ICE. On `failed`, check `iceConnectionState`: an ICE failure is terminal until `restartIce()`, which leaves DTLS, data channels and tracks open; a DTLS failure with ICE still connected is a security or configuration fault no restart fixes. `closed` appears only after your own `close()`, which fires no event, so the far end's hang-up must arrive over signalling.
code
pseudocode · 20 lineson pc.connectionstatechange:
switch pc.connectionState:
"connecting": show("connecting")
"connected": cancel(reconnectTimer); restarts = 0; show("live")
"disconnected": show("reconnecting")
reconnectTimer = after(5 s):
if no new bytes in pc.getStats(): pc.restartIce()
"failed":
if pc.iceConnectionState == "failed" and restarts < 3:
restarts = restarts + 1
pc.restartIce() // next offer carries new ICE credentials
else:
report("dtls or repeated ice failure"); hangUp()
// "closed" never arrives here: close() fires no event
on signalling message "hangup":
hangUp()
hangUp():
pc.close(); stopLocalTracks(); cancel(reconnectTimer)go deeper
Recall the connectionState values from new through connected to failed or closed, and that 'disconnected' can recover on its own.
Explain how connectionState combines ICE and DTLS, which values exist only on iceConnectionState, and that close() changes state without an event.
Demonstrate the reaction policy: wait out 'disconnected' with a traffic check, restart ICE only for ICE failure, treat DTLS failure as a fault, and signal hang-ups explicitly.
Set the reliability budget for calls: how long a user tolerates reconnecting, how many restarts to allow, and which failure classes page an operator versus degrade quietly.
## Four state attributes, one for the call screen The W3C WebRTC 1.0 API exposes four state attributes on `RTCPeerConnection`, each with its own change event: | Attribute | Tracks | Values | |---|---|---| | `signalingState` | the offer/answer exchange | `stable`, `have-local-offer`, `have-remote-offer`, `have-local-pranswer`, `have-remote-pranswer`, `closed` | | `iceGatheringState` | candidate gathering | `new`, `gathering`, `complete` | | `iceConnectionState` | ICE connectivity only | `new`, `checking`, `connected`, `completed`, `disconnected`, `failed`, `closed` | | `connectionState` | ICE **and** DTLS together | `new`, `connecting`, `connected`, `disconnected`, `failed`, `closed` | Media cannot flow until ICE has a working pair **and** DTLS has finished its handshake and verified the far end's fingerprint. Only `connectionState` captures both, which makes it the attribute a call screen should follow. Note that `checking` and `completed` belong to `iceConnectionState` alone. ## How connectionState is derived The specification defines `connectionState` as the first rule that applies, considering the transports used by the current transceivers and the SCTP transport: 1. **`closed`** — the ICE connection state is `closed`, which happens only when this connection has been closed. 2. **`failed`** — the ICE connection state is `failed`, **or any `RTCDtlsTransport` is `failed`**. 3. **`disconnected`** — the ICE connection state is `disconnected`. 4. **`new`** — ICE is `new` and every DTLS transport is `new` or `closed`, or there are no transports. 5. **`connected`** — ICE is `connected` and every DTLS transport is `connected` or `closed`. 6. **`connecting`** — anything else, such as ICE still checking or DTLS still negotiating. Two consequences follow. `disconnected` only ever comes from ICE. `failed` has two possible causes, and they need different responses. ## Reacting to "disconnected" The specification calls ICE `disconnected` **transient**: it "may trigger intermittently (and resolve itself without action) on a flaky network", and how it is detected is implementation-dependent — a lost network interface or unanswered STUN requests, for example. A sound reaction: - show a "reconnecting" indicator instead of ending the call; - wait a few seconds; the W3C specification suggests checking with `getStats` whether bytes sent or received are still increasing; - if traffic has stopped, restart ICE rather than waiting for `failed`. ## Reacting to "failed" First ask which layer failed, by reading `iceConnectionState`: - **ICE failed.** Every pair has failed its checks or lost consent. The specification calls this "a terminal state until ICE is restarted", and the W3C specification recommends an ICE restart when `iceConnectionState` becomes `failed`. Call `restartIce()`; the next offer carries new ICE credentials and must cross the signalling channel. The mechanics of the restart belong to ICE, but the state model matters here: entering `failed` does **not** close the DTLS transports, the SCTP association or its data channels, and does not mute tracks, so a successful restart resumes the call in place. - **DTLS failed while ICE is connected.** The transport failed through an error, such as receiving an error alert or failing to validate the remote fingerprint; the `RTCDtlsTransport` fires an `error` event whose detail is `dtls-failure` or `fingerprint-failure`. A new network path does not cure this. Treat it as a security or configuration fault: log it, alert on it, and set up a fresh connection only if the cause is understood. Cap restart attempts and give the user a clear failure after a bounded time; how many attempts and how long is an application choice, not a specification rule. ## Reacting to "closed" `closed` is reached only when this side calls `close()`. The algorithm stops every transceiver, sets every data channel to `closed` abruptly, tears down the SCTP association with an ABORT, closes the DTLS transports and destroys the ICE agent, releasing resources such as TURN permissions. It sets `connectionState` and `iceConnectionState` to `closed` **without firing any event**, so code that cleans up only inside a `connectionstatechange` handler never runs. The far end calling `close()` never shows up locally as `closed`. Its ICE agent stops answering, so the local side typically drifts to `disconnected` and later `failed`. Applications therefore send an explicit hang-up message over signalling and close on receiving it. ## Production checklist - Drive the UI from `connectionState`; drive restart decisions from `iceConnectionState`. - Distinguish ICE failure from DTLS failure in logs and metrics; they have different owners. - Release cameras, microphones and timers in the code that calls `close()`, not in an event handler. - Measure time spent in `disconnected`, restarts attempted and restarts that reached `connected` again.
- Why can WebRTC's iceConnectionState read 'connected' while connectionState reads 'failed'?Because `connectionState` also folds in every `RTCDtlsTransport`, and any DTLS transport in `failed` makes the aggregate `failed` regardless of ICE. ICE found a working path, but the DTLS handshake failed, for example on an error alert or a remote fingerprint that did not validate. An ICE restart will not help; the cause is security or configuration.
- In WebRTC, does an ICE 'failed' state close the connection's data channels?No. The specification says entering `failed` does not close DTLS transports, SCTP associations or the data channels over them, and does not mute tracks, precisely because an ICE restart may resume connectivity. The channels simply stop delivering until a new pair is selected, then carry on.
- Why should a WebRTC application not rely on connectionstatechange to clean up after close()?`close()` sets `connectionState` and `iceConnectionState` to `closed` without firing any event, so a cleanup handler keyed on the change never runs. Release cameras, microphones, timers and signalling subscriptions in the same code path that calls `close()`.
saying these in an interview costs you the question
- 'disconnected' means the call is over and the peer connection should be closed
- When the far peer hangs up, the local connectionState becomes 'closed'
- connectionState and iceConnectionState are two names for the same value
- An ICE 'failed' state closes the data channels, so they must be recreated
- Every 'failed' connection is fixed by restarting ICE
- close() fires connectionstatechange, so cleanup belongs in that handler