WebRTC Tutorial
May 16th, 2017

WebRTC Tutorial: Simple video chat

This tutorial will teach you:

Check out the live demo

What is WebRTC?

WebRTC is a collection of communications protocols and APIs that enable real-time peer to peer connections within the browser. It's perfect for multiplayer games, chat, video and voice conferences or filesharing.

WebRTC is available in most modern browsers expect Safari. It's currently supported by Chrome, Firefox, Edge and Opera. Safari has listed support for WebRTC as being in development.

WebRTC terms

Signaling

The discovery and negotiation process of WebRTC peers is called signaling. For two devices in different networks to find each other they need to use a central service called a signaling server. Using the signaling server two devices can discover each other and exchange negotiation messages. WebRTC does not specify signaling; different technologies such as Websockets can be employed for it.

ICE Candidates

Two peers exchange ICE candidates until they find a method of communication that they both support. After the connection has been established ICE candidates can be traded again to upgrade to a better and faster communication method.

STUN Server

STUN servers are used to get an external network address and to pass firewalls.

HTML Markup

The HTML is rather basic. We need two video elements. One for our Webcam's video stream and another for the remote connection's. Let's mute our video, so we only hear the remote video.

<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>

The full index.html file can be found from here.

JavaScript Setup

For the app to support more than one simultaneous call, a different URL hash will be generated for each room.

Let's read the room name from the hash part of the URL. If the URL has no hash part, let's generate it. The generated URL can then be shared with a friend.

// Generate random room name if needed
if (!location.hash) {
 location.hash = Math.floor(Math.random() * 0xFFFFFF).toString(16);
}
const roomHash = location.hash.substring(1);

configuration will be passed to the RTCPeerConnection instance. We can use Google's public STUN server.

const configuration = {
 iceServers: [{
   urls: 'stun:stun.l.google.com:19302' // Google's public STUN server
 }]
};

onSuccess and onError handlers will be used for cleaner callbacks handling.

function onSuccess() {};
function onError(error) {
  console.error(error);
};

Connecting to the signaling server

As explained before, signaling is used for the discovery and negotiation process of WebRTC peers. Let's use Scaledrone as our signaling server because it lets us use WebRTC without doing any server programming. However, if you wish to write your own signaling server, this tutorial will still work fine.

Scaledrone works by letting you subscribe to a room, it then broadcasts messages sent into that room to all subscribed users. This makes Scaledrone ideal for WebRTC signaling.

To import Scaledrone add this script tag before the closing </head> tag.

<script type='text/javascript' src='https://cdn.scaledrone.com/scaledrone.min.js'></script>

After connecting to Scaledrone we'll subscribe to a room (the room name is from the URL hash). The members event tells who is connected to the room, including us.

// Room name needs to be prefixed with 'observable-'
const roomName = 'observable-' + roomHash;
let room;
const drone = new ScaleDrone('CHANNEL_ID_FROM_SCALEDRONE');
 
drone.on('open', error => {
 if (error) {
   return onError(error);
 }
 room = drone.subscribe(roomName);
 room.on('open', error => {
   if (error) {
     onError(error);
   }
 });
 // We're connected to the room and received an array of 'members'
 // connected to the room (including us). Signaling server is ready.
 room.on('members', members => {
   if (members.length >= 3) {
     return alert('The room is full');
   }
   // If we are the second user to connect to the room we will be creating the offer
   const isOfferer = members.length === 2;
   startWebRTC(isOfferer);
   startListentingToSignals();
 });
});

The sendMessage function will send a signaling message to the other connection.

// Send signaling data via Scaledrone
function sendMessage(message) {
 drone.publish({
   room: roomName,
   message
 });
}

WebRTC

We finally got to the fun part!

The RTCPeerConnection instance pc represents a WebRTC connection between the local computer and a remote peer.

RTCPeerConnection emit handling

Finally, we get our audio and video stream, set it as the source of the local video element and add it to the connection.

let pc;
function startWebRTC(isOfferer) {
 pc = new RTCPeerConnection(configuration);
 
 // 'onicecandidate' notifies us whenever an ICE agent needs to deliver a
 // message to the other peer through the signaling server
 pc.onicecandidate = event => {
   if (event.candidate) {
     sendMessage({'candidate': event.candidate});
   }
 };
 
 // If user is offerer let the 'negotiationneeded' event create the offer
 if (isOfferer) {
   pc.onnegotiationneeded = () => {
     pc.createOffer().then(localDescCreated).catch(onError);
   }
 }
 
 // When a remote stream arrives display it in the #remoteVideo element
 pc.onaddstream = event => {
   remoteVideo.srcObject = event.stream;
 };
 
 navigator.mediaDevices.getUserMedia({
   audio: true,
   video: true,
 }).then(stream => {
   // Display your local video in #localVideo element
   localVideo.srcObject = stream;
   // Add your stream to be sent to the conneting peer
   pc.addStream(stream);
 }, onError);
}

To listen to messages from the signaling service define a startListentingToSignals function. We are interested in two types of messages; these are the same messages that we send out in the code above:

function startListentingToSignals() {
 // Listen to signaling data from Scaledrone
 room.on('data', (message, client) => {
   // Message was sent by us
   if (!client || client.id === drone.clientId) {
     return;
   }
   if (message.sdp) {
     // This is called after receiving an offer or answer from another peer
     pc.setRemoteDescription(new RTCSessionDescription(message.sdp), () => {
       // When receiving an offer lets answer it
       if (pc.remoteDescription.type === 'offer') {
         pc.createAnswer().then(localDescCreated).catch(onError);
       }
     }, onError);
   } else if (message.candidate) {
     // Add the new ICE candidate to our connections remote description
     pc.addIceCandidate(
       new RTCIceCandidate(message.candidate), onSuccess, onError
     );
   }
 });
}

localDescCreated gets called when creating an offer and when answering one. It updates the local description of the connection.

function localDescCreated(desc) {
 pc.setLocalDescription(
   desc,
   () => sendMessage({'sdp': pc.localDescription}),
   onError
 );
}

In conclusion

As you can see, creating a video chat using WebRTC is quite simple, it took about 100 lines of code. It's especially simple when you don't have to write any server side code.

We'll be writing more WebRTC tutorials in the future so keep your eyes open.

Lastly 💫

Check out the live demo and full source code.

If you have any questions or feedback, please write to us.

Updated on 21. sep 2017: Got rid of some deprecation warnings on Firefox (getting rid of all of the warnings breaks Chrome). There is a bug when using Firefox as the offerer peer (second connected user). Joining first with Firefox and then with Chrome works.

Scaledrone is a realtime messaging service and platform.
Send live updates, create chatrooms and collaborative tools.
Try Scaledrone for free