How to make a peer-connection using webrtc

designing a simple video chat application 

To get a fair idea of what we are trying to build let us examine the figure

We want to build a connection between two clients so that they can share their video-streams.As it is clear from the figure that we need a mediator to act as a channel between the clients.Our purpose will be solved by a server which can connect users over the internet using their IP addresses.

 Let us begin with the server. The server just needs to handover whatever it receives from one user to the other.
We will establish an express server. Establishing an express server is quite easy.(First, install express package locally for your project.)

const express=require('express')
const app =express()

Just these two lines will establish your express app to handle API request like GET and POST.
Now,we add some configuration to our express app.

app.use(express.json())
app.use(express.urlencoded({
extended:true
}))
Note: The above lines are used to parse json or urlencoded payloads.

app.use('/',express.static(path.join(__dirname,'public')))
Note: Above line tells the application to serve static files in "public" directory on root path i.e.,'/'.


Now, we need our server to be proactive and thus we will use websockets. (First, install socket.io locally)

var http = require('http').createServer(app)
var io = require('socket.io')(http)
Note: The above lines are for configuring out http web address with websockets.

Following lines tell the server to just pass on the message to the other user over internet.
io.on('connection',(socket)=>{
console.log('User connected');
socket.on('message',(message)=>{
io.emit('forward',message)
})
})

Our server is completely ready and we can now begin to write the code for front-end i.e.,clients.
The public directory will look like this:
The HTML file will look like this:

<html>
<head>

<!--line 1--> <script src="socket.io/socket.io.js"></script>

<script src="./webrtc.js"></script>
</head>
<body>
<video id="localVideo" autoplay muted ></video> <video id="remoteVideo" autoplay ></video> <button id="callbtn" onclick="start(true)" >Connect</button>
</body>

</html>
Note: Refer to github link on the bottom of the page to see the complete code.
The purpose of line 1 is to include the socket client exposed by the server at /socket.io/socket.io.js

The heart of this project is how the connections are handled on the front end.This all is managed by the js file inside the public directory.

This is the most tricky part.

var socket=io() //this line creates an http server
function pageReady() {
localVideo = document.getElementById('localVideo'); //declare localVideo globally
remoteVideo = document.getElementById('remoteVideo'); //declare remoteVideo globally
callbtn=document.getElementById('callbtn');
socket.on('forward',(message)=>{
gotMessageFromServer(message)
})
}
Note: Refer to github link on the bottom of the page to see the complete code.We need to call the above fn each time the page loads (use - window.onload=pageReady())
The above fn calls gotMessageFromServer each time it receives a message.

function gotMessageFromServer(message) {
if(!peerConnection) //declare peerConnection globally
start(false);
var signal=message
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp))
.then( function() {
if(signal.sdp.type == 'offer') {
peerConnection.createAnswer()
.then(gotDescription)
.catch( errorHandler);
}
});
}
else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)) .catch(errorHandler); } } function errorHandler(error){ console.log(error) } function start(isCaller) { peerConnection = new RTCPeerConnection(); peerConnection.onicecandidate = gotIceCandidate; peerConnection.onaddstream = gotRemoteStream; if(localStream) peerConnection.addStream(localStream); if(isCaller) { peerConnection.createOffer().then(gotDescription).catch (createOfferError); } } function gotDescription(description) { peerConnection.setLocalDescription(description, function () { socket.emit('message',{'sdp': description}) }, function() {console.log('set description error')}); } function gotIceCandidate(event) { if(event.candidate != null) { socket.emit('message',{'ice': event.candidate}) } } function gotRemoteStream(event) { const mediastream=event.stream; remoteVideo.srcObject = mediastream; } function getUserMediaSuccess(stream) { localStream = stream; localVideo.srcObject = stream; } function myvideo(){ var constraints = { video: true, audio: true, }; if(navigator.mediaDevices.getUserMedia) { //condition 1 navigator.mediaDevices.getUserMedia(constraints) .then(getUserMediaSuccess) .catch(errorHandler); } else { alert('Your browser does not support getUserMedia API'); } }
Guys, please do not get overwhelmed on looking at code.We will discuss each and every function and its purpose in detail. 

Functional description:

pageReady()
 This function  is to be called everytime the page loads. It attaches DOM elements with variables named localvideo,remotevideo and callbtn. This helps in attaching the mediastream from local device and stream received from remote client with localvideo and remotevideo respectively.

myvideo()                                                       //Call this fn in pageready
 This function attaches the localstream to the DOM element.  The condition 1 in this function checks if the browser supports getUserMedia. navigator.mediaDevices.getUsermedia method asks for permissions to access the mediastreams mentioned by its parameter i.e., constraints. this method returns a promise. For more info about the method click here.

getUserMediaSuccess()
 This function is resolved after navigator.mediaDevices.getUsermedia. It receives the local media stream as its parameters and attaches the DOM element with the stream. As soon as it happens we see a video stream fetched from our device on to the webpage.

errorHandler()
 This function is just to log the errors that we may face.

start()
 This function can be called in two cases: when you press the call button or you receive a message from server. In this function peerconnection is assigned as an RTCpeerconnection. RTCpeerconnection represents a connection between local computer and remote peer. onicecandidate and onaddstream are event handlers. onicecadidate(i.e., goticecandidate) is called whenever we need to communicate back to the other client. onaddstream(i.e., gotremotestream ) is called whenever we receive the remote connection. For more details click here.
Then we load the localstream to the peerconnection object.
In case we are the caller (i.e.,we click the button), we need to create an SDP offer for the purpose if making connection. peerconnection.createoffer() does this for us.

gotIceCandidate()
  This function is called whenever we need to tell the peer about our ice candidate or the configuration/specifics of the peerconnection.Look at the picture to make it clear.
ICE Candidate serves as an interface between the streams.For more details click here.

gotRemoteStream()
 This function is called when we receive the mediastream of our peer.It attaches the stream to DOM element.

gotDescription()
  This function is resolved after the creation of descrition by peerconnection.createOffer() .It receives the description as its parameters and sends it to the peer.

gotMessageFromServer()
  This functions initialises peerConnection object in case it is not (when you are not the caller).After that it checks the type of message received.
  1. Description(sdp):  In this case we need to add the description sent by remote peer as an object interface between our communication  channel. Then after successfully doing that we need to create an answer stating our configuration.This phase involves mutual understanding of description and using it to communicate.
  2. ICE Candidate: In this case we set the ICE candidate /agent between the streams for interactive communication.
That's it. If you think something is missing in the code please refer to the link mentioned below.
Please comment if it was helpful.

Comments

Popular posts from this blog

Create and deploy a Discord Bot

Understanding and building Dialogflow agents to handle custom intents

Three ways to deploy Node.Js applications quickly