Chrome vs FireFox: WebRTC Stats API with Twilio Video - Twilio
Power modern communications. Build the next generation of voice and SMS applications.
Start building for free

Chrome vs FireFox: WebRTC Stats API with Twilio Video

Screen Shot 2016-03-07 at 11.52.25 AM

The code in this is currently outdated because our Video SDK was in BETA and has updated since the writing of this blog post. Check out this tutorial instead if you want help using Twilio video.

The WebRTC statistics spec details an API that gives developers access to a ton of statistical information about a WebRTC peer connection. It is currently evolving and is partially implemented in Chrome and FireFox. Neither browser has their stats API implementation up the to full spec yet and they both vary in execution. Code you write for one browser will almost certainly not work in the other browser.

This post will highlight notable differences between the two implementations. We will be writing browser-agnostic code to gather statistics via a peer connection from a Twilio Video conversation.

Getting started with a Twilio Video conversation

In order to use the stats API, we will need to gather statistics on a WebRTC peer connection. Let’s begin by building a basic application with Twilio Video.

Twilio Video makes it super easy to build multi-person video chat applications and takes care of all the complicated WebRTC boilerplate that we would otherwise have to deal with. No dealing with STUN/TURN servers or ICE candidates. This is perfect for quickly demonstrating the getStats API in both browsers.

We are going to make a simple two-way video application. You’ll need a basic web page with one div for each participant of this video conversation. Create and open a file called index.html and add the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebRTC r00lz</title>
</head>
<body>
  <div id="remote"></div>
  <div id="local"></div>
  <script src="https://media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
  <script src="https://media.twiliocdn.com/sdk/js/conversations/v0.13/twilio-conversations.min.js"></script>
  <script src="conversationStats.js"></script>
</body>
</html>

This code includes the client side code for Twilio Video conversations. You’ll also need to write a bit of JavaScript to accept conversation invitations and attach the media associated with each participant to those divs.

Create a file called conversationStats.js and add this code to it:

function handleConversationStats(conversation) {
  console.log('We will deal with this in a little bit.');
}

function onInviteAccepted(conversation) {
  conversation.localMedia.attach('#local');

  conversation.on('participantConnected', function(participant) {
    participant.media.attach('#remote');

    handleConversationStats(conversation);
  });
}

var accessToken = 'GRAB THIS FROM YOUR TWILIO DASHBOARD IN A MINUTE';
var accessManager = Twilio.AccessManager(accessToken);
var client = Twilio.Conversations.Client(accessManager);

// Begin listening for invites to Twilio Video conversations.
client.listen().then(function() {
  client.on('invite', function(invite) {
    invite.accept().then(onInviteAccepted);
  });
});

First we are declaring two functions. One is just a placeholder for code we are about to write to grab stats with the conversation. The other function onInviteAccepted is going to be called when we receive an invite to start a video conversation with someone. It takes a conversation and attaches the participants’ media to their respective elements on the page.

Next we declare the variables that will be needed to use the Video API. The access token will be generated from your Twilio dashboard in a future step. We pass it to an AccessManager which handles the permissions for our Video app. Next a conversations client is instantiated and we listen for invites which we will automatically accept.

In order to run this code you’ll need to create a Twilio account. None of this will cost you anything because Twilio Video is still in public beta.

Next you’ll need to generate an access token to quickly use in your client side code. Usually you would have a web server generate these for you, but in the interest of time we will just use the dev tools on your Twilio Dashboard.

You can use any identity you want. For mine, I’ll use “Sam”. This will generate a JSON web token for you that gives you access to the API.

VideoAccessToken

Copy and paste this access token into your code where appropriate inconversationStats.js.

Now open index.html with your web browser. Scroll down on the dev tools page to place a call to your video application using the identity you made before. You can now see yourself twice on the page!

VideoConversation

Basics of the WebRTC stats API

The WebRTC stats API does not know what a Twilio conversation object is, so we will need a peer connection.

These two lines of code that will allow you to grab a peer connection object from a Twilio Video conversation. We will be using this shortly:

var dialog = conversation._dialogs.values().next().value;
var peerConnection = dialog.session.mediaHandler.peerConnection;

Let’s start by looking at the basic code according to the spec for gathering stats on a peer connection object. Given a PeerConnection object, we can call getStats and pass a function that takes a report object with relevant information.

Open conversationStats.js and replace the code inside handleConversationStats with the following:

  var dialog = conversation._dialogs.values().next().value;
  var peerConnection = dialog.session.mediaHandler.peerConnection;

  var selector = null;
  peerConnection.getStats(selector, function (report) {
    for (var i in report) {
      console.log(report[i].type); 
    }
  }, function logError(error) {
    console.log(error.name + ": " + error.message);
  });

The getStats function called on a peer connection object takes a nullable MediaStreamTrack selector, a success callback and a failure callback in that order. The success callback is passed an RTCStatsReport that contains a number of RTCStats objects of different types. As seen in the above code, you usually have to iterate through all of these stats to find the ones that fit the type you’re looking for such as localcandidate or outboundrtp.

If you run this code in Firefox and start another video conversation, you should see report types being logged to the console. Chrome and Firefox both currently implement this evolving spec differently. Firefox’s version more closely matches right now as Chrome’s current version was implemented before the spec was updated.

Comparing the differences between browsers

