This tutorial will teach you:

The basics of WebRTC

How to create a 1on1 video chat

How to use Scaledrone for signaling so that no server coding is needed

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.

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.

If we are the only user in the room, we'll start the WebRTC code and wait for an offer from another user.

If we are the second connected user, we'll start the WebRTC code and signal an offer to the first user.

With three or more connected users the room is full.

// 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

onicecandidate returns locally generated ICE candidates for signaling to other users. We pass it on to our signaling service.

returns locally generated ICE candidates for signaling to other users. We pass it on to our signaling service. onnegotiationneeded is triggered when a change has occurred which requires session negotiation. This event starts the createOffer process and is only handled by the user that is an offerer.

is triggered when a change has occurred which requires session negotiation. This event starts the createOffer process and is only handled by the user that is an offerer. onaddstream returns the remote video and audio stream of the remote user. Set that as the source of the remote video element.

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:

message.sdp - Session Description Protocol is a string describing the local end of the remote connection. After receiving an offer or answer from another peer we can answer it.

- Session Description Protocol is a string describing the local end of the remote connection. After receiving an offer or answer from another peer we can answer it. message.candidate - add the new ICE candidate to our connections remote description.

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.

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.