Webrtc Tutorial
Webrtc Tutorial
Audience
This tutorial is going to help all those developers who would like to learn how to build applications
such as real-time advertising, multiplayer games, live broadcasting, e-learning, to name a few,
where the action takes place in real time.
Prerequisites
WebRTC is a powerful tool that can be used to infuse Real-Time Communications (RTC)
capabilities into browsers and mobile applications. This tutorial covers only the basics of WebRTC
and any regular developer with some level of exposure to real-time session management can
easily grasp the concepts discussed here.
All the content and graphics published in this e-book are the property of Tutorials Point (I) Pvt.
Ltd. The user of this e-book is prohibited to reuse, retain, copy, distribute, or republish any
contents or a part of contents of this e-book in any manner without written consent of the
publisher.
We strive to update the contents of our website and tutorials as timely and as precisely as
possible, however, the contents may contain inaccuracies or errors. Tutorials Point (I) Pvt. Ltd.
provides no guarantee regarding the accuracy, timeliness, or completeness of our website or its
contents including this tutorial. If you discover any errors on our website or in this tutorial,
please notify us at contact@tutorialspoint.com
i
WebRTC
Table of Contents
About the Tutorial ............................................................................................................................................. i
Audience ........................................................................................................................................................... i
Prerequisites ..................................................................................................................................................... i
STUN ...............................................................................................................................................................21
TURN ...............................................................................................................................................................22
ICE ...................................................................................................................................................................24
ii
WebRTC
RTCPeerConnection API...................................................................................................................................34
7. SENDING MESSAGES.................................................................................................................... 47
Answering .......................................................................................................................................................60
Android ...........................................................................................................................................................70
iOS...................................................................................................................................................................71
Blackberry .......................................................................................................................................................71
iii
WebRTC
iv
1. Web RTC Overview WebRTC
Basic Scheme
WebRTC allows you to set up peer-to-peer connections to other web browsers quickly and easily.
To build such an application from scratch, you would need a wealth of frameworks and libraries
dealing with typical issues like data loss, connection dropping, and NAT traversal. With WebRTC,
all of this comes built-in into the browser out-of-the-box. This technology doesn't need any
plugins or third-party software. It is open-sourced and its source code is freely available at
http://www.webrtc.org/.
The WebRTC API includes media capture, encoding and decoding audio and video, transportation
layer, and session management.
1
WebRTC
Media Capture
The first step is to get access to the camera and microphone of the user's device. We detect the
type of devices available, get user permission to access these devices and manage the stream.
Transportation Layer
The transportation layer manages the order of packets, deal with packet loss and connecting to
other users. Again the WebRTC API gives us an easy access to events that tell us when there
are issues with the connection.
Session Management
The session management deals with managing, opening and organizing connections. This is
commonly called signaling. If you transfer audio and video streams to the user it also makes
sense to transfer collateral data. This is done by the RTCDataChannel API.
Engineers from companies like Google, Mozilla, Opera and others have done a great job to bring
this real-time experience to the Web.
Browser Compatibility
The WebRTC standards are one of the fastest evolving on the web, so it doesn't mean that every
browser supports all the same features at the same time. To check whether your browser
supports WebRTC or not, you may visit http://caniuse.com/#feat=rtcpeerconnection.
Throughout all the tutorials, I recommend you to use Chrome for all the examples.
2
WebRTC
Click the Allow button to start streaming your video and audio to the web page. You should
see a video stream of yourself.
3
WebRTC
Now open the URL you are currently on in a new browser tab and click on JOIN. You should
see two video streams one from your first client and another from the second one.
Use Cases
The real-time web opens the door to a whole new range of applications, including text-based
chat, screen and file sharing, gaming, video chat, and more. Besides communication you can
use WebRTC for other purposes like:
4
WebRTC
real-time marketing
real-time advertising
back office communications (CRM, ERP, SCM, FFM)
HR management
social networking
dating services
online medical consultations
financial services
surveillance
multiplayer games
live broadcasting
e-learning
Summary
Now you should have a clear understanding of the term WebRTC. You should also have an idea
of what types of applications can be built with WebRTC, as you have already tried it in your
browser. To sum up, WebRTC is quite a useful technology.
5
2. WebRTC Architecture WebRTC
6
WebRTC
API for web developers this layer contains all the APIs web developer needed, including
RTCPeerConnection, RTCDataChannel, and MediaStrean objects.
Transport components allow establishing connections across various types of networks while
voice and video engines are frameworks responsible for transferring audio and video streams
from a sound card and camera to the network. For Web developers, the most important part is
WebRTC API.
If we look at the WebRTC architecture from the client-server side we can see that one of the
most commonly used models is inspired by the SIP(Session Initiation Protocol) Trapezoid.
In this model, both devices are running a web application from different servers. The
RTCPeerConnection object configures streams so they could connect to each other, peer-to-peer.
This signaling is done via HTTP or WebSockets.
7
WebRTC
In this model both devices use the same web application. It gives web developer more flexibility
when managing user connections.
The main task of the RTCPeerConnection object is to setup and create a peer connection. We
can easily hook keys points of the connection because this object fires a set of events when they
appear. These events give you access to the configuration of our connection:
8
WebRTC
The RTCPeerConnection is a simple javascript object, which you can simply create this way:
[code]
var conn = new RTCPeerConnection(conf);
conn.onaddstream = function(stream){
// use stream here
};
[/code]
The RTCPeerConnection object accepts a conf parameter, which we will cover later in these
tutorials. The onaddstream event is fired when the remote user adds a video or audio stream to
their peer connection.
MediaStream API
Modern browsers give a developer access to the getUserMedia API, also known as the
MediaStream API. There are three key points of functionality:
It gives a developer access to a stream object that represent video and audio streams
It manages the selection of input user devices in case a user has multiple cameras or
microphones on his device
It provides a security level asking user all the time he wants to fetch s stream
9
WebRTC
To test this API let's create a simple HTML page. It will show a single <video> element, ask the
user's permission to use the camera and show a live stream from the camera on the page. Create
an index.html file and add:
[code]
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<video autoplay></video>
<script src=client.js></script>
</body>
</html>
[/code]
[code]
//checks if the browser supports WebRTC
function hasUserMedia(){
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia || navigator.msGetUserMedia;
return !!navigator.getUserMedia;
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
//get both video and audio streams from user's camera
navigator.getUserMedia({ video: true, audio: true }, function (stream) {
var video = document.querySelector('video');
//insert stream into the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
} else {
10
WebRTC
Now open the index.html and you should see the video stream displaying your face.
But be careful, because WebRTC works only on the server side. If you simply open this page
with the browser it won't work. You need to host these files on the Apache or Node servers, or
which one you prefer.
[code]
var peerConn = new RTCPeerConnection();
//establishing peer connection
//...
//end of establishing peer connection
var dataChannel = peerConnection.createDataChannel("myChannel",
dataChannelOptions);
// here we can start sending direct messages to another peer
[/code]
This is all you needed, just two lines of code. Everything else is done on the browser's internal
layer. You can create a channel at any peer connection until the RTCPeerConnectionobject is
closed.
Summary
You should now have a firm grasp of the WebRTC architecture. We also covered MediaStream,
RTCPeerConnection, and RTCDataChannel APIs. The WebRTC API is a moving target, so always
keep up with the latest specifications.
11
3. WebRTC Environment WebRTC
Before we start building our WebRTC applications, we should set our coding environment. First
of all, you should have a text editor or IDE where you can edit HTML and Javascript. There are
chances that you have already chosen the preferred one as you are reading this tutorial. As for
me, I'm using WebStorm IDE. You can download its trial version at
https://www.jetbrains.com/webstorm/. I'm also using Linux Mint as my OS of choice.
The other requirement for common WebRTC applications is having a server to host the HTML
and Javascript files. The code will not work just by double-clicking on the files because the
browser is not allowed to connect to cameras and microphones unless the files are being served
by an actual server. This is done obviously due to the security issues.
There are tons of different web servers, but in this tutorial, we are going to use Node.js with
node-static.:
3. Open the /home/YOUR_USERNAME/.profile file and add the following line to the end:
export PATH=$PATH:/usr/local/nodejs/bin
5. Now the node command should be available from the command line. The npm command
is also available. NMP is the package manager for Node.js. You can learn more at
https://www.npmjs.com/ .
6. Open up a terminal and run sudo npm install -g node-static. This will install the static
web server for Node.js.
7. Now navigate to any directory containing the HTML files and run the static command
inside the directory to start your web server.
There is another way to install nodejs. Just run sudo apt-get install nodejs in the terminal
window.
To test your Node.js installation open up your terminal and run the node command. Type a few
commands to check how it works:
12
WebRTC
Node.js runs Javascript files as well as commands typed in the terminal. Create an index.js file
with the following content:
console.log(Testing Node.js);
Then run the node index command. You will see the following:
When building our signaling server we will use a WebSockets library for Node.js . To install in
run npm install ws in the terminal.
For testing our signaling server, we will use the wscat utility. To install it run npm install -g wscat
in your terminal window.
13
WebRTC
WebRTC Protocols
Real-time data communication means a fast connection speed between both user's devices. A
common connection takes a frame of video or audio and transfers it to another user's device
between 30 and 60 times per second in order to achieve a good quality. So it is important to
understand that sending the latest frame of data is more crucial than making sure that every
single frame gets to the other side. That is why WebRTC applications may miss certain frames
in order to keep a good speed of the connection.
You may see this effect almost in any video-playing application nowadays. Video games and
video streaming apps can afford to lose a few frames of video because our mind try to fill these
spaces as we always visualize what we are watching. If we want our application to play 50 frames
in one second and we miss frames 15, 25, and 38, most of the time, the user won't event notice
it. So for video streaming applications there is a different set of requirements:
This is why WebRTC applications use UDP (User Datagram Protocol) as the transport protocol.
Most web applications today are built with the using of the TCP (Transmission Control Protocol)
because it guarantees that:
14
WebRTC
any data that does not get to the other side will be resent and sending of other data will
be temporarily terminated
You may see why TCP is a great choice for most web applications today. If you are requesting
an HTML page, it makes sense to get all the data in the right order. But this technology can not
fit for all use cases. If we take, for example, a multiplayer game, the user will be able to only
see what has happened in the last few seconds and nothing more which may lead to a large
bottleneck when the data is missing:
The audio and video WebRTC connection is not mean to be the most reliable, but rather to be
the fastest between two user's devices. So we can afford losing frames, which means that UDP
is the best choice for audio and video streaming applications.
UDP was built to be a less reliable transport layer. You can not be sure in:
15
WebRTC
Nowadays, WebRTC sends media packets in the fastest way possible. WebRTC can be a complex
topic when concerning large corporate networks. Their firewalls can block UDP traffic across
them. A lot of work have been done to make UDP work properly for wide audience.
Most Internet traffic today is built on TCP and UDP, not only web pages. You can find them in
tablets, mobile devices, Smart TVs, and more. So it is important to understand how these
technologies work.
The SDP is a well-known method of establishing media connections as it appeared in the late
90s. It has been used in a vast amount of other types of applications before WebRTC like phone
and text-based chatting.
The SDP is string data containing sets of key-value pairs, separated by line breaks:
key=value\n
The key is a single character that sets the type of the value. The value is a machine-readable
configuration value.
The SDP covers media description and media constraints for a given user. When we start using
RTCPeerConnection object later we will be able easily print this to the javascript console.
The SDP is the first part of the peer connection. Peers have to exchange SDP data with the help
of the signaling channel in order to establish a connection.
v=0
o=- 487255629242026503 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG
m=audio 9 RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
c=IN IP4 0.0.0.0
16
WebRTC
a=ice-pwd:sbfskHYHACygyHW1wVi8GZM+
a=ice-options:google-ice
a=fingerprint:sha-256
28:4C:19:10:97:56:FB:22:57:9E:5A:88:28:F3:04:DF:37:D0:7D:55:C3:D1:59:B0:B2:81
:FB:9D:DF:CB:15:A8
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:3607952327 cname:v1SBHP7c76XqYcWx
a=ssrc:3607952327 msid:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG 9eb1f6d5-c3b2-
46fe-b46b-63ea11c46c74
a=ssrc:3607952327 mslabel:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG
a=ssrc:3607952327 label:9eb1f6d5-c3b2-46fe-b46b-63ea11c46c74
m=video 9 RTP/SAVPF 100 116 117 96
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:8a1/LJqQMzBmYtes
17
WebRTC
a=ice-pwd:sbfskHYHACygyHW1wVi8GZM+
a=ice-options:google-ice
a=fingerprint:sha-256
28:4C:19:10:97:56:FB:22:57:9E:5A:88:28:F3:04:DF:37:D0:7D:55:C3:D1:59:B0:B2:81
:FB:9D:DF:CB:15:A8
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=sendrecv
a=rtcp-mux
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=100
a=ssrc-group:FID 1175220440 3592114481
a=ssrc:1175220440 cname:v1SBHP7c76XqYcWx
a=ssrc:1175220440 msid:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG 43d2eec3-7116-
4b29-ad33-466c9358bfb3
a=ssrc:1175220440 mslabel:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG
a=ssrc:1175220440 label:43d2eec3-7116-4b29-ad33-466c9358bfb3
a=ssrc:3592114481 cname:v1SBHP7c76XqYcWx
a=ssrc:3592114481 msid:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG 43d2eec3-7116-
4b29-ad33-466c9358bfb3
a=ssrc:3592114481 mslabel:6x9ZxQZqpo19FRr3Q0xsWC2JJ1lVsk2JE0sG
a=ssrc:3592114481 label:43d2eec3-7116-4b29-ad33-466c9358bfb3
This is taken from my own laptop. It is complex to understand at first glance. It starts with
identifying the connection with the IP address, then sets up basic information about my request,
audio and video information, encryption type. So the goal is not to understand every line, but to
get familiar with it because you will never have to work with it directly.
18
WebRTC
v=0
o=- 5504016820010393753 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 9 RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:RjDpYl08FRKBqZ4A
a=ice-pwd:wSgwewyvypHhyxrcZELBLOBO
a=fingerprint:sha-256
28:4C:19:10:97:56:FB:22:57:9E:5A:88:28:F3:04:DF:37:D0:7D:55:C3:D1:59:B0:B2:81
:FB:9D:DF:CB:15:A8
a=setup:active
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
m=video 9 RTP/SAVPF 100 116 117 96
19
WebRTC
a=ice-ufrag:RjDpYl08FRKBqZ4A
a=ice-pwd:wSgwewyvypHhyxrcZELBLOBO
a=fingerprint:sha-256
28:4C:19:10:97:56:FB:22:57:9E:5A:88:28:F3:04:DF:37:D0:7D:55:C3:D1:59:B0:B2:81
:FB:9D:DF:CB:15:A8
a=setup:active
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=recvonly
a=rtcp-mux
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=100
To sum up, the SDP acts as a text-based profile of your device to other users trying to connect
to you.
Finding a Route
In order to connect to another user, you should find a clear path around your own network and
the other user's network. But there are chances that the network you are using has several
levels of access control to avoid security issues. There are several technologies used for finding
a clear route to another user:
20
WebRTC
To understand how they work, let's see how the layout of a typical WebRTC connection looks
like:
The first step is finding out your own IP address. But there's an issue when your IP address is
sitting behind a network router. To increase security and allow multiple users to use the same
IP address the router hides your own network address and replaces it with another one. It is a
common situation when you have several IP addresses between yourself and the public Web.
STUN
STUN helps to identify each user and find a good connection between them. First of all it makes
a request to a server, enabled with the STUN protocol. Then the server sends back the IP address
of the client. The client now can identify itself with this IP address.
21
WebRTC
For using this protocol, you need a STUN-enabled server to connect to. The great thing is that
Chrome and Firefox provide default servers out-of-the-box for you to test things out.
For application in a production environment, you will need to deploy your own STUN and TURN
servers for your clients to use. There are several open-sourced services providing this today.
TURN
Sometimes there is a firewall not allowing any STUN-based traffic to the other user. For example
in some enterprise NAT. This is where TURN comes out as a different method of connecting with
another user.
TURN works by adding a relay in between the clients. This relay acts as a peer to peer connection
on behalf the users. The user then gets its data from the TURN server. Then the TURN server
will obtain and redirect every data packet that gets sent to it for each user. This is why, it is the
last resort when there are no alternatives.
22
WebRTC
Most of the time users will be fine without TURN. When setting up a production application, it is
a good idea to decide whether the cost of using a TURN server is worth it or not.
23
WebRTC
ICE
Now we can learn how STUN and TURN are all brought together through ICE. It utilizes STUN
and TURN to provide a successful peer to peer connection. ICE finds and tests in sorted order a
range of addresses that will work for both of the users.
When ICE starts off it doesn't know anything about each user's network. The process of ICE will
go through a set of stages incrementally to discover how each client's network is set up, using
a different set of technologies. The main task is to find out enough information about each
network in order to make a successful connection.
STUN and TURN are used to find each ICE candidate. ICE will use the STUN server to find an
external IP. If the connection fails it will try to use the TURN server. When the browser finds a
new ICE candidate, it notifies the client application about it. Then the application sends the ICE
candidate through the signaling channel. When enough addresses are found and tested the
connection is established.
The developers of WebRTC knew that every application would be unique when using the data
channel. Some might want the high performance of UDP while others might need the reliable
delivery of TCP. That is why the created the SCTP protocol. These are the features of SCTP:
There are two modes of the transport layer: reliable and unreliable
When transporting data messages, the are allowed to be broken down and reassembled
on the other side
There are two order modes of the transport layer: ordered and not ordered
24
WebRTC
Flow and congestion control are provided through the transport layer
The SCTP protocol uses multiple endpoints (number of connections between two IP locations),
which sends messages broken down through chunks (a part of any message).
So you must understand that the data channel uses a completely different protocol than the
other data-based transport layers in the browser. You can easily configure it up to your needs.
Summary
In this chapter, we covered several of the technologies that enable peer connections, such as
UDP, TCP, STUN, TURN, ICE, and SCTP. You should now have a surface-level understanding of
how SDP works and its use cases.
25
4. WebRTC MediaStream APIs WebRTC
The MediaStream API was designed to easy access the media streams from local cameras and
microphones. The getUserMedia() method is the primary way to access local input devices.
A real-time media stream is represented by a stream object in the form of video or audio
It provides a security level through user permissions asking the user before a web
application can start fetching a stream
The selection of input devices is handled by the MediaStream API (for example, when
there are two cameras or microphones connected to the device)
Each MediaStream object includes several MediaStreamTrack objects. They represent video and
audio from different input devices.
Each MediaStreamTrack object may include several channels (right and left audio channels).
These are the smallest parts defined by the MediaStream API.
There are two ways to output MediaStream objects. First, we can render output into a video or
audio element. Secondly, we can send output to the RTCPeerConnection object, which then send
it to a remote peer.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<video autoplay></video>
<script src="client.js"></script>
</body>
</html>
26
WebRTC
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
//enabling video and audio channels
navigator.getUserMedia({ video: true, audio: true }, function (stream) {
var video = document.querySelector('video');
//inserting our stream to the video tag
video.src = window.URL.createObjectURL(stream);
}, function (err) {});
} else {
alert("WebRTC is not supported");
}
Here we create the hasUserMedia() function which checks whether WebRTC is supported or not.
Then we access the getUserMedia function where the second parameter is a callback that accept
the stream coming from the user's device. Then we load our stream into the video element using
window.URL.createObjectURL which creates a URL representing the object given in parameter.
Now refresh your page, click Allow, and you should see your face on the screen.
27
WebRTC
Remember to run all your scripts using the web server. We have already installed one in the
WebRTC Environment Tutorial.
MediaStream API
Properties
MediaStream.active (read only) Returns true if the MediaStream is active, or false
otherwise.
MediaStream.ended (read only, deprecated) Return true if the ended event has
been fired on the object, meaning that the stream has been completely read, or false if
the end of the stream has not been reached.
28
WebRTC
Event Handlers
MediaStream.onactive A handler for an active event that is fired when a MediaStream
object becomes active.
Methods
MediaStream.addTrack() Adds the MediaStreamTrack object given as argument to
the MediaStream. If the track has already been added, nothing happens.
29
WebRTC
To test the above APIs change change the index.html in the following way:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<video autoplay></video>
<div><button id="btnGetAudioTracks">getAudioTracks()</button></div>
<div><button id="btnGetTrackById">getTrackById()</button></div>
<div><button id="btnGetTracks">getTracks()</button></div>
<div><button id="btnGetVideoTracks">getVideoTracks()</button></div>
<div><button id="btnRemoveAudioTrack">removeTrack() -
audio</button></div>
<div><button id="btnRemoveVideoTrack">removeTrack() -
video</button></div>
<script src="client.js"></script>
</body>
</html>
We added a few buttons to try out several MediaStream APIs. Then we should add event handlers
for our newly created button. Modify the client.js file this way:
var stream;
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
30
WebRTC
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
btnGetAudioTracks.addEventListener("click", function(){
console.log("getAudioTracks");
console.log(stream.getAudioTracks());
});
btnGetTrackById.addEventListener("click", function(){
console.log("getTrackById");
console.log(stream.getTrackById(stream.getAudioTracks()[0].id));
});
btnGetTracks.addEventListener("click", function(){
console.log("getTracks()");
console.log(stream.getTracks());
});
btnGetVideoTracks.addEventListener("click", function(){
console.log("getVideoTracks()");
console.log(stream.getVideoTracks());
});
31
WebRTC
btnRemoveAudioTrack.addEventListener("click", function(){
console.log("removeAudioTrack()");
stream.removeTrack(stream.getAudioTracks()[0]);
});
btnRemoveVideoTrack.addEventListener("click", function(){
console.log("removeVideoTrack()");
stream.removeTrack(stream.getVideoTracks()[0]);
});
Now refresh your page. Click on the getAudioTracks() button, then click on the removeTrack() -
audio button. The audio track should now be removed. Then do the same for the video track.
If you click the getTracks() button you should see all MediaStreamTracks (all connected video
and audio inputs). Then click on the getTrackById() to get audio MediaStreamTrack.
32
WebRTC
Summary
In this chapter, we created a simple WebRTC application using the MediaStream API. Now you
should have a clear overview of the various MediaStream APIs that make WebRTC work.
33
5. WebRTC RTCPeerConnection APIs WebRTC
The RTCPeerConnection API is the core of the peer-to-peer connection between each of the
browsers. To create the RTCPeerConnection objects simply write
var pc = RTCPeerConnection(config);
where the config argument contains at least on key, iceServers. It is an array of URL objects
containing information about STUN and TURN servers, used during the finding of the ICE
candidates. You can find a list of available public STUN servers at
https://code.google.com/p/natvpn/source/browse/trunk/stun_server_list.
Depending upon whether you are the caller or the callee the RTCPeerConnection object is used
in a slightly different way on each side of the connection.
1. Register the onicecandidate handler. It sends any ICE candidates to the other peer, as
they are received.
2. Register the onaddstream handler. It handles the displaying of the video stream once it
is received from the remote peer.
3. Register the message handler. Your signaling server should also have a handler for
messages received from the other peer. If the message contains the
RTCSessionDescription object, it should be added to the RTCPeerConnection object using
the setRemoteDescription() method. If the message contains the RTCIceCandidate
object, it should be added to the RTCPeerConnection object using the addIceCandidate()
method.
4. Utilize getUserMedia() to set up your local media stream and add it to the
RTCPeerConnection object using the addStream() method.
5. Start offer/answer negotiation process. This is the only step where the caller's flow is
different from the callee's one. The caller starts negotiation using the createOffer()
method and registers a callback that receives the RTCSessionDescription object. Then
this callback should add this RTCSessionDescription object to your RTCPeerConnection
object using setLocalDescription(). And finally, the caller should send this
RTCSessionDescription to the remote peer using the signaling server. The callee, on the
other, registers the same callback, but in the createAnswer() method. Notice that the
callee flow is initiated only after the offer is received from the caller.
RTCPeerConnection API
Properties
34
WebRTC
o new: the ICE agent is waiting for remote candidates or gathering addresses
o checking: the ICE agent has remote candidates, but it has not found a connection
yet
o connected: the ICE agent has found a usable connection, but is still checking more
remote candidate for better connection.
o completed: the ICE agent has found a usable connection and stopped testing remote
candidates.
o failed: the ICE agent has checked all the remote candidates but didn't find a match
for at least one component.
35
WebRTC
o have-local-offer: the local side of the connection has locally applied a SDP offer.
o have-remote-offer: the remote side of the connection has locally applied a SDP
offer.
o have-local-pranswer: a remote SDP offer has been applied, and a SDP pranswer
applied locally.
o have-remote-pranswer: a local SDP has been applied, and a SDP pranswer applied
remotely.
Event Handlers
RTCPeerConnection.onaddstream This handler is called when the addstream event
is fired. This event is sent when a MediaStream is added to this connection by the remote
peer.
36
WebRTC
Methods
RTCPeerConnection() Returns a new RTCPeerConnection object.
37
WebRTC
Establishing a Connection
Now let's create an example application. Firstly, run the signaling server we created in the
signaling server tutorial via node server.
There will be two text inputs on the page, one for a login and one for a username we want to
connect to. Create an index.html file and add the following code:
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
<input type="text" id="loginInput" />
<button id="loginBtn">Login</button>
</div>
<div>
<input type="text" id="otherUsernameInput" />
38
WebRTC
<button id="connectToOtherUsernameBtn">Establish
connection</button>
</div>
<script src="client2.js"></script>
</body>
</html>
You can see that we've added the text input for a login, the login button, the text input for the
other peer username, and the connect-to-him button. Now create a client.js file and add the
following code:
39
WebRTC
40
WebRTC
connection.onopen = function () {
console.log("Connected");
};
41
WebRTC
You can see that we establish a socket connection to our signaling server. When a user clicks on
the login button the application sends his username to the server. If login is successful the
application creates the RTCPeerConnection object and setup onicecandidate handler which sends
all found icecandidates to the other peer. Now open the page and try to login. You should see
the following console output:
The next step is to create an offer to the other peer. Add the following code to your client.js file:
if (otherUsername.length > 0) {
//make an offer
myConnection.createOffer(function (offer) {
console.log();
send({
type: "offer",
offer: offer
});
42
WebRTC
myConnection.setLocalDescription(offer);
}, function (error) {
alert("An error has occurred.");
});
}
});
myConnection.createAnswer(function (answer) {
myConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("oops...error");
});
}
43
WebRTC
You can see that when a user clicks the Establish connection button the application makes an
SDP offer to the other peer. We also set onAnswer and onCandidate handlers. Reload your page,
open it in two tabs, login with two users and try to establish a connection between them. You
should see the following console output:
Now the peer-to-peer connection is established. In the next tutorials, we will add video and audio
streams as well as text chat support.
44
6. WebRTC RTCDataChannel APIs WebRTC
WebRTC is not only good at transferring audio and video streams, but any arbitrary data we
might have. This is where the RTCDataChannel object comes into play.
RTCDataChannel API
Properties
RTCDataChannel.label (read only) Returns a string containing the data channel
name.
RTCDataChannel.id (read only) Returns a unique id for the channel which is set at
the creation of the RTCDataChannel object.
o connecting: Indicates that the connection is not yet active. This is the initial state.
o closing: Indicates that the connection is in the process of shutting down. The cached
messages are in the process of being sent or received, but no newly created task is
accepting.
o closed: Indicates that the connection could not be established or has been shut down.
45
WebRTC
Event Handlers
RTCDataChannel.onopen This event handler is called when the open event is fired.
This event is sent when the data connection has been established.
RTCDataChannel.onclose This event handler is called when the close event is fired.
This event is sent when the data connection has been closed.
RTCDataChannel.onerror This event handler is called when the error event is fired.
This event is sent when an error has been encountered.
Methods
RTCDataChannel.close() Closes the data channel.
RTCDataChannel.send() Sends the data in the parameter over the channel. The data
can be a blob, a string, an ArrayBuffer or an ArrayBufferView.
46
7. Sending Messages WebRTC
Now let's create a simple example. Firstly, run the signaling server we created in the signaling
server tutorial via node server.
There will be three text inputs on the page, one for a login, one for a username, and one for the
message we want to send to the other peer. Create an index.html file and add the following
code:
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
<input type="text" id="loginInput" />
<button id="loginBtn">Login</button>
</div>
<div>
<input type="text" id="otherUsernameInput" />
<button id="connectToOtherUsernameBtn">Establish
connection</button>
</div>
<div>
<input type="text" id="msgInput" />
<button id="sendMsgBtn">Send text message</button>
</div>
<script src="client.js"></script>
</body>
</html>
We've also added three buttons for login, establishing a connection and sending a message. Now
create a client.js file and add the following code:
47
WebRTC
48
WebRTC
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
default:
break;
}
};
49
WebRTC
}
};
openDataChannel();
}
};
connection.onopen = function () {
console.log("Connected");
};
You can see that we establish a socket connection to our signaling server. When a user clicks on
the login button the application sends his username to the server. If login is successful the
application creates the RTCPeerConnection object and setup onicecandidate handler which sends
all found icecandidates to the other peer. It also runs the openDataChannel() function which
creates a dataChannel. Notice that when creating the RTCPeerConnection object the second
argument in the constructor optional: [{RtpDataChannels: true}] is mandatory if you are
using Chrome or Opera. The next step is to create an offer to the other peer. Add the following
code to your client.js file:
connectedUser = otherUsername;
if (otherUsername.length > 0) {
//make an offer
myConnection.createOffer(function (offer) {
console.log();
send({
type: "offer",
offer: offer
});
myConnection.setLocalDescription(offer);
}, function (error) {
alert("An error has occurred.");
});
}
});
myConnection.createAnswer(function (answer) {
myConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("oops...error");
});
}
51
WebRTC
You can see that when a user clicks the Establish connection button the application makes an
SDP offer to the other peer. We also set onAnswer and onCandidate handlers. Finally, let's
implement the openDataChannel() function which creates our dataChannel. Add the following
code to your client.js file:
Here we create the dataChannel for our connection and add the event handler for the send
message button. Now open this page in two tabs, login with two users, establish a connection,
and try to send messages. You should see them in the console output. Notice that the above
example is tested in Opera.
Now you may see that RTCDataChannel is extremely powerful part of the WebRTC API. There
are a lot of other use cases for this object, like peer-to-peer gaming or torrent-based file sharing.
53
8. WebRTC Signaling WebRTC
Most WebRTC applications are not just being able to communicate through video and audio.
They need many other features. In this chapter, we are going to build a basic signaling server.
To communicate with another user you simply need to exchange contact information and the
rest will be done by WebRTC. The process of connecting to the other user is also known as
signaling and negotiation. It consists of a few steps:
3. The signaling layer notifies another user that someone want to connect to him. He can
accept or decline.
6. Both users exchange software and hardware information through the signaling server.
The WebRTC specification does not contain any standards about exchanging information. So
keep in mind that the above is just an example of how signaling may happen. You can use any
protocol or technology you like.
54
WebRTC
The above diagram is the messaging flow between users when using the signaling server. First
of all, each user registers with the server. In our case, this will be a simple string username.
Once users have registered, they are able to call each other. User 1 makes an offer with the
user identifier he wishes to call. The other user should answers. Finally, ICE candidates are sent
between users until they can make a connection.
To create a WebRTC connection clients have to be able to transfer messages without using a
WebRTC peer connection. This is where we will use HTML5 WebSockets a bidirectional socket
connection between two endpoints a web server and a web browser. Now let's start using the
WebSocket library. Create the server.js file and insert the following code:
55
WebRTC
The first line requires the WebSocket library which we have already installed. Then we create a
socket server on the port 9090. Next, we listen to the connection event. This code will be
executed when a user makes a WebSocket connection to the server. We then listen to any
messages sent by the user. Finally, we send a response to the connected user saying Hello from
server.
Now run node server and the server should start listening for socket connections.
To test our server, we'll use the wscat utility which we also have already installed. This tool helps
in connecting directly to the WebSocket server and test out commands. Run our server in one
terminal window, then open another and run the wscat -c ws://localhost:9090 command. You
should see the following on the client side:
56
WebRTC
User Registration
In our signaling server, we will use a string-based username for each connection so we know
where to send messages. Let's change our connection handler a bit:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
This way we accept only JSON messages. Next, we need to store all connected users somewhere.
We will use a simple Javascript object for it. Change the top of our file:
57
WebRTC
We are going to add a type field for every message coming from the client. For example if a user
wants to login, he sends the login type message. Let's define it:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
The following code is a helper function for sending messages to a connection. Add it to the
server.js file:
The above function ensures that all our messages are sent in the JSON format.
When the user disconnects we should clean up its connection. We can delete the user when the
close event is fired. Add the following code to the connection handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
}
});
Now let's test our server with the login command. Keep in mind that all messages must be
encoded in the JSON format. Run our server and try to login. You should see something like this:
59
WebRTC
Making a Call
After successful login the user wants to call another. He should make an offer to another user to
achieve it. Add the offer handler:
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
Firstly, we get the connection of the user we are trying to call. If it exists we send him offer
details. We also add otherName to the connection object. This is made for the simplicity of
finding it later.
Answering
Answering to the response has a similar pattern that we used in the offer handler. Our server
just passes through all messages as answer to another user. Add the following code after the
offer handler:
60
WebRTC
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
You can see how this is similar to the offer handler. Notice this code follows the createOffer and
createAnswer functions on the RTCPeerConnection object.
Now we can test our offer/answer mechanism. Connect two clients at the same time and try to
make offer and answer. You should see the following:
In this example, offer and answer are simple strings, but in a real application they will be filled
in with the SDP data.
ICE Candidates
The final part is handling ICE candidate between users. We use the same technique just passing
messages between users. The main difference is that candidate messages might happen multiple
times per user in any order. Add the candidate handler:
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
61
WebRTC
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
This will also send the other user the leave event so he can disconnect his peer connection
accordingly. We should also handle the case when a user drops his connection from the signaling
server. Let's modify our close handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName){
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
62
WebRTC
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
Now if the connection terminates our users will be disconnected. The close event will be fired
when a user closes his browser window while we are still in offer, answer or candidate state.
console.log("User connected");
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
63
WebRTC
} catch (e) {
console.log("Invalid JSON");
data = {};
}
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
64
WebRTC
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
65
WebRTC
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
66
WebRTC
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
So the work is done and our signaling server is ready. Remember that doing things out of order
when making a WebRTC connection can cause issues.
Summary
In this chapter, we built simple and straightforward signaling server. We walked through the
signaling process, user registration and offer/answer mechanism. We also implemented sending
candidates between users.
67
9. WebRTC Browser Support WebRTC
The Web is moving so fast and it is always improving. New standards are created every day.
Browsers allow updates to be installed without the user ever knowing, so you should keep up
with what is going on in the world of the Web and WebRTC. Here is an overview of what this is
up to today.
Browser Support
Every browser doesn't have all the same WebRTC features at the same time. Different browsers
may be ahead of the curve, which makes some WebRTC features work in one browser and not
another. The current support for WebRTC in the browser is shown in the following picture.
Android OS
On Android operating systems, WebRTC applications for Chrome and Firefox should work out-
of-the-box. They are able to work with other browsers after Android Ice Cream Sandwich version
(4.0). This is due to the code sharing between desktop and mobile versions.
68
WebRTC
Apple
Apple has not yet made any announcement about their plans to support WebRTC in Safari on
OS X. One of the possible workarounds for hybrid native iOS applications os to embed the
WebRTC code directly into the application and load this app into a WebView.
Internet Explorer
Microsoft doesn't support WebRTC on desktops. But they have officially confirmed that they are
going to implement ORTC (Object Realtime Communications) in future versions of IE(Edge).
They are not planning to support WebRTC 1.0. They labeled their ORTC as WebRTC 1.1, although
it is just a community enhancement and not the official standard. Recently they've added the
ORTC support to the latest Microsoft Edge version. You may learn more at
https://blogs.windows.com/msedgedev/2015/09/18/ortc-api-is-now-available-in-microsoft-
edge/.
Summary
Notice that WebRTC is a collection of APIs and protocols, not a single API. The support for each
of these is developing on different browsers and operating systems at a different level. A great
way to check the latest level of support is through http://canisue.com. It tracks adoption of
modern APIs across multiple browsers. You can also find the latest information on browser
supports as well as WebRTC demos at http://www.webrtc.org, which is supported by Mozilla,
Google, and Opera.
69
10. WebRTC Mobile Support WebRTC
In the mobile world, the WebRTC support is not on the same level as it is on desktops. Mobile
devices have their own way, so WebRTC is also something different on the mobile platforms.
When developing a WebRTC application for desktop, we consider using Chrome, Firefox or Opera.
All of them support WebRTC out of the box. In general, you just need a browser and not bother
about the desktop's hardware.
In the mobile world there are three possible modes for WebRTC today:
Android
In 2013, the Firefox web browser for Android was presented with WebRTC support out of the
box. Now you can make video calls on Android devices using the Firefox mobile browser.
Google Chrome for Android provides WebRTC support as well. As you've already noticed, the
most interesting features usually first appear in Chrome.
In the past year, the Opera mobile browser appeared with WebRTC support. So for Android you
have Chrome, Firefox, and Opera. Other browsers don't support WebRTC.
iOS
Unfortunately, WebRTC is not supported on iOS now. Although WebRTC works well on Mac when
using Firefox, Opera, or Chrome, it is not supported on iOS.
Nowadays, your WebRTC application won't work on Apple mobile devices out of the box. But
there is a browser Bowser. It is a web browser developed by Ericsson and it supports WebRTC
out of the box. You can check its homepage at http://www.openwebrtc.org/bowser/.
Today, it is the only friendly way to support your WebRTC application on iOS. Another way is to
develop a native application yourself.
Windows Phones
Microsoft doesn't support WebRTC on mobile platforms. But they have officially confirmed that
they are going to implement ORTC (Object Realtime Communications) in future versions of IE.
They are not planning to support WebRTC 1.0. They labeled their ORTC as WebRTC 1.1, although
it is just a community enhancement and not the official standard.
So today Window Phone users can't use WebRTC applications and there is no way to beat this
situation.
Blackberry
WebRTC applications are not supported on Blackberry either, in any way.
Today only Android devices that are version 4 or higher provide this feature. Apple still doesn't
show any activity with WebRTC support. So Safari users can't use WebRTC applications. Microsoft
also did not introduce it in Windows Phone 8.
71
WebRTC
only way to bring WebRTC features to the iOS device and Opera, which is a nice alternative for
Android platform. The rest of the available mobile browsers don't support WebRTC.
Supporting mobile devices is one of the biggest pains because mobile devices have limited screen
space along with limited resources. You might want the mobile device to only capture a 480x320
resolution or smaller video stream to save power and bandwidth. Using the user agent string in
the browser is a good way to test whether the user is on a mobile device or not. Let's see an
example. Create the index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<video autoplay></video>
<script src="client.js"></script>
</body>
</html>
}
},
audio: true
};
function hasUserMedia() {
//check if the browser supports the WebRTC
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia);
}
if (hasUserMedia()) {
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
73
WebRTC
Run the web server using the static command and open the page. You should see it is 800x600.
Then open this page in a mobile viewport using chrome tools and check the resolution. It should
be 480x320.
Constraints are the easiest way to increase the performance of your WebRTC application.
Summary
In this chapter, we learned about the issues that can occur when developing WebRTC
applications for mobile devices. We discovered different limitations of supporting the WebRTC
74
WebRTC
API on mobile platforms. We also launched a demo application where we set different constraints
for desktop and mobile browsers.
75
11. WebRTC Video Demo WebRTC
In this chapter, we are going to build a client application that allows two users on separate
devices to communicate using WebRTC. Our application will have two pages. One for login and
the other for calling another user.
The two pages will be the div tags. Most input is done through simple event handlers.
76
WebRTC
Signaling Server
To create a WebRTC connection clients have to be able to transfer messages without using a
WebRTC peer connection. This is where we will use HTML5 WebSockets a bidirectional socket
connection between two endpoints a web server and a web browser. Now let's start using the
WebSocket library. Create the server.js file and insert the following code:
77
WebRTC
});
The first line requires the WebSocket library which we have already installed. Then we create a
socket server on the port 9090. Next, we listen to the connection event. This code will be
executed when a user makes a WebSocket connection to the server. We then listen to any
messages sent by the user. Finally, we send a response to the connected user saying Hello from
server.
In our signaling server, we will use a string-based username for each connection so we know
where to send messages. Let's change our connection handler a bit:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
This way we accept only JSON messages. Next, we need to store all connected users somewhere.
We will use a simple Javascript object for it. Change the top of our file:
We are going to add a type field for every message coming from the client. For example if a user
wants to login, he sends the login type message. Let's define it:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
78
WebRTC
} catch (e) {
console.log("Invalid JSON");
data = {};
}
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
79
WebRTC
});
The following code is a helper function for sending messages to a connection. Add it to the
server.js file:
When the user disconnects we should clean up its connection. We can delete the user when the
close event is fired. Add the following code to the connection handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
}
});
After successful login the user wants to call another. He should make an offer to another user to
achieve it. Add the offer handler:
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
80
WebRTC
offer: data.offer,
name: connection.name
});
break;
Firstly, we get the connection of the user we are trying to call. If it exists we send him offer
details. We also add otherName to the connection object. This is made for the simplicity of
finding it later.
Answering to the response has a similar pattern that we used in the offer handler. Our server
just passes through all messages as answer to another user. Add the following code after the
offer handler:
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
The final part is handling ICE candidate between users. We use the same technique just passing
messages between users. The main difference is that candidate messages might happen multiple
times per user in any order. Add the candidate handler:
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
81
WebRTC
});
}
break;
To allow our users to disconnect from another user we should implement the hanging up
function. It will also tell the server to delete all user references. Add the leave handler:
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
This will also send the other user the leave event so he can disconnect his peer connection
accordingly. We should also handle the case when a user drops his connection from the signaling
server. Let's modify our close handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName){
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
82
WebRTC
}
}
});
console.log("User connected");
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]){
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
84
WebRTC
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
85
WebRTC
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
86
WebRTC
}
}
});
connection.send("Hello world");
});
Client Application
One way to test this application is opening two browser tabs and trying to call each other.
First of all, we need to install the bootstrap library. Bootstrap is a frontend framework for
developing web applications. You can learn more at http://getbootstrap.com/. Create a folder
called, for example, videochat. This will be our root application folder. Inside this folder create
a file package.json (it is necessary for managing npm dependencies) and add the following:
{
"name": "webrtc-videochat",
"version": "0.1.0",
"description": "webrtc-videochat",
"author": "Author",
"license": "BSD-2-Clause"
}
Then run npm install bootstrap. This will install the bootstrap library in the
videochat/node_modules folder.
Now we need to create a basic HTML page. Create an index.html file in the root folder with the
following code:
<html>
<head>
<title>WebRTC Video Demo</title>
87
WebRTC
<link rel="stylesheet"
href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
video {
background: black;
border: 1px solid gray;
}
.call-page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#localVideo {
width: 150px;
height: 150px;
position: absolute;
top: 15px;
right: 15px;
}
88
WebRTC
#remoteVideo {
width: 500px;
height: 500px;
}
</style>
<body>
</div>
</div>
</div>
89
WebRTC
</div>
</div>
<script src="client.js"></script>
</body>
</html>
This page should be familiar to you. We have added the bootstrap css file. We have also defined
two pages. Finally, we have created several text fields and buttons for getting information from
the user. You should see the two video elements for local and remote video streams. Notice that
we have added a link to a client.js file.
Now we need to establish a connection with our signaling server. Create the client.js file in the
root folder with the following code:
//our username
var name;
var connectedUser;
conn.onopen = function () {
console.log("Connected to the signaling server");
};
switch(data.type) {
case "login":
handleLogin(data.success);
90
WebRTC
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
91
WebRTC
Now run our signaling server via node server. Then, inside the root folder run the static command
and open the page inside the browser. You should see the following console output:
The next step is implementing a user log in with a unique username. We simply send a username
to the server, which then tell us whether it is taken or not. Add the following code to your client.js
file:
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
//display the call page if login is successful
loginPage.style.display = "none";
callPage.style.display = "block";
Firstly, we select some references to the elements on the page. The we hide the call page. Then,
we add an event listener on the login button. When the user clicks it, we send his username to
the server. Finally, we implement the handleLogin callback. If the login was successful, we show
the call page and starting to set up a peer connection.
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
93
WebRTC
//**********************
//Starting a peer connection
//**********************
}, function (error) {
console.log(error);
});
}
};
Now if you run the code, the page should allow you to log in and display your local video stream
on the page.
Now we are ready to initiate a call. Firstly, we send an offer to another user. Once a user gets
the offer, he creates an answer and start trading ICE candidates. Add the following code to the
client.js file:
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
95
WebRTC
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
96
WebRTC
};
We add a click handler to the Call button, which initiates an offer. Then we implement several
handlers expected by the onmessage handler. They will be processed asynchronously until both
the users have made a connection.
The last step is implementing the hang-up feature. This will stop transmitting data and tell the
other user to close the call. Add the following code:
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
Now run the code. You should be able to log in to the server using two browser tabs. You can
then call the tab and hang up the call.
97
WebRTC
//our username
var name;
var connectedUser;
conn.onopen = function () {
console.log("Connected to the signaling server");
};
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
99
WebRTC
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
callPage.style.display = "none";
100
WebRTC
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
};
yourConn = new webkitRTCPeerConnection(configuration);
}, function (error) {
console.log(error);
});
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
102
WebRTC
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
103
WebRTC
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
Summary
This demo provides a baseline of features that every WebRTC application needs. To improve this
demo you can add user identification through platforms like Facebook or Google, handle user
input for invalid data. Also, the WebRTC connection can fail because of several reasons like not
supporting the technology or not being able to traverse firewalls. A worth of work has gone into
making any WebRTC application stable.
104
12. WebRTC Voice Demo WebRTC
In this chapter, we are going to build a client application that allows two users on separate
devices to communicate using WebRTC audio streams. Our application will have two pages. One
for login and the other for making an audio call to another user.
The two pages will be the div tags. Most input is done through simple event handlers.
105
WebRTC
Signaling Server
To create a WebRTC connection clients have to be able to transfer messages without using a
WebRTC peer connection. This is where we will use HTML5 WebSockets a bidirectional socket
connection between two endpoints a web server and a web browser. Now let's start using the
WebSocket library. Create the server.js file and insert the following code:
The first line requires the WebSocket library which we have already installed. Then we create a
socket server on the port 9090. Next, we listen to the connection event. This code will be
executed when a user makes a WebSocket connection to the server. We then listen to any
messages sent by the user. Finally, we send a response to the connected user saying Hello from
server.
In our signaling server, we will use a string-based username for each connection so we know
where to send messages. Let's change our connection handler a bit:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
106
WebRTC
data = {};
}
});
This way we accept only JSON messages. Next, we need to store all connected users somewhere.
We will use a simple Javascript object for it. Change the top of our file:
We are going to add a type field for every message coming from the client. For example if a user
wants to login, he sends the login type message. Let's define it:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
success: false
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
The following code is a helper function for sending messages to a connection. Add it to the
server.js file:
108
WebRTC
When the user disconnects we should clean up its connection. We can delete the user when the
close event is fired. Add the following code to the connection handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
}
});
After successful login the user wants to call another. He should make an offer to another user to
achieve it. Add the offer handler:
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
break;
Firstly, we get the connection of the user we are trying to call. If it exists we send him offer
details. We also add otherName to the connection object. This is made for the simplicity of
finding it later.
Answering to the response has a similar pattern that we used in the offer handler. Our server
just passes through all messages as answer to another user. Add the following code after the
offer handler:
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
109
WebRTC
The final part is handling ICE candidate between users. We use the same technique just passing
messages between users. The main difference is that candidate messages might happen multiple
times per user in any order. Add the candidate handler:
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
To allow our users to disconnect from another user we should implement the hanging up
function. It will also tell the server to delete all user references. Add the leave handler:
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
110
WebRTC
type: "leave"
});
}
break;
This will also send the other user the leave event so he can disconnect his peer connection
accordingly. We should also handle the case when a user drops his connection from the signaling
server. Let's modify our close handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName){
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
console.log("User connected");
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
112
WebRTC
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
113
WebRTC
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
114
WebRTC
break;
}
});
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
115
WebRTC
Client Application
One way to test this application is opening two browser tabs and trying to make an audio call to
each other.
First of all, we need to install the bootstrap library. Bootstrap is a frontend framework for
developing web applications. You can learn more at http://getbootstrap.com/. Create a folder
called, for example, audiochat. This will be our root application folder. Inside this folder create
a file package.json (it is necessary for managing npm dependencies) and add the following:
{
"name": "webrtc-audiochat",
"version": "0.1.0",
"description": "webrtc-audiochat",
"author": "Author",
"license": "BSD-2-Clause"
}
Then run npm install bootstrap. This will install the bootstrap library in the
audiochat/node_modules folder.
Now we need to create a basic HTML page. Create an index.html file in the root folder with the
following code:
<html>
<head>
<title>WebRTC Voice Demo</title>
<link rel="stylesheet"
href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
<div id="loginPage" class="container text-center">
<div class="row">
<div class="col-md-4 col-md-offset-4">
116
WebRTC
This page should be familiar to you. We have added the bootstrap css file. We have also defined
two pages. Finally, we have created several text fields and buttons for getting information from
the user. You should see the two audio elements for local and remote audio streams. Notice that
we have added a link to a client.js file.
117
WebRTC
Now we need to establish a connection with our signaling server. Create the client.js file in the
root folder with the following code:
//our username
var name;
var connectedUser;
conn.onopen = function () {
console.log("Connected to the signaling server");
};
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
118
WebRTC
case "leave":
handleLeave();
break;
default:
break;
}
};
Now run our signaling server via node server. Then, inside the root folder run the static command
and open the page inside the browser. You should see the following console output:
The next step is implementing a user log in with a unique username. We simply send a username
to the server, which then tell us whether it is taken or not. Add the following code to your client.js
file:
//******
//UI selectors block
//******
119
WebRTC
callPage.style.display = "none";
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
120
WebRTC
}
};
Firstly, we select some references to the elements on the page. The we hide the call page. Then,
we add an event listener on the login button. When the user clicks it, we send his username to
the server. Finally, we implement the handleLogin callback. If the login was successful, we show
the call page and starting to set up a peer connection.
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
localAudio.src = window.URL.createObjectURL(stream);
}, function (error) {
console.log(error);
});
}
};
Now if you run the code, the page should allow you to log in and display your local audio stream
on the page.
122
WebRTC
Now we are ready to initiate a call. Firstly, we send an offer to another user. Once a user gets
the offer, he creates an answer and start trading ICE candidates. Add the following code to the
client.js file:
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
123
WebRTC
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
We add a click handler to the Call button, which initiates an offer. Then we implement several
handlers expected by the onmessage handler. They will be processed asynchronously until both
the users have made a connection.
The last step is implementing the hang-up feature. This will stop transmitting data and tell the
other user to close the call. Add the following code:
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteAudio.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
124
WebRTC
};
Now run the code. You should be able to log in to the server using two browser tabs. You can
then make an audio call to the tab and hang up the call.
125
WebRTC
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
126
WebRTC
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
127
WebRTC
129
WebRTC
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
130
WebRTC
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteAudio.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};
131
13. WebRTC Text Demo WebRTC
In this chapter, we are going to build a client application that allows two users on separate
devices to send messages each other using WebRTC. Our application will have two pages. One
for login and the other for sending messages to another user.
132
WebRTC
The two pages will be the div tags. Most input is done through simple event handlers.
Signaling Server
To create a WebRTC connection clients have to be able to transfer messages without using a
WebRTC peer connection. This is where we will use HTML5 WebSockets a bidirectional socket
connection between two endpoints a web server and a web browser. Now let's start using the
WebSocket library. Create the server.js file and insert the following code:
The first line requires the WebSocket library which we have already installed. Then we create a
socket server on the port 9090. Next, we listen to the connection event. This code will be
executed when a user makes a WebSocket connection to the server. We then listen to any
messages sent by the user. Finally, we send a response to the connected user saying Hello from
server.
In our signaling server, we will use a string-based username for each connection so we know
where to send messages. Let's change our connection handler a bit:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
133
WebRTC
data = {};
}
});
This way we accept only JSON messages. Next, we need to store all connected users somewhere.
We will use a simple Javascript object for it. Change the top of our file:
We are going to add a type field for every message coming from the client. For example if a user
wants to login, he sends the login type message. Let's define it:
connection.on('message', function(message){
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
});
} else {
//save user connection on the server
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
The following code is a helper function for sending messages to a connection. Add it to the
server.js file:
When the user disconnects we should clean up its connection. We can delete the user when the
close event is fired. Add the following code to the connection handler:
135
WebRTC
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
}
});
After successful login the user wants to call another. He should make an offer to another user to
achieve it. Add the offer handler:
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
break;
Firstly, we get the connection of the user we are trying to call. If it exists we send him offer
details. We also add otherName to the connection object. This is made for the simplicity of
finding it later.
Answering to the response has a similar pattern that we used in the offer handler. Our server
just passes through all messages as answer to another user. Add the following code after the
offer handler:
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
136
WebRTC
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
The final part is handling ICE candidate between users. We use the same technique just passing
messages between users. The main difference is that candidate messages might happen multiple
times per user in any order. Add the candidate handler:
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
To allow our users to disconnect from another user we should implement the hanging up
function. It will also tell the server to delete all user references. Add the leave handler:
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
137
WebRTC
break;
This will also send the other user the leave event so he can disconnect his peer connection
accordingly. We should also handle the case when a user drops his connection from the signaling
server. Let's modify our close handler:
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName){
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
console.log("User connected");
138
WebRTC
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
139
WebRTC
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.name);
//if UserB exists then send him offer details
var conn = users[data.name];
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
140
WebRTC
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
});
141
WebRTC
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
connection.send("Hello world");
});
Client Application
One way to test this application is opening two browser tabs and trying to send a message each
other.
First of all, we need to install the bootstrap library. Bootstrap is a frontend framework for
developing web applications. You can learn more at http://getbootstrap.com/. Create a folder
142
WebRTC
called, for example, textchat. This will be our root application folder. Inside this folder create
a file package.json (it is necessary for managing npm dependencies) and add the following:
{
"name": "webrtc-textochat",
"version": "0.1.0",
"description": "webrtc-textchat",
"author": "Author",
"license": "BSD-2-Clause"
}
Then run npm install bootstrap. This will install the bootstrap library in the
textchat/node_modules folder.
Now we need to create a basic HTML page. Create an index.html file in the root folder with the
following code:
<html>
<head>
<title>WebRTC Text Demo</title>
<link rel="stylesheet"
href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
<div id="loginPage" class="container text-center">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h2>WebRTC Text Demo. Please sign in</h2>
<label for="usernameInput" class="sr-only">Login</label>
<input type="email" id="usernameInput" class="form-control form-
group" placeholder="Login" required="" autofocus="">
143
WebRTC
This page should be familiar to you. We have added the bootstrap css file. We have also defined
two pages. Finally, we have created several text fields and buttons for getting information from
144
WebRTC
the user. On the chat page you should see the div tag with the chatarea ID where all our
messages will be displayed. Notice that we have added a link to a client.js file.
Now we need to establish a connection with our signaling server. Create the client.js file in the
root folder with the following code:
//our username
var name;
var connectedUser;
conn.onopen = function () {
console.log("Connected to the signaling server");
};
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
145
WebRTC
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
Now run our signaling server via node server. Then, inside the root folder run the static command
and open the page inside the browser. You should see the following console output:
The next step is implementing a user log in with a unique username. We simply send a username
to the server, which then tell us whether it is taken or not. Add the following code to your client.js
file:
//******
//UI selectors block
146
WebRTC
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
}
};
Firstly, we select some references to the elements on the page. The we hide the call page. Then,
we add an event listener on the login button. When the user clicks it, we send his username to
the server. Finally, we implement the handleLogin callback. If the login was successful, we show
the call page, set up a peer connection, and create a data channel.
147
WebRTC
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
type: "candidate",
candidate: event.candidate
});
}
};
//when we receive a message from the other peer, display it on the screen
dataChannel.onmessage = function (event) {
chatArea.innerHTML += connectedUser + ": " + event.data + "<br />";
};
dataChannel.onclose = function () {
console.log("data channel is closed");
};
}
};
If login was successful the application creates the RTCPeerConnection object and setup
onicecandidate handler which sends all found icecandidates to the other peer. It also creates a
dataChannel. Notice, that when creating the RTCPeerConnection object the second argument in
the constructor optional: [{RtpDataChannels: true}] is mandatory if you are using Chrome
or Opera. The next step is to create an offer to the other peer. Once a user gets the offer, he
creates an answer and start trading ICE candidates. Add the following code to the client.js file:
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
149
WebRTC
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
150
WebRTC
};
We add a click handler to the Call button, which initiates an offer. Then we implement several
handlers expected by the onmessage handler. They will be processed asynchronously until both
the users have made a connection.
The next step is implementing the hang-up feature. This will stop transmitting data and tell the
other user to close the data channel. Add the following code:
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
The last step is sending a message to another peer. Add the click handler to the send
message button:
151
WebRTC
Now run the code. You should be able to log in to the server using two browser tabs. You can
then set up a peer connection to the other user and send him a message as well as close the
data channel by clicking the Hang Up button.
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
152
WebRTC
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
153
WebRTC
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var msgInput = document.querySelector('#msgInput');
var sendMsgBtn = document.querySelector('#sendMsgBtn');
var chatArea = document.querySelector('#chatarea');
var yourConn;
var dataChannel;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
154
WebRTC
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
156
WebRTC
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
//when user clicks the "send message" button
sendMsgBtn.addEventListener("click", function (event) {
var val = msgInput.value;
chatArea.innerHTML += name + ": " + val + "<br />";
//sending a message to a connected peer
dataChannel.send(val);
msgInput.value = "";
});
157
14. WebRTC Security WebRTC
In this chapter, we are going to add security features to the signaling server we created in the
WebRTC Signaling chapter. There will be two enhancements:
2. Unpack it
4. After the installation is finished, run make test to check whether everything is working
correctly.
To run the Redis server type redis-server in the terminal console. You should see the following:
158
WebRTC
Now open a new terminal window and run redis-cli to open a client application.
Basically, Redis is a key-value database. To create a key with a string value, you should use the
SET command. To read the key value you should use the GET command. Let's add two users
and passwords for them. Keys will be the usernames and values of these keys will be the
corresponding passwords.
159
WebRTC
Now we should modify our signaling server to add a user authentication. Add the following code
to the top of the server.js file:
In the above code, we require the Redis library for Node.js and creating a redis client for our
server.
To add the authentication modify the message handler on the connection object:
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
160
WebRTC
connection.isAuth = true;
sendTo(connection, {
type: "login",
success: true
});
}
});
break;
}
}
}
//...
//*****other handlers*******
In the above code if a user tries to login we get from Redis his password, check if it matches
with the stored one, and if it successful we store his username on the server. We also add the
isAuth flag to the connection to check whether the user is authenticated. Notice this code:
If an unauthenticated user tries to send offer or leave the connection we simply send an error
back.
The next step is enabling a secure socket connection. It is highly recommended for WebRTC
applications. PKI (Public Key Infrastructure) is a digital signature from a CA (Certificate
Authority). Users then check that the private key used to sign a certificate matches the public
key of the CA's certificate. For the development purposes. we will use a self-signed security
certificate.
162
WebRTC
We will use the openssl. It is an open source tool that implements SSL (Secure Sockets Layer)
and TLS (Transport Layer Security) protocols. It is often installed by default on Unix systems.
Run openssl version -a to check whether it is installed.
To generate public and private security certificate keys, you should follow the steps given below:
163
WebRTC
3. Generate a signing request. You will be asked additional questions about your
company. Just hit the Enter button all the time.
openssl x509 -req -days 1095 -in server.csr -signkey server.key -out
server.crt
164
WebRTC
Now you have two files, the certificate (server.crt) and the private key (server.key). Copy them
into the signaling server root folder.
//https://github.com/visionmedia/superagent/issues/205
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}, processRequest).listen(cfg.port);
In the above code, we require the fs library to read private key and certificate, create the cfg
object with the binding port and paths for private key and certificate. Then, we create an HTTPS
server with our keys along with WebSocket server on the port 9090.
Click the continue anyway button. You should see the OK message.
To test our secure signaling server, we will modify the chat application we created in the
WebRTC Text Demo tutorial. We just need to add a password field. The following is the entire
index.html file:
166
WebRTC
<html>
<head>
<title>WebRTC Text Demo</title>
<link rel="stylesheet"
href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
</style>
<body>
</div>
167
WebRTC
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4 text-center">
<div class="panel panel-primary">
<div class="panel-heading">Text chat</div>
<div id="chatarea" class="panel-body text-left"></div>
</div>
</div>
</div>
</div>
<script src="client.js"></script>
168
WebRTC
</body>
</html>
We also need to enable a secure socket connection in the client.js file through this line var conn
= new WebSocket('wss://localhost:9090'); . Notice the wss protocol. Then, the login button
handler must modified to send password along with username:
if (name.length > 0) {
send({
type: "login",
name: name,
password: pwd
});
}
});
//our username
var name;
var connectedUser;
conn.onopen = function () {
console.log("Connected to the signaling server");
};
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
170
WebRTC
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var passwordInput = document.querySelector('#passwordInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
callPage.style.display = "none";
if (name.length > 0) {
send({
171
WebRTC
type: "login",
name: name,
password: pwd
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...incorrect username or password");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
};
//when we receive a message from the other peer, display it on the screen
dataChannel.onmessage = function (event) {
chatArea.innerHTML += connectedUser + ": " + event.data + "<br />";
};
dataChannel.onclose = function () {
console.log("data channel is closed");
};
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
173
WebRTC
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
174
WebRTC
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
yourConn.close();
yourConn.onicecandidate = null;
};
Now run our secure signaling server via node server. Run node static inside the modified chat
demo folder. Open localhost:8080 in two browser tabs. Try to log in. Remember only user1
with password1 and user2 with password2 are allowed to login. Then establish the
RTCPeerConnection(call another user) and try to send a message.
175
WebRTC
176
WebRTC
//https://github.com/visionmedia/superagent/issues/205
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
177
WebRTC
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
178
WebRTC
});
break;
case "offer":
if(conn != null){
//setting that UserA connected with UserB
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("Sending answer to: ", data.name);
//for ex. UserB answers UserA
var conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.name);
var conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
180
WebRTC
}
break;
case "leave":
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
connection.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName){
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
}
}
});
181
WebRTC
default:
sendTo(connection, {
type: "error",
message: "Command no found: " + data.type
});
break;
}
});
Summary
In this chapter, we added user authentication to our signaling server. We also learned how to
create self-signed SSL certificates and use them in the scope of WebRTC applications.
182