const pc1 = new RTCPeerConnection(null);
const pc2 = new RTCPeerConnection(null);
async function call(){
const offer = await pc1.createOffer();
pc1.setLocalDescription(offer);
pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
pc2.setLocalDescription(answer);
pc1.setRemoteDescription(pc2.localDescription);
}
async function showVideo(){
const config={audio:true,video:true};
stream = await navigator.mediaDevices.getUserMedia(config);
pc1.addStream(stream);
}
This is simplified version of my code.
Now in this code I'm not adding icecandidate to the peer neither listening to onicecandidate. But if I invoke call() twice my connection gets established.
I used event handler for iceconnectionstate change and found that when I invoke call for first time it iceconnection state is in checking condition and when I invoke it for second time it gets called and state became completed.
So I'm wondering how does even without adding icecandidate to another peer how checking gets initiated and gets connected for the second time?
The RTCIceCandidate interface—part of the WebRTC API—represents a candidate Interactive Connectivity Establishment (ICE) configuration which may be used to establish an RTCPeerConnection . An ICE candidate describes the protocols and routing needed for WebRTC to be able to communicate with a remote device.
It is a standard method of NAT traversal used in WebRTC. It is defined in IETF RFC 5245. ICE deals with the process of connecting media through NATs by conducting connectivity checks. ICE collects all available candidates (local IP addresses, reflexive addresses – STUN ones and relayed addresses – TURN ones).
Four reasons: A bug in your code, a behavior of Chrome, no firewall, and something about how trickle ICE works.
First things first: The following sets pc1.setRemoteDescription(null)
:
pc2.setLocalDescription(answer);
pc1.setRemoteDescription(pc2.localDescription); // pc2.localDescription == null here
...because setLocalDescription
is an asynchronous method that doesn't complete immediately.
Now pc2.localDescription
does eventually get set, so the second time you invoke call()
it's there, and negotiation works.
To fix this you must wait on the promise using await
or then
:
await pc2.setLocalDescription(answer);
pc1.setRemoteDescription(pc2.localDescription); // pc2.localDescription is set!
The browser can communicate with other machines on the same LAN, or itself, using "host" candidates (your machine's IP). No ICE servers needed to discover those.
The signaling (trickling) of individual ice candidates using onicecandidate
, is an optimization meant to speed up negotiation. Once setLocalDescription
succeeds, the browser's internal ICE Agent starts, inserting ICE candidates, as they're discovered, into the localDescription
itself. Wait a few seconds to negotiate, and trickling isn't necessary at all: all ICE candidates will be in the offer and answer transmitted.
I suspect a race where the second time you invoke call()
Chrome's ICE agent remembers the host candidates it has collected from last time, and inserts them into the offer and localDescription
immediately, before setLocalDescription
's success callback has run to completion. This might be a bug, or it may be how the spec says it should work. In any case, the behavior seems to differ depending on browser atm, so I wouldn't rely on it today.
Call!
button once, then once more.0 candidates
(output twice); both times; no connection.3 candidates
the second time, with candidates.
// await wait(2000);
4
or 8 candidates
after 2 seconds.The actual number of candidates may vary on your system, but the behavior shouldn't.
When I run this in Firefox, It doesn't connect either time, unless I modify it to wait or trickle candidates).
Browser update: Both browsers have bugs: Firefox is too restrictive and Chrome is too lenient in recovering from ICE failure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With