Nowadays, the availability of broadband internet connection is not a big deal. Thus, it’s a pretty natural thing that the video chatting apps become more and more common, replacing the communication using text messaging. Whether your intention is chatting with your relatives or discuss some business ideas with colleagues, video chats provide you with the most natural way of interpersonal communication.

The problem is when you want to eliminate the number of applications used and use your web browser as a video chat tool, it may require the installation of additional plugins. It is not always convenient for a user and developer, since reaching the cross-browser support may be a serious issue. As an answer to this challenge, WebRTC was created.

The Main Features of WebRTC

Probably, the main feature that attracts attention to WebRTC is the possibility to use peer-to-peer communication between the browsers without using any frameworks, plugins, or additional applications. It supports popular browsers such as Firefox, Opera, and Chrome and can be used on the main mobile platforms such as Android and iOS.

You can use third-party frameworks that can simplify the development process, but WebRTC doesn’t require any development tools. It’s an API that allows you to obtain audio, video or other data stream, gather network information, report errors, and initiate or closed sessions, etc.

The functionality of WebRTC is based on three pillars.

getUserMedia (MediaStream)

The MediaStream API allows you to get access to the streams of media such as audio and video data from your webcam. The getUserMedia() method gets three parameters:

• constraints define whether you want to use audio, video, or both of them. As well, it allows you to define such details as the video resolution, for example,

• a success callback that is passed a MediaStream

• an error callback that is called in case of errors

Here’s a canonical example that can help you understand how everything works:

