Best Practices for ICE Candidate Trickle vs Bulk Gathering
Trickle ICE transmits candidates incrementally via onicecandidate. This approach reduces Time-To-First-Frame by 200–800ms. Bulk gathering waits for iceGatheringState to reach 'complete' before transmitting a single SDP payload. While bulk simplifies signaling logic, it introduces unacceptable latency for real-time media pipelines.
Your architecture should prioritize connection speed over payload simplicity. Review your ICE Candidate Gathering & Filtering strategy before committing to a gathering mode. Modern browsers default to trickle. Forcing bulk requires explicit state monitoring and risks NAT binding expiration.
Signaling Implementation & State Synchronization
Trickle requires an ordered, idempotent message queue over WebSocket or DataChannel. Each candidate must map to its exact sdpMid and sdpMLineIndex. Design your WebRTC Protocol Stack & Signaling Servers to preserve candidate order and handle out-of-band delivery gracefully.
Track iceConnectionState and iceGatheringState independently via a lightweight state machine. Never block the signaling thread awaiting bulk completion. Implement a strict 3-second fallback timeout to switch to bulk if trickle stalls due to restrictive NAT traversal.
Minimal Trickle Implementation with Bulk Fallback
Use this exact configuration to handle incremental candidates while preventing indefinite stalls.
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
pc.onicecandidate = (e) => {
if (e.candidate) {
signaling.send({ type: 'trickle', candidate: e.candidate });
}
};
// Fallback to bulk if trickle stalls
let trickleTimeout = setTimeout(() => {
if (pc.iceGatheringState !== 'complete') {
console.warn('Trickle stalled, switching to bulk SDP exchange');
signaling.send({ type: 'offer', sdp: pc.localDescription.sdp });
}
}, 3000);
pc.onicegatheringstatechange = () => {
if (pc.iceGatheringState === 'complete') {
clearTimeout(trickleTimeout);
signaling.send({ type: 'bulk-sdp', sdp: pc.localDescription.sdp });
}
};
Reproduction Steps & Debugging Log Patterns
- Initialize
RTCPeerConnectionwithiceServerspointing to a high-latency TURN relay. - Intercept
onicecandidateand artificially delay candidate emission by 2s. - Observe
iceGatheringStatetransitions in your console.
Expected output shows new -> gathering -> complete. If trickle fails, you will see checking -> disconnected in the connection state. Poll pc.getStats() for candidatePair transitions. A stalled session logs iceState: 'failed' with mismatched localCandidateType: 'host' and remoteCandidateType: 'relayed'. Use chrome://webrtc-internals/ to trace nomination timing and verify iceTransportPolicy constraints.
Common Implementation Mistakes
- Assuming
onicecandidatefires synchronously with SDP generation. This causes race conditions in signaling. - Failing to handle
nullcandidates inonicecandidate. This incorrectly signals end-of-candidates to the peer. - Using bulk gathering on mobile networks. NAT binding refreshes expire candidates before signaling completes.
- Ignoring
iceTransportPolicy: 'relay'constraints. This suppresses host candidates and artificially extends gathering time.
Frequently Asked Questions
When should I force bulk ICE gathering over trickle? Only in constrained environments where signaling channels cannot handle asynchronous streams. Legacy SIP gateways or strict firewalls that drop WebSocket keep-alives may require it. Modern web and mobile apps must use trickle for acceptable latency.
How do I detect if trickle ICE has failed?
Monitor iceConnectionState for transitions to 'failed' or 'disconnected' while iceGatheringState remains 'gathering'. Implement a heartbeat or timeout on the signaling channel. Trigger a fallback to bulk SDP or call pc.restartIce() if no candidates arrive within 2–3 seconds.
Does trickle ICE increase signaling server load? Marginally. Each candidate generates a separate WebSocket frame. Modern servers efficiently handle thousands of concurrent connections. The latency reduction of 300–600ms directly improves user experience in video and chat applications.