What are ICE Candidates?
After exchanging SDP (Session Description Protocol) messages between peers, peers should connect to each other to transfer real-time data. However, connecting between peers is a bit complex because each peer is behind a NAT/Firewall in their local network, and they don’t know how to find right loads to the destination to exchange stream data.
To solve this issue, WebRTC uses ICE (Interactive Connectivity Establishment) protocol, and peers can negotiate the actual connection between them by exchanging ICE candidates.
ICE is utilized to find all possible methods for establishing peer-to-peer connections through NAT. This is achieved by combining the functionalities of STUN (Session Traversal Utilities for NAT) servers and TURN (Traversal Using Relays around NAT) servers.
By adding an ICE candidate received from the remote peer via its signaling server, you can effectively manage and modify the media stream data, facilitating a successful peer-to-peer communication even across different types of NAT configurations.
Add an ICE Candidate
You can set an RTCPeerConnection
s remote description, which is a standard format for describing multimedia communication sessions for a peer-to-peer connection by calling RTCPeerConnection.addIceCandidate() method like the code below:
await peerConnection.addIceCandidate(candidate)
This method adds a new ICE candidate to the connection. You can obtain the candidate from the remote peer via the signaling channel. The candidate contains the network connection information such as the IP address and port of the remote peer like the example below:
a=candidate:7344997325 1 udp 2043216322 192.168.0.42 44323 typ host
The RTCPeerConnection.addIceCandidate()
receives a candidate
parameter, which describes the properties of the candidate from the SDP attribute. Now, you should get an ICE candidate object from the remote peer.
Exchange ICE Candidates
You may already be noticed, if you want to add an ICE candidate with the addIceCandidate
method, you should have a candidate
instance. You can get a candidate instance by adding an event listener, which listens to ICE candidates from a peer connection:
localPeerConnection.addEventListener('icecandidate', e => onIceCandidate(peerConnection, e));
The icecandidate event is sent to an peer connection when the local peer set the SDP with RTCPeerConnection.setLocalDescription() method. Then the candidate should be sent to the remote peer over the signaling server, so the remote peer can add it to its set of remote candidates:
const candidate = getCandidateFromSignalingServer();
await remotePeerConnection.addIceCandidate(candidate);
The local peer also should receive ICE candidates from the signaling server and add the candidates to the peer connection.
const candidate = getCandidateFromSignalingServer();
await localPeerConnection.addIceCandidate(candidate);
Now you understand how to exchange ICE candidates between the local peer and the remote peer.
Local and Remote Peer Connection
Now let’s combine all the concepts above and establish a peer connection by exchanging ICE candidates between a local peer (p1
) and a remote peer (p2
):
let pc1, pc2;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
function getName(pc) {
return (pc === pc1) ? 'pc1' : 'pc2';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
async function call() {
pc1 = new RTCPeerConnection();
pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
pc2 = new RTCPeerConnection();
pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
}
async function onIceCandidate(pc, event) {
try {
await (getOtherPc(pc).addIceCandidate(event.candidate));
console.log(`${getName(pc)} addIceCandidate success`);
} catch (e) {
onCatch(pc, e);
}
console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
function onCatch(error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `<p>Something went wrong: ${error.name}</p>`;
}
Typically, exchanging ICE candidates should be done by the signaling server, but in this tutorial, we don’t use a signaling server and set up a connection between two RTCPeerConnection
objects (known as peers) on the same page to help you better grasp how ICE candidates are exchanged.
You’ve learned the essential concepts to establish a peer connection. Now, let’s build a demo project that simulates a peer connection.