Making connections with WebRTC

Patrick Van Stee's Headshot
Patrick Van Stee

Until recently, browsers really only supported stateless communication over HTTP. That's beginning to change with new technologies like WebSockets and ServerSentEvents, but WebRTC throws a new feature into the mix: peer-to-peer communication. You can stream data directly between two browsers without having to connect through a server. Now backed by Google and Mozilla (Microsoft supports part of the spec), the project is gaining a lot of traction.

Let's take a look at some of the features and how to use them. The following examples use the webkit prefix for simplicity, but in most cases, switching to the moz prefix will work for Mozilla browsers.

Media Streams

By calling getUserMedia() the browser can ask for access (with approval from the user) to video and audio devices. Each device is returned as a MediaStream object, which acts just like a Blob from the FileReader API. We can then use a video tag and the return value of createObjectURL() as the source to display the live feed from our video camera and microphone. Here's a tiny demo and a code snippet to emphasize how easy it is to use.

function onUserMediaSuccess(stream) {
  video.src = URL.createObjectURL(stream)
}

function onUserMediaError(stream) {
  console.log('Oops something went wrong!')
}

navigator.webkitGetUserMedia(
  { audio: true, video: true },
  onUserMediaSuccess,
  onUserMediaError
)

Peer Connections

Connecting to other computers can be a little tricky, especially if both clients are behind a router. To make this easier, ICE support is baked right in. Each browser can ask for a list of publicly accessible addresses ("candidates" is the domain term) which is then sent up to the server. When the other client receives the list from the server, they can then figure out which address works the best for opening a connection.

The connection process is pretty simple. Each client makes an offer to connect, which can include constraints and options like requiring a video stream. Then they will have a chance to accept or reject the offer. Once the connection is opened you can apply a stream (video and/or audio) which will forward any data received over the wire to the other browser. This keeps the API relatively simple, while still allowing the realtime communication that we need.

Since there are a few steps involved, let's take a look at how we would implement this:

This example leaves out the communication with the server via ServerSentEvents (server.send() and server.onmessage()). A working implementation is up on GitHub if you're interested.

connection = nil
server = new ServerConnection()

// Handle messages received from the server
server.onmessage = function(message) {
  message = JSON.parse(message)

   switch(message.type) {
    // Respond to an offer
    case 'offer':
      connection.setRemoteDescription(new RTCSessionDescription(message))
      connection.createAnswer(function(sessionDescription) {
        connection.setLocalDescription(sessionDescription)
        server.send(sessionDescription)
      })
      break
    // Respond to an answer
    case 'answer':
      connection.setRemoteDescription(new RTCSessionDescription(message))
      break
    // Respond to an ice candidate
    case 'candidate':
      connection.addIceCandidate(new RTCIceCandidate({
        sdpMLineIndex: message.label,
        candidate: message.candidate
      }))
      break
  }
}

// Create the connection object
connection = new webkitRTCPeerConnection({
  iceServers: [{ url: 'stun:stun.l.google.com:19302' }]
})

// Try out new ice candidates
connection.onicecandidate = function(event) {
  if(event.candidate) {
    server.send({
      type: 'candidate',
      label: event.candidate.spdMLineIndex,
      id: event.candidate.sdpMid,
      candidate: event.candidate.candidate
    })
  }
}

// Wire up the video stream once we get it
connection.onaddstream = function(event) {
  video.src = URL.createObjectURL(event.stream)
}

// Kick off the negotiation with an offer request
function openConnection() {
  connection.createOffer(function(sessionDescription) {
    connection.setLocalDescription(sessionDescription)
    server.send(sessionDescription)
  })
}

function onUserMediaSuccess(stream) {
  connection.addStream(stream)
  openConnection()
}

// Ask for access to the video and audio devices
navigator.webkitGetUserMedia({
  audio: true,
  video: true
}, onUserMediaSuccess, null)

Browser Support

Right now the project is still only available in developer builds of Chrome and Firefox. But since the specification has been at least partially implemented in more than one browser, it won't be long until we see support for this in stable releases. Microsoft has begun implementing WebRTC for Internet Explorer, which is good news. However, there are a few places where they deviated from the W3C spec in favor of their own API. This might not be that big of a deal, since the crazy smart guys at Google added WebRTC support to ChromeFrame. I haven't tried it myself, but supposedly it works in IE6.

There are even more WebRTC features that I haven't even touched on in this post, like encryption, screen sharing and raw data connections. Check out the official specification, Google's sample application, and start writing some apps of your own!

Recent Comments

comments powered by Disqus