WebRTC Introduction - Interactive Connectivity Establishment
WIP
- WebRTC Introduction - Websockets
- WebRTC Introduction - Client Side Signalling
- WebRTC Introduction - Interactive Connectivity Establishment
Interactive Connectivity Establishment (ICE)
Peer Connection
Use STUN servers to exchange your public address:
// List of STUN server to exchange public IPs
const iceServers = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun.nextcloud.com:443" }
]
}
The RTCPeerConnection interface represents a WebRTC connection between the local computer and a remote peer. It provides methods to connect to a remote peer, maintain and monitor the connection, and close the connection once it's no longer needed.
rtcPeerConnection
is an interface that provides several functions that need to be defined. First we need to handle the local stream that we get from fetchLocalStream
in form of the userStream
object. This is done by addTrack()
:
// Room is ready event
socket.on('ready', function() {
if (creator) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
rtcPeerConnection.oniceccandidate = onIceCandidate()
rtcPeerConnection.ontrack = onTrack()
// get local audio stream from userStream object
rtcPeerConnection.addTrack(userStream.getTracks()[0], userstream)
// get local video stream from userStream object
rtcPeerConnection.addTrack(userStream.getTracks()[0], userstream)
}
})
The second one is onIceCandidate()
returns a candidate for the connection that needs to be published through our websocket:
// If ICE candidate is returned emit over ws
function onIceCandidate(event) {
if (event.candidate) {
socket.emit("candidate", event.candidate, chatRoom.value);
}
}
The third function we need to handle is onTrack()
that handles the media stream that is provided by the remote candidate:
// If ICE candidate starts streaming media emit over ws
function onTrack(event) {
peerVideo.srcObject = event.streams[0];
peerVideo.onloadedmetadata = function (e) {
peerVideo.play();
};
}
Negotiate Media (SDP)
The creator of the room now needs to use the established connection to send an offer to the remote candidate. Since the answering remote candidate will have to send the same media information I will export those functions and recycle them.
When the room is created and ready get public address of the remote candidate and collect your local media information
function rtcPeering() {
rtcPeerConnection = new RTCPeerConnection(iceServers)
rtcPeerConnection.oniceccandidate = onIceCandidate()
rtcPeerConnection.ontrack = onTrack()
// get audio stream from userStream object
rtcPeerConnection.addTrack(userStream.getTracks()[0], userstream)
// get video stream from userStream object
rtcPeerConnection.addTrack(userStream.getTracks()[0], userstream)
}
Offer
Send the offer to your remote candidate:
// Room is ready event
socket.on('ready', function() {
console.log('INFO :: Room ready.')
// Room creator generates offer for remote candidate to join
if (creator) {
rtcPeering()
// create offer
rtcPeerConnection
.createOffer()
.then((offer) => {
rtcPeerConnection.setLocalDescription(offer);
socket.emit("offer", offer, roomName);
})
.catch((error) => {
console.log(error);
});
}
console.log('INFO :: Offer send.')
})
Answer
When the remote candidate receives the offer it will provide the following answer containing the same information but it's local media:
// Triggered on receiving an offer from the person who created the room.
socket.on("offer", function (offer) {
// If not creator send answer to offer
if (!creator) {
rtcPeering()
rtcPeerConnection.setRemoteDescription(offer);
rtcPeerConnection
.createAnswer()
.then((answer) => {
rtcPeerConnection.setLocalDescription(answer);
socket.emit("answer", answer, roomName);
})
.catch((error) => {
console.log(error);
});
}
console.log('INFO :: Answer send.')
});