WebRTC and the Early API

In my last article, WebRTC and the Ocean of Acryonyms, I went over the networking terminology behind WebRTC. In this sequel of sorts, I will go over the new WebRTC API in great laboring detail. By the end of it you should have working peer-to-peer DataChannels and Media.

Shims

As you can imagine, with such an early API, you must use the browser prefixes and shim it to a common variable.

var PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;

PeerConnection

This is the starting point to creating a connection with a peer. It accepts information about which servers to use and options for the type of connection.

var pc = new PeerConnection(server, options);

server

The server object contains information about which TURN and/or STUN servers to use. This is required to ensure most users can actually create a connection by avoiding restrictions in NAT and firewalls.

var server = {
    iceServers: [
        {url: "stun:23.21.150.121"},
        {url: "stun:stun.l.google.com:19302"},
        {url: "turn:numb.viagenie.ca", credential: "webrtcdemo", username: "louis%40mozilla.com"}
    ]
}

Google runs a public STUN server that we can use. I also created an account at http://numb.viagenie.ca/ for a free TURN server to access. You may want to do the same and replace with your own credentials.

options

Depending on the type of connection, you will need to pass some options.

var options = {
    optional: [
        {DtlsSrtpKeyAgreement: true},
        {RtpDataChannels: true}
    ]
}

DtlsSrtpKeyAgreement is required for Chrome and Firefox to interoperate.

RtpDataChannels is required if we want to make use of the DataChannels API on Firefox.

ICECandidate

After creating the PeerConnection and passing in the available STUN and TURN servers, an event will be fired once the ICE framework has found some “candidates” that will allow you to connect with a peer. This is known as an ICE Candidate and will execute a callback function on PeerConnection#onicecandidate.

pc.onicecandidate = function (e) {
    // candidate exists in e.candidate
    if (e.candidate == null) { return }
    send("icecandidate", JSON.stringify(e.candidate));
    pc.onicecandidate = null;
};

When the callback is executed, we must use the signal channel to send the Candidate to the peer. On Chrome, multiple ICE candidates are usually found, we only need one so I typically send the first one then remove the handler. Firefox includes the Candidate in the Offer SDP.

Signal Channel

Now that we have an ICE candidate, we need to send that to our peer so they know how to connect with us. However this leaves us with a chicken and egg situation; we want PeerConnection to send data to a peer but before that we need to send them metadata…

This is where the signal channel comes in. It’s any method of data transport that allows two peers to exchange information. In this article, we’re going to use FireBase because it’s incredibly easy to setup and doesn’t require any hosting or server-code.

For now just imagine two methods exist: send() will take a key and assign data to it and recv() will call a handler when a key has a value.

The structure of the database will look like this:

{
    "<roomId>": {
        "candidate:<peerType>": …
        "offer": …
        "answer": …
    }
}

Connections are divided by a roomId and will store 4 pieces of information, the ICE candidate from the offerer, the ICE candidate from the answerer, the offer SDP and the answer SDP.

Offer

An Offer SDP (Session Description Protocol) is metadata that describes to the other peer the format to expect (video, formats, codecs, encryption, resolution, size, etc etc).

An exchange requires an offer from a peer, then the other peer must receive the offer and provide back an answer.

pc.createOffer(function (offer) {
    pc.setLocalDescription(offer);
 
    send("offer", JSON.stringify(offer));
}, errorHandler, constraints);

errorHandler

If there was an issue generating an offer, this method will be executed with error details as the first argument.

var errorHandler = function (err) {
    console.error(err);
};

constraints

Options for the offer SDP.

var constraints = {
    mandatory: {
        OfferToReceiveAudio: true,
        OfferToReceiveVideo: true
    }
};

OfferToReceiveAudio/Video tells the other peer that you would like to receive video or audio from them. This is not needed for DataChannels.

Once the offer has been generated we must set the local SDP to the new offer and send it through the signal channel to the other peer and await their Answer SDP.

Answer

An Answer SDP is just like an offer but a response; sort of like answering the phone. We can only generate an answer once we have received an offer.

recv("offer", function (offer) {
    offer = new SessionDescription(JSON.parse(offer))
    pc.setRemoteDescription(offer);
 
    pc.createAnswer(function (answer) {
        pc.setLocalDescription(answer);
 
        send("answer", JSON.stringify(answer));
    }, errorHandler, constraints);
});

DataChannel

I will first explain how to use PeerConnection for the DataChannels API and transferring arbitrary data between peers.

Note: At the time of this article, interoperability between Chrome and Firefox is not possible with DataChannels. Chrome supports a similar but private protocol and will be supporting the standard protocol soon.

var channel = pc.createDataChannel(channelName, channelOptions);

The offerer should be the peer who creates the channel. The answerer will receive the channel in the callback ondatachannel on PeerConnection. You must call createDataChannel() once before creating the offer.

channelName

This is a string that acts as a label for your channel name. Warning: Make sure your channel name has no spaces or Chrome will fail on createAnswer().

channelOptions

var channelOptions = {};

Currently these options are not well supported on Chrome so you can leave this empty for now. Check the RFC for more information about the options.

Channel Events and Methods

onopen

Executed when the connection is established.

onerror

Executed if there is an error creating the connection. First argument is an error object.