var constraints = {video: true}; function successCallback(localMediaStream) { window.stream = localMediaStream; // stream available to console var video = document.querySelector("video"); video.src = window.URL.createObjectURL(localMediaStream); video.play(); } function errorCallback(error){ console.log("navigator.getUserMedia error: ", error); } navigator.getUserMedia(constraints, successCallback, errorCallback); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var constraints = { video : true } ; function successCallback ( localMediaStream ) { window . stream = localMediaStream ; // stream available to console var video = document . querySelector ( "video" ) ; video . src = window . URL . createObjectURL ( localMediaStream ) ; video . play ( ) ; } function errorCallback ( error ) { console . log ( "navigator.getUserMedia error: " , error ) ; } navigator . getUserMedia ( constraints , successCallback , errorCallback ) ;

In this case, we’re interested only in the video stream from a webcam (check how the constraints object looks like). If you add the <video autoplay></video> element to your page, successCallback will set the video stream as the source for it. In the case of error, you’ll see the error message defined within errorCallback in your console.

If you want to check this example, simply add the JavaScript code from above between the <script></script> tags. But you should notice that this code can be used only on a server such as Apache for example.

RTCPeerConnection

The main aim of RTCPeerConnection is to provide you with the communication of data between the peers. Its responsibilities also included codec handling, codec handling, security issues, etc.

RTCDataChannel

Besides exchanging audio and video streams between the peers, WebRTC apps support the transmission of other types of data. You can create a real-time WebRTC text chat with file transfer support, for example. For exchanging the data such as text or files, RTCDataChannel is used. It uses the same API as WebSockets, so if you’re already familiar with it, it won’t be a hard task to learn new WebRTC tricks. Built-in DTLS protocol will guarantee the safety of transmitted data.

Signaling

Another important part of WebRTC functionality is signaling. Using WebRTC, you can create an app that allows transmitting data between the several browsers. But there’s also a need for a mechanism that will coordinate the communication process. This exactly what signaling is. It’s used to exchange the session messages, info about the network configuration, and information about the media capabilities of browsers such as used codecs. To make all this job, a signaling server is required. The WebRTC standard doesn’t specify which server technology you should use. So, you can choose one or the other according to your preferences.

For exchanging the network information and connecting peers with each other via the UDP protocol, WebRTC apps use the ICE Framework. A caller creates a new RTCPeerConnection object with an onicecandidate handler. After a network candidate is available, the handler is called. Then, using WebSocket or any other mechanism, the caller can send serialized candidate data to a callee. After getting the candidate message, the callee uses addIceCandidate for adding the candidate to the remote peer description.

For exchanging the session descriptions, WebRTC applications use Session Description Protocol (SDP). This protocol doesn’t deliver any media from one peer to another. Its primary purpose is to describe the streaming media initialization parameters that are used during “negotiation” between the caller and callee. The main aim of such a process is to make sure that media type, format, and other properties are compatible. Here’s an example of how a serialized SDP object can look like:

v=0

o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5

s=SDP Seminar

i=A Seminar on the session description protocol

u=http://www.example.com/seminars/sdp.pdf

e=j.doe@example.com (Jane Doe)

c=IN IP4 224.2.17.12/127

t=2873397496 2873404696

a=recvonly

m=audio 49170 RTP/AVP 0

m=video 51372 RTP/AVP 99

a=rtpmap:99 h263-1998/90000

Let’s take a look at the meanings of some parameters:

• v= protocol version number

• o= originator and session identifier: username, id, version number, network address

• s= session name

• i= title or short information about the session

• m= media name and transport address

• c= connection information

• etc.

The process of exchanging local and remote media information takes place in several stages:

1. The caller uses the createOffer() method that creates an SDP offer which includes information about the local session such as media capabilities

2. setLocalDescription() allows setting the local description using the data gathered in the previous step

3. This description is sent to the callee using the signaling channel

4. Using the setRemoteDescription() method the callee sets the received data as the remote description

5. The callee uses the createAnswer() method to create and then set his own local session description.

6. After that, the callee sets the received description as the remote description and send its local description back to the caller

7. When the caller gets the sent data from the callee, he uses the setRemoteDescription method to set it as the remote description

May look a little bit messy, but everything will become clearer after we take a look at the practical example.

Creating a Video Chatting App

Let’s proceed with the coding process. As it been said, WebRTC itself doesn’t require any third-party frameworks. But since we need the signaling server we’ll use Node.js for our example. If you don’t have it installed on your system, you can check this download page. Once again, WebRTC standards do not require using Node.js or any other server technology specifically. So, you can use your favorite one.

Create a new folder named public. Within this folder, create a new HTML file index.html:

<!DOCTYPE html> <html> <head> <script src="webrtc.js"></script> <title>WebRTC Video Chatting App</title> </head> <body> <video id="remoteVideo" autoplay></video> <video id="localVideo" autoplay muted></video> <input id="videoCallButton" type="button" disabled value="Video Call"/> <input id="endCallButton" type="button" disabled value="End Call"/> <script>window.addEventListener("load", pageReady);</script> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < ! DOCTYPE html > < html > < head > <script src = "webrtc.js" > </script> < title > WebRTC Video Chatting App < / title > < / head > < body > < video id = "remoteVideo" autoplay > < / video > < video id = "localVideo" autoplay muted > < / video > < input id = "videoCallButton" type = "button" disabled value = "Video Call" / > < input id = "endCallButton" type = "button" disabled value = "End Call" / > <script> window . addEventListener ( "load" , pageReady ) ; </script> < / body > < / html >

This code will add to the page two <video></video> elements. The first one will display the media stream from your webcam. The second one is for the media stream from the person you will call. Two buttons allow to initiate and end the call. The addEventListener method will call the pageReady function after the page is loaded.

We’ve already added the webrtc.js file to the page. It will contain JavaScript code that implements previously explained WebRTC functionality. Let’s create some useful variables first:

var config = { wssHost: 'wss://localhost:3434'}; var constraints = { "audio": true, "video": true }; var localVideoElem = null, remoteVideoElem = null, localVideoStream = null, videoCallButton = null, endCallButton = null; var peerConn = null, wsc = new WebSocket(config.wssHost), peerConnCfg = {'iceServers': [ {'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'} ] }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var config = { wssHost : 'wss://localhost:3434' } ; var constraints = { "audio" : true , "video" : true } ; var localVideoElem = null , remoteVideoElem = null , localVideoStream = null , videoCallButton = null , endCallButton = null ; var peerConn = null , wsc = new WebSocket ( config . wssHost ) , peerConnCfg = { 'iceServers' : [ { 'url' : 'stun:stun.services.mozilla.com' } , { 'url' : 'stun:stun.l.google.com:19302' } ] } ;

Pay attention to the first code line. It’ll be used when we’ll create a new WebSocket connection. If you plan to test the application on a single device you can live it as is. But if you want to run a server and then connect to it from other devices, this value must match the IP address of the device on which the server is running. For example var config = { wssHost: ‘wss://192.168.100.10:3434’};. The peerConnCfg variable defines the parameters that will be used to initiate new RTCPeerConnection. The localVideoElem, remoteVideoElem, videoCallButton and endCallButton will be used to get access to the HTML elements of the page.

Now we can define the pageReady() function that is assigned to the load event:

function pageReady() { videoCallButton = document.getElementById("videoCallButton"); endCallButton = document.getElementById("endCallButton"); localVideo = document.getElementById('localVideo'); remoteVideo = document.getElementById('remoteVideo'); videoCallButton.removeAttribute("disabled"); videoCallButton.addEventListener("click", initiateCall); endCallButton.addEventListener("click", function (evt) { wsc.send(JSON.stringify({"closeConnection": true })); }); }; 1 2 3 4 5 6 7 8 9 10 11 function pageReady ( ) { videoCallButton = document . getElementById ( "videoCallButton" ) ; endCallButton = document . getElementById ( "endCallButton" ) ; localVideo = document . getElementById ( 'localVideo' ) ; remoteVideo = document . getElementById ( 'remoteVideo' ) ; videoCallButton . removeAttribute ( "disabled" ) ; videoCallButton . addEventListener ( "click" , initiateCall ) ; endCallButton . addEventListener ( "click" , function ( evt ) { wsc . send ( JSON . stringify ( { "closeConnection" : true } ) ) ; } ) ; } ;

Using getElementById(), we linked our variables to the page elements to have an opportunity to manipulate them. Then we enabled the Video Call button and attached a click event listener to it. So, after a user clicks this button the initiateCall() function will be called. The same way the closeConnection() function will be called after a user clicks the End Call button.

The prepareCall method creates a new RTCPeerConnection instance and assigns the required event listeners:

function prepareCall() { peerConn = new RTCPeerConnection(peerConnCfg); // send any ice candidates to the other peer peerConn.onicecandidate = onIceCandidateHandler; // once remote stream arrives, show it in the remote video element peerConn.onaddstream = onAddStreamHandler; }; function onIceCandidateHandler(evt) { if (!evt || !evt.candidate) return; wsc.send(JSON.stringify({"candidate": evt.candidate })); }; function onAddStreamHandler(evt) { videoCallButton.setAttribute("disabled", true); endCallButton.removeAttribute("disabled"); // set remote video stream as source for remote video HTML5 element remoteVideo.src = URL.createObjectURL(evt.stream); }; After a user clicks the Video Call button it calls the initiateCall() function: function initiateCall() { prepareCall(); navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { /* use the stream */ localVideoStream = stream; localVideo.src = URL.createObjectURL(localVideoStream); peerConn.addStream(localVideoStream); createAndSendOffer(); }).catch(function(err) { /* handle the error */ console.log(error); }); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function prepareCall ( ) { peerConn = new RTCPeerConnection ( peerConnCfg ) ; // send any ice candidates to the other peer peerConn . onicecandidate = onIceCandidateHandler ; // once remote stream arrives, show it in the remote video element peerConn . onaddstream = onAddStreamHandler ; } ; function onIceCandidateHandler ( evt ) { if ( ! evt || ! evt . candidate ) return ; wsc . send ( JSON . stringify ( { "candidate" : evt . candidate } ) ) ; } ; function onAddStreamHandler ( evt ) { videoCallButton . setAttribute ( "disabled" , true ) ; endCallButton . removeAttribute ( "disabled" ) ; // set remote video stream as source for remote video HTML5 element remoteVideo . src = URL . createObjectURL ( evt . stream ) ; } ; After a user clicks the Video Call button it calls the initiateCall ( ) function : function initiateCall ( ) { prepareCall ( ) ; navigator . mediaDevices . getUserMedia ( constraints ) . then ( function ( stream ) { /* use the stream */ localVideoStream = stream ; localVideo . src = URL . createObjectURL ( localVideoStream ) ; peerConn . addStream ( localVideoStream ) ; createAndSendOffer ( ) ; } ) . catch ( function ( err ) { /* handle the error */ console . log ( error ) ; } ) ; } ;

It uses the previously defined prepareCall() function. Then we need to get the media stream from the web cam and set it as the source for the <video id=”localVideo”></video> element. Finally, we create and send the connection offer to another peer using the createAndSendOffer() method that will be discussed later.

After a callee received an offer, he should get the media stream from his webcam, display it within the <video> element, and then create and send the answer.

Here’s how the answerCall() function works:

function answerCall() { prepareCall(); // get the local stream, show it in the local video element and send it navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { /* use the stream */ localVideoStream = stream; localVideo.src = URL.createObjectURL(localVideoStream); peerConn.addStream(localVideoStream); createAndSendAnswer(); }).catch(function(err) { /* handle the error */ console.log(error); }); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 function answerCall ( ) { prepareCall ( ) ; // get the local stream, show it in the local video element and send it navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { /* use the stream */ localVideoStream = stream ; localVideo . src = URL . createObjectURL ( localVideoStream ) ; peerConn . addStream ( localVideoStream ) ; createAndSendAnswer ( ) ; } ) . catch ( function ( err ) { /* handle the error */ console . log ( error ) ; } ) ; } ;

Now we have to define how WebSocket message exchange between peers will work:

wsc.onmessage = function (evt) { var signal = null; if (!peerConn) answerCall(); signal = JSON.parse(evt.data); if (signal.sdp) { console.log("Received SDP from remote peer."); peerConn.setRemoteDescription(new RTCSessionDescription(signal.sdp)); } else if (signal.candidate) { console.log("Received ICECandidate from remote peer."); peerConn.addIceCandidate(new RTCIceCandidate(signal.candidate)); } else if ( signal.closeConnection){ console.log("Received 'close call' signal from remote peer."); endCall(); } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 wsc . onmessage = function ( evt ) { var signal = null ; if ( ! peerConn ) answerCall ( ) ; signal = JSON . parse ( evt . data ) ; if ( signal . sdp ) { console . log ( "Received SDP from remote peer." ) ; peerConn . setRemoteDescription ( new RTCSessionDescription ( signal . sdp ) ) ; } else if ( signal . candidate ) { console . log ( "Received ICECandidate from remote peer." ) ; peerConn . addIceCandidate ( new RTCIceCandidate ( signal . candidate ) ) ; } else if ( signal . closeConnection ) { console . log ( "Received 'close call' signal from remote peer." ) ; endCall ( ) ; } } ;

Here’s how we created a new connection after clicking the Video Call button: peerConn = new RTCPeerConnection(peerConnCfg);. Now, we can analyze the state of the peerConn variable to decide what to do next. If there’s no such object (!peerConn), it’s a callee’s side, and we can simply answer the call. After receiving an offer from the remote peer, we can use the setRemoteDescription method to change the remote description. When we receive a new ICE candidate from the remote peer, we have to use the addIceCandidate method to add a new remote candidate to the RTCPeerConnection’s remote description.

The createAndSendOffer and createAndSendAnswer method are used for exchanging the media information between peers. This step was described in the Signaling section of this article:

function createAndSendOffer() { peerConn.createOffer( function (offer) { var off = new RTCSessionDescription(offer); peerConn.setLocalDescription(new RTCSessionDescription(off), function() { wsc.send(JSON.stringify({"sdp": off })); }, function(error) { console.log(error);} ); }, function (error) { console.log(error);} ); }; function createAndSendAnswer() { peerConn.createAnswer( function (answer) { var ans = new RTCSessionDescription(answer); peerConn.setLocalDescription(ans, function() { wsc.send(JSON.stringify({"sdp": ans })); }, function (error) { console.log(error);} ); }, function (error) {console.log(error);} ); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function createAndSendOffer ( ) { peerConn . createOffer ( function ( offer ) { var off = new RTCSessionDescription ( offer ) ; peerConn . setLocalDescription ( new RTCSessionDescription ( off ) , function ( ) { wsc . send ( JSON . stringify ( { "sdp" : off } ) ) ; } , function ( error ) { console . log ( error ) ; } ) ; } , function ( error ) { console . log ( error ) ; } ) ; } ; function createAndSendAnswer ( ) { peerConn . createAnswer ( function ( answer ) { var ans = new RTCSessionDescription ( answer ) ; peerConn . setLocalDescription ( ans , function ( ) { wsc . send ( JSON . stringify ( { "sdp" : ans } ) ) ; } , function ( error ) { console . log ( error ) ; } ) ; } , function ( error ) { console . log ( error ) ; } ) ; } ;

The last step is to define the endCall() function:

function endCall() { peerConn.close(); peerConn = null; videoCallButton.removeAttribute("disabled"); endCallButton.setAttribute("disabled", true); if (localVideoStream) { localVideoStream.getTracks().forEach(function (track) { track.stop(); }); localVideo.src = ""; } if (remoteVideo) remoteVideo.src = ""; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 function endCall ( ) { peerConn . close ( ) ; peerConn = null ; videoCallButton . removeAttribute ( "disabled" ) ; endCallButton . setAttribute ( "disabled" , true ) ; if ( localVideoStream ) { localVideoStream . getTracks ( ) . forEach ( function ( track ) { track . stop ( ) ; } ) ; localVideo . src = "" ; } if ( remoteVideo ) remoteVideo . src = "" ; } ;

Calling the close method we close the RTCPeerConnection. Then we have to stop the video streams and reset the state of the <video> elements. Last, we need to enable the Video Call to allow a user to make a new call and disable the End Call button.

Creating a Signaling Server

WebRTC works only with SSL. For the testing needs you can create two files that will be used by our app. Create a new subfolder named ssl. Within this folder create a new file named cert.pem:

SEE CERTIFICATE —–BEGIN CERTIFICATE—–

MIIFeTCCA2GgAwIBAgIJALbd9g3P6dkrMA0GCSqGSIb3DQEBCwUAMDExCzAJBgNV

BAYTAkRFMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0wCwYDVQQKEwRIT01FMB4XDTE2

MTIyMTExMDU1OVoXDTI2MTIxOTExMDU1OVowMTELMAkGA1UEBhMCREUxEzARBgNV

BAgTClNvbWUtU3RhdGUxDTALBgNVBAoTBEhPTUUwggIiMA0GCSqGSIb3DQEBAQUA

A4ICDwAwggIKAoICAQDU/8U/mJhm0JtYaOI+37sXt2UfTG/5x6bS+ggdPALPwHDY

FN5HXyqVClDw5tAhsVYHlvspqkiU94APuRdPcy2vSIcCGfOPSb5+68JDDukslM9H

cTZfdYOMyWt0MUmEqRmfOLOojTPZ0WYT0oaCCpHPij1FLEFhFV7aoeqB5Bb2kB49

oLjWQE4Qm7wmIULxSKIN08BicoJsyh7YPlWwrj2lZblSVGIfx2xuy1T7ce1ouVId

GLqb1GywOacdHvay+XETS3khBWEgI8bnOx/9KuyjZGbIxkjf2KrV6E1aA99Rvb4F

y7XyEGhdFOj+bpeNh8+klBXARbPY20aSrs5cGtYs/jG1/aAts3YiFDrXLn9F/NXd

87bKe7Tk7skJLFe4Z2H7/04L0xyBvczzh/svnqwm6J5t0s7PkWnaN9CCQohARVUp

B3jGY6Tzi+WAE7tBYMU268F/Y/sdmgtDvikIRcAw7zpo1TJWbQDiwN3o/sJ0RDZG

bWnUvetLKyed9oB7CyftO7Nho4y8RVID+fvpQt7j2IHoproRx/4sbxT/ZbgVMVO7

CDYzyW2lSmt94K02kzueN/1frZR5S2wDcP3DtmNECg8aoTysF+ZewOjzWjwW5PVb

tpz6+AVi68+rzYbd0I5iVHVjqSwiavwYl5a/hQRrQMoctf10fTnitmEYtd1XewID

AQABo4GTMIGQMB0GA1UdDgQWBBSTCmeH5Oq0oC+o/2M8BECRRlkxEjBhBgNVHSME

WjBYgBSTCmeH5Oq0oC+o/2M8BECRRlkxEqE1pDMwMTELMAkGA1UEBhMCREUxEzAR

BgNVBAgTClNvbWUtU3RhdGUxDTALBgNVBAoTBEhPTUWCCQC23fYNz+nZKzAMBgNV

HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBkBa3S1+ZQaOkRz+GtTWrK2d8f

FmCn265jAMJadbJKvq6p81kvFr6gOVUsxRUuPGAlqC08EVVM9DzbkAvT1aoQKDax

2hHHkp0wMXwGNDcjt83UXaSWY2NmUfqkLD29qhMbNbTFjvzij3BJ4Nz9kwO3QKBy

9h+pFDVGrT2B3cn6KGCFvNq2lyzVJyc0rO1mUG6+BgnHx6cfzv9lT1oKe0bH8N41

iRuNMedSzV8gnpTQtVw5AHi/pJy0UekOzs5bKfg9iiOUoQ63D1UFQdrOom1ocbrK

8eZvzDwRRsZx41hdA+5yjtPTpDTyG7XBNLgHqv0fqRcrQUa5MDEtXZP5SIoO3uk+

3irCl/F5jgvvDbQWPQSVJ+H85H9Sg0BbjfY8r4xSsS4LyG0dko7snq++Gva33PSl

tBcBhr/MlLyA5mqaI9zlHx5LuEQfhyKeZqIxioWKDIJ3ccbQ+mwtVgWvqOJ6YA+z

b+cXLiZOqKa+h1iQqy8MBHBMBT0fqhHDNtEY0iOGrz/IFsHTiapNCSF/R06ansvw

CLICw9IfkQ0XPjt1nnAq7BV5+XB1vZj8GY3Au2d6Cz+oYtFvdUx0JIfVernfw59K

K6r7Qo4wHprZosxwJzzRtP8V/gXBlMxVMNXTnKn0psnFh857UP03t1lAFmCJxMeh

vgSKirSB9FMod0lb4A==

—–END CERTIFICATE—–



The second file should be named key.pem:

SEE ENCRYPTED PRIVATE KEY —–BEGIN ENCRYPTED PRIVATE KEY—–

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

YHo=

—–END ENCRYPTED PRIVATE KEY—–



Our application works via WebSocket, so we have to install it as well as some other dependencies. Create a new file named package.json and paste the following code in it:

{ "name": "webrtc-video-chat", "main": "server.js", "dependencies": { "express": "^4.14.0", "fs": "0.0.1-security", "ws": "^1.1.1" } } 1 2 3 4 5 6 7 8 9 { "name" : "webrtc-video-chat" , "main" : "server.js" , "dependencies" : { "express" : "^4.14.0" , "fs" : "0.0.1-security" , "ws" : "^1.1.1" } }

To install the required files use the following command:

npm install

The application uses Secure WebSockets on port 3434. The server code allows WebSocket connections. It also broadcasts messages that were received from one peer to all other users.

Paste the required server in the server.js file:

const WebSocketServer = require('ws').Server, express = require('express'), https = require('https'), app = express(), fs = require('fs'); const pkey = fs.readFileSync('./ssl/key.pem'), pcert = fs.readFileSync('./ssl/cert.pem'), options = {key: pkey, cert: pcert, passphrase: '123456789'}; var wss = null, sslSrv = null; // use express static to deliver resources HTML, CSS, JS, etc) // from the public folder app.use(express.static('public')); app.use(function(req, res, next) { if(req.headers['x-forwarded-proto']==='http') { return res.redirect(['https://', req.get('Host'), req.url].join('')); } next(); }); // start server (listen on port 3434 - SSL) sslSrv = https.createServer(options, app).listen(3434); console.log("The HTTPS server is up and running"); // create the WebSocket server wss = new WebSocketServer({server: sslSrv}); console.log("WebSocket Secure server is up and running."); /** successful connection */ wss.on('connection', function (client) { console.log("A new WebSocket client was connected."); /** incomming message */ client.on('message', function (message) { /** broadcast message to all clients */ wss.broadcast(message, client); }); }); // broadcasting the message to all WebSocket clients. wss.broadcast = function (data, exclude) { var i = 0, n = this.clients ? this.clients.length : 0, client = null; if (n < 1) return; console.log("Broadcasting message to all " + n + " WebSocket clients."); for (; i < n; i++) { client = this.clients[i]; // don't send the message to the sender... if (client === exclude) continue; if (client.readyState === client.OPEN) client.send(data); else console.error('Error: the client state is ' + client.readyState); } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 const WebSocketServer = require ( 'ws' ) . Server , express = require ( 'express' ) , https = require ( 'https' ) , app = express ( ) , fs = require ( 'fs' ) ; const pkey = fs . readFileSync ( './ssl/key.pem' ) , pcert = fs . readFileSync ( './ssl/cert.pem' ) , options = { key : pkey , cert : pcert , passphrase : '123456789' } ; var wss = null , sslSrv = null ; // use express static to deliver resources HTML, CSS, JS, etc) // from the public folder app . use ( express . static ( 'public' ) ) ; app . use ( function ( req , res , next ) { if ( req . headers [ 'x-forwarded-proto' ] === 'http' ) { return res . redirect ( [ 'https://' , req . get ( 'Host' ) , req . url ] . join ( '' ) ) ; } next ( ) ; } ) ; // start server (listen on port 3434 - SSL) sslSrv = https . createServer ( options , app ) . listen ( 3434 ) ; console . log ( "The HTTPS server is up and running" ) ; // create the WebSocket server wss = new WebSocketServer ( { server : sslSrv } ) ; console . log ( "WebSocket Secure server is up and running." ) ; /** successful connection */ wss . on ( 'connection' , function ( client ) { console . log ( "A new WebSocket client was connected." ) ; /** incomming message */ client . on ( 'message' , function ( message ) { /** broadcast message to all clients */ wss . broadcast ( message , client ) ; } ) ; } ) ; // broadcasting the message to all WebSocket clients. wss . broadcast = function ( data , exclude ) { var i = 0 , n = this . clients ? this . clients . length : 0 , client = null ; if ( n < 1 ) return ; console . log ( "Broadcasting message to all " + n + " WebSocket clients." ) ; for ( ; i < n ; i ++ ) { client = this . clients [ i ] ; // don't send the message to the sender... if ( client === exclude ) continue ; if ( client . readyState === client . OPEN ) client . send ( data ) ; else console . error ( 'Error: the client state is ' + client . readyState ) ; } } ;

To run the app use the command:

nodejs server.js

If you haven’t changed the webrtc.js file, you can open in your browser https://localhost:3434/. In a new browser window, you can open the same address and click the Video Call button.

Conclusion

The WebRTC example that we reviewed provides only basic functionality. It supports only one-to-one video chat, doesn’t allow exchanging text messages or files and it does not look attractive enough. The team of experienced web developers can significantly extend the functionality of WebRTC based chatting apps and create a competitive solution capable of providing the required level of security.

XB Software offers video chat apps development and building of innovative communication tools and web real-time applications using WebRTC and other real-time technologies.