LiveKit WebRTC ICE stuck in checking on iPhone Safari – Room.connect() timeout

Hello LiveKit team,

We are developing LumirLive, a web-based live video streaming platform using LiveKit Cloud and LiveKit JavaScript SDK 2.22.2.

We are experiencing repeated connection failures when starting a live broadcast from Safari on an iPhone.

Observed behavior:

  • LiveKit signaling is accepted successfully.
  • SDP offer/answer negotiation completes.
  • The publisher PeerConnection remains in connecting.
  • ICE remains in checking.
  • ICE gathering remains in gathering.
  • DTLS remains in new.
  • Room.connect() times out after 30 seconds.
  • No camera or microphone tracks are published.

Comparison:

A previous session (47) connected successfully in approximately 1.14 seconds. Subsequent sessions (48–51) failed to establish the media connection.

For session 51, our diagnostics recorded a candidate pair in waiting state, not nominated, with no connectivity checks on that sampled pair. However, we cannot determine whether TURN relay candidates were available or whether other candidate pairs were tested.

Environment:

  • iPhone Safari
  • LiveKit JavaScript SDK 2.22.2
  • LiveKit Cloud
  • DEVELOPMENT environment

Questions:

  1. What could cause ICE to remain in checking after successful signaling and SDP negotiation?
  2. Could this be related to TURN relay availability, Safari ICE behavior, or SDK connection handling?
  3. Are there recommended diagnostics or configuration checks for this situation?
  4. Is there a way for LiveKit support to compare the ICE connectivity behavior of a successful session and a failed session without exposing IP addresses or credentials?

We can provide sanitized timestamps and additional diagnostic information privately if needed.

Thank you for your assistance.

Hi Paola,

Disclosure: I work at Ant Media (a different WebRTC media server), so I can’t speak to LiveKit Cloud specifics, but the symptoms you describe are general ICE/network behavior that may help.

ICE stuck in checking with DTLS in new after successful signaling and SDP means the signaling path works but no candidate pair has carried media yet. The fact that session 47 connected and 48–51 didn’t suggests it depends on the network path, not your code. Things I’d check:

  1. Confirm TURN relay candidates are being gathered. If the gathered candidates are only host/srflx and none are relay, a restrictive network (carrier NAT, captive Wi-Fi, firewall blocking UDP) can leave you stuck exactly like this. Log the iceServers your client actually receives.
  2. Test by network. Try the same iPhone on Wi-Fi vs cellular. If it fails on one and works on the other, it’s the network path, not the SDK.
  3. Check iOS-side interference. VPNs, iCloud Private Relay, Low Data Mode and content blockers can all affect ICE.
  4. Force relay as a test. If I recall correctly, LiveKit’s JS SDK lets you pass rtcConfig with iceTransportPolicy: 'relay' in the connect options (please verify in their docs). If the connection then succeeds, TURN works and the direct paths are being blocked. If it still fails, the problem is TURN reachability.
  5. Use TURN over TCP/TLS on 443. This is the usual fix for networks that block UDP.
  6. Inspect on the device. Safari Web Inspector (Mac connected to the iPhone) shows the candidate and connection states, and the Trickle ICE test page shows what a given TURN server returns.

For reference, we wrote up how we handle UDP-blocked and restricted networks with a TURN server on 443/TCP. The principle applies to any WebRTC stack:

Hi Paola, on the LiveKit Cloud side, can you let us know how your session numbers (47 - 51) correspond to room session IDs (which start with RM_ ? We can see if there’s some clues in the server logs as a first step.