channel.onerror = function (err) {
    console.error("Channel Error:", err);
};

onmessage

channel.onmessage = function (e) {
    console.log("Got message:", e.data);
}

The heart of the connection. When you receive a message, this method will execute. The first argument is an event object which contains the data, time received and other information.

onclose

Executed if the other peer closes the connection.

Binding the Events

If you were the creator of the channel (meaning the offerer), you can bind events directly to the DataChannel you created with createChannel. If you are the answerer, you must use the ondatachannel callback on PeerConnection to access the same channel.

pc.ondatachannel = function (e) {
    e.channel.onmessage = function () {};
};

The channel is available in the event object passed into the handler as e.channel.

send()

channel.send("Hi Peer!");

This method allows you to send data directly to the peer! Amazing. You must send either String, Blob, ArrayBuffer or ArrayBufferView, so be sure to stringify objects.

close()

Close the channel once the connection should end. It is recommended to do this on page unload.

Media

Now we will cover transmitting media such as audio and video. To display the video and audio you must include a <video> tag on the document with the attribute autoplay.

Get User Media

<video id="preview" autoplay></video>
 
var video = document.getElementById("preview");
navigator.getUserMedia(mediaOptions, function (stream) {
    video.src = URL.createObjectURL(stream);
}, errorHandler);

mediaOptions

Constraints on what media types you want to return from the user.

var mediaOptions = {
    video: true,
    audio: true
};

If you just want an audio chat, remove the video key.

errorHandler

Executed if there is an error returning the requested media.

Media Events and Methods

addStream

Add the stream from getUserMedia to the PeerConnection.

pc.addStream(stream);

onaddstream

Executed when the connection has been setup and the other peer has added the stream to the peer connection with addStream. You need another <video> tag to display the other peer’s media.

<video id="otherPeer" autoplay></video>
 
var otherPeer = document.getElementById("otherPeer");
pc.onaddstream = function (e) {
    otherPeer.src = URL.createObjectURL(e.stream);
};

The first argument is an event object with the other peer’s media stream.

View the Source already

You can see the source built up from all the snippets in this article at my WebRTC repo.

About Louis Stowasser

I am a Partner Engineer for Mozilla, maintainer of Gamedev Weekly and creator of the CraftyJS game engine, based in Brisbane Australia.

More articles by Louis Stowasser…


14 comments

  1. Jonathan

    Awesome. That’s one of the best practical guide have seen about WebRTC. I wish I had that when I started using it, specially this one: “interoperability between Chrome and Firefox is not possible with DataChannels”.

    July 31st, 2013 at 17:50

    1. bardu

      I agree with Jonathan.

      August 1st, 2013 at 09:09

  2. Fabian Gort

    Nice and clear article, thanks! Does anyone know if and when Chrome and Firefox are going to use the same protocol for reliable datachannels? It sounds a bit like they are taking a different path.

    August 2nd, 2013 at 03:54

    1. Randell Jesup

      Chrome Canary should be supporting the same base protocol (draft-ietf-rtcweb-data-protocol) as we are, any day now. It may take a little longer before FirefoxChrome DataChannels work seamlessly, but since they have our implementation to test against, I doubt it will take long.

      August 2nd, 2013 at 10:20

  3. Fabian Gort

    Ah, that sounds exciting! Thanks for your answer.

    August 2nd, 2013 at 14:26

  4. Binyamin

    http://louisstow.github.io/WebRTC/datachannels.html does not works on Chrome, neither on Firefox.

    August 7th, 2013 at 00:10

    1. Randell Jesup

      I just tried it, both in 26 26, and 23 26, and it worked.

      Could detail out the versions you’re using, and the sequence you go through to try it?

      August 7th, 2013 at 05:43

    2. Louis Stowasser

      It should work between two instances of either Chrome Canary or Firefox Nightly. Chrome and Firefox won’t work.

      August 11th, 2013 at 16:06

  5. Nathan Gordon

    Really nice article! The layout made it so easy to follow.

    Would it be simple to allow extra peers to connect using this structure?

    August 7th, 2013 at 16:42

    1. Louis Stowasser

      I believe you would need to create another data channel to include a new peer.

      August 11th, 2013 at 16:07

  6. Sam Dutton

    Excellent article, really clear writing in an easily digestible format.

    Keep ’em coming!

    August 8th, 2013 at 03:17

  7. Benjamin Slater

    I’ve been working on a video chat feature for my website for a couple weeks now and I have to say that this is by far the best article describing how webRTC works.

    I was testing the source code (media.html) and it works great in different tabs (or windows) on the same computer, but when I tried testing it between 2 computers it only shows the localVideo streaming. I’ve been experimenting with it and the blob url data for the remote stream is shared but the remoteVideo does not play. Have you encounter a similar issue when building the demo?

    August 19th, 2013 at 14:16

  8. nicole rivers

    Hi. You can check out this blog post. It might give you some insights.
    http://www.ideyatech.com/2013/06/how-will-webrtc-transform-the-internet/

    August 22nd, 2013 at 00:00

  9. Jan Honza Odvarko

    Are there any Firefox APIs (hooks, callbacks, etc.) that would allow dev tools like Firebug to monitor the communication just like it does for HTTP requests?

    August 28th, 2013 at 07:33

Comments are closed for this article.