The code sample above will currently run in Firefox without problems. For reference I am running version 36.0.4. Here is the same code, but written to work in Chrome:

  var dialog = conversation._dialogs.values().next().value;
  var peerConnection = dialog.session.mediaHandler.peerConnection;

  var selector = null;
  peerConnection.getStats(function(response) {
    response.result().forEach(function(report) {
      console.log(report.type);
    });
  }, selector, function logError(error) {
    console.log(error.name + ": " + error.message);
  });

Notice that the first two arguments passed to the getStats function are reversed. This is one of the more obvious differences that would cause your code to not work.

The other main difference is that the report object passed to the success callback is different, which changes the way you interact with the data you receive. In Firefox, the report object matches the spec. In Chrome, the success callback receives a RTCStatsResponse object which has a result method that returns an array of RTCStatsReport objects. These are not the same RTCStatsReport objects referred to in the spec.

Writing a browser agnostic wrapper

Let’s standardize this report by writing a function that takes the response from getStats, checks which browser specific WebRTC implementation we’re using and then returns a version of the report that matches the spec so that your code doesn’t have to do this every time.

Open conversationStats.js and add the following code to the beginning:

// Firefox implements the getStats spec more closely than Chrome does.
// In Chrome, the object passed to the success cb is structured differently.
function standardizeReport(response) {
  if (navigator.mozGetUserMedia) {
    return response;
  }

  var standardReport = {};
  response.result().forEach(function(report) {
    var standardStats = {
      id: report.id,
      type: report.type
    };
    report.names().forEach(function(name) {
      standardStats[name] = report.stat(name);
    });
    standardReport[standardStats.id] = standardStats;
  });

  return standardReport;
}

Firefox’s implementation will be our the default because it more closely follows the spec for now.

We also need to be mindful of which order the arguments are passed in depending on the browser. Let’s write a wrapper function around getStats that will pass the correct arguments depending on which version we are using.

Hop back over to conversationStats.js and include this code to the beginning:

// This function wraps the getStats API and returns the correct usage
// for whichever browser the developer is using.
function getStats(pc, selector, successCb, failureCb) {
  if (navigator.mozGetUserMedia) {
    // This means we are using Firefox
    // getStats takes args in different order in Chrome and Firefox
    return pc.getStats(selector, function(response) {
      var report = standardizeReport(response);
      successCb(report);
    }, failureCb);
  } else {
    // In Chrome, the first two arguments are reversed.
    return pc.getStats(function(response) {
      var report = standardizeReport(response);
      successCb(report);
    }, selector, failureCb);
  }
}

Instead of calling the getStats function available to a given peer connection, we can pass that peer connection to our own getStats function. Now it will pass the correct arguments regardless of which browser we are using.

Before starting another video call, let’s fix the portion of our code that calls getStats and have it use our new wrapper function instead.

Browser agnostic stats on a peer connection

Now let’s add onto our video code by passing the conversation to a function that will grab its peer connection and call getStats on it. Open conversationStats.js again and replace the entire function handleConversationStats with this code that also adds another function:

// Takes a Twilio Video conversation and retrieves stats on it.
function handleConversationStats(conversation) {
  var dialog = conversation._dialogs.values().next().value;
  var peerConnection = dialog.session.mediaHandler.peerConnection;

  var selector = null;
  setInterval(function() {
    getStats(peerConnection, selector, handleStatsReport, function(error) {
      console.log(error);
    });
  }, 5000);
}

// Success callback passed to getStats()
function handleStatsReport(report) {
  var packets = 0;

  // "outboundrtp" type for firefox and "ssrc" type for Chrome with packetsSent
  // googRtt for chrome or mozRtt for firefox to get round trip time.
  if (navigator.mozGetUserMedia) {
    // User is using firefox
    for (var i in report) {
      var currentReport  = report[i];
      if (currentReport.type == "outboundrtp") {
        packets += parseInt(currentReport.packetsSent, 10);
      }
    }
  } else {
    for (var i in report) {
      var currentReport  = report[i];
      if (currentReport.type == "ssrc" && currentReport.packetsSent) {
        packets += parseInt(currentReport.packetsSent, 10);
      }
    }
  }

  console.log(packets + ' packets were sent so far.');
}

One more thing to note is that even though the report object is now structured correctly, Chrome and Firefox still have different type values for certain stats objects. In this example, notice that for outbound RTP stats objects Firefox uses the type outboundrtp whereas Chrome uses ssrc for both inbound and outbound.

In Chrome, to determine if a stats object was outbound or inbound, you need to check for the existence of keys like packetsSent. This code calls getStats on a Twilio Video conversation every 5 seconds and logs the number of packets sent.

Reload your index.html page in the browser of your choice. Head over to your dev tools and place another call to your application. Keep in mind that the tokens generated by that tool expire after a short period of time so you may have to generate new ones if you get authentication errors.

Now you can open your console and see the stats objects for yourself as well as the number of packets sent every 5 seconds.

Continuing with Twilio Video and WebRTC stats

As you can see, there is a ton of stuff you can do with the WebRTC stats API. I can’t wait to see what becomes available as the spec moves further along and Chrome/Firefox further their implementations. Furthermore, if you want to see ways to deal with other WebRTC spec inconsistencies you can check out this adapter, which some of the code for this post was inspired by.

The code I wrote in this blog post only used the most basic functionality of Twilio Video for the purpose of grabbing a peer connection. If you want to continue with Twilio Video, check out the quickstart we have for getting a realistic application up and running in just a few minutes.

Feel free to reach out if you have any questions or comments or just want to show off what kind of cool stuff you’ve built

Sign up and start building
Not ready yet? Talk to an expert.