Cross-browser camera capture with getUserMedia/WebRTC

Overview

With Firefox adding support for getUserMedia, three of the major desktop browsers now have the ability to get data from cameras without the use of plugins. As it’s still early days, however, the implementations differ slightly between browsers. Below is an example of how to work around these differences and a script to do the heavy lifting for you, but first, an overview of how the three browsers stack up.

Comparison of getUserMedia behaviour in browsers as of February 2013
Firefox 18 Opera 12 Chrome 24
Requires vendor prefix Yes (moz) No Yes (webkit)
Triggered with autoplay attribute No Yes Yes
Requires enabling by user Yes 1 No No
Firing of playing event Repeatedly Once Once
Supports file:// protocol Yes Yes No
Tab playing notification None Icon Animated icon
Permission request Each page load First page load only Each page load

getUserMedia has to be enabled in Firefox by setting the media.peerconnection.enabled option to true in about:config.

There are a few more differences once we start coding so let’s walk through it. Our recipe for getUserMedia success will be broken down into the following easy steps:

  1. A helping of HTML5
  2. A dollop of feature detection
  3. A spoonful of streaming
  4. Ready for serving
  5. A final tip

Deep breath – here we go…

A helping of HTML5

Our main task in this short tutorial is just to get a moving image displaying in a page. In that respect, it’s no different to regular video so the first step is a simple <video> element in our HTML:

<video></video>

That’s it. No controls, no src, no nothing.

Over to the JavaScript, and obviously we need to get a reference to the <video> element, which we can do like so (or alternatively with an id):

var video = document.querySelector('video');

A dollop of feature detection

Now it gets interesting as we check for getUserMedia support. We’re definitely not going to use unreliable user agent sniffing for this — no, we’ll do it the easy way by checking for the navigator.getUserMedia object. This is prefixed in Firefox and Chrome so first it’s handy to assign it to a common object for all browsers. While we’re at it, let’s do it for the window.URL object as well which we’ll use later on.

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;

And next, the actual existence checking:

if (navigator.getUserMedia) {
    // Call the getUserMedia method here
} else {
    console.log('Native device media streaming (getUserMedia) not supported in this browser.');
    // Display a friendly "sorry" message to the user.
}

If getUserMedia is supported, we need to pass it three arguments — an options object, a success callback function and an error callback function. Note that the error callback is required in Firefox but optional in Opera and Chrome. The options argument is a JSON-style object that specifies whether audio, video or both are to be used. The following example code is for video only:

navigator.getUserMedia({video: true}, successCallback, errorCallback);

Dialogs requesting camera access

Dialog in Firefox
Dialog in Google Chrome
Dialog in Opera

A spoonful of streaming

So far so good, so let’s define what happens next. The success callback function receives an argument containing the video stream from the camera and we want to send that stream to our <video> element. We do this by setting its src attribute but there are a couple of things to bear in mind:

  • Firefox uses the mozSrcObject attribute whereas Opera and Chrome use src.
  • Chrome uses the createObjectURL method whereas Firefox and Opera send the stream directly.

With Firefox, video.mozSrcObject is initially null rather than undefined so we can rely on this to detect for Firefox’s support (hat tip to Florent). Once the stream knows where to go we can tell the video stream to play.

function successCallback(stream) {
    if (video.mozSrcObject !== undefined) {
        video.mozSrcObject = stream;
    } else {
        video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
    };
    video.play();
}

Ready for serving

And there you have it. Add a simple error callback function and we have a working cross-browser script which looks something like this:

window.addEventListener('DOMContentLoaded', function() {
    'use strict';
    var video = document.querySelector('video');
 
    function successCallback(stream) {
        // Set the source of the video element with the stream from the camera
        if (video.mozSrcObject !== undefined) {
            video.mozSrcObject = stream;
        } else {
            video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
        }
        video.play();
    }
 
    function errorCallback(error) {
        console.error('An error occurred: [CODE ' + error.code + ']');
        // Display a friendly "sorry" message to the user
    }
 
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
 
    // Call the getUserMedia method with our callback functions
    if (navigator.getUserMedia) {
        navigator.getUserMedia({video: true}, successCallback, errorCallback);
    } else {
        console.log('Native web camera streaming (getUserMedia) not supported in this browser.');
        // Display a friendly "sorry" message to the user
    }
}, false);

Available on GitHub

To get started with accessing getUserMedia in a cross web browser fashion, we have also put a working example on GitHub: GumWrapper.

A final tip

If you want to do anything fancy with the camera’s stream like capture a still image or add fancy effects, you’ll probably want to send its data to a canvas context. You can use drawImage() for this, in which case you’ll need the dimensions of the video. These are available through the video.videoWidth and video.videoHeight properties but beware — they’re only set when the browser has information about the stream. This means you have to listen for certain events before you can get these properties. There are a few relevant events, always fired in the following order:

  1. play
  2. loadedmetadata
  3. loadeddata
  4. playing

The play event is fired after the video.play() method is called but there may be a slight delay before the video actually starts playing. That’s where the playing event comes in but note that it’s fired repeatedly in Firefox while the stream or video is playing. Before that are a couple of data events, the first of which is just for metadata, however in Firefox this doesn’t include video dimensions. Consequently, the most reliable event to listen for is the loadeddata event — you can then be sure of knowing the width and height of the video stream. You could code it up like this:

video.addEventListener('loadeddata', function() {
console.log('Video dimensions: ' + video.videoWidth + ' x ' + video.videoHeight);
}, false);

Incidentally, you could also use the stream’s dimensions as a further error check, for example checking whether the width and height are above 0. This would avoid problems such as the user’s webcam being broken or simply not plugged in.

And there you have it. I’m sure the differences between browsers will disappear as the technology matures but for the time being, the above code should help you on your way.

About Daniel Davis

@ourmaninjapan Daniel's work experience includes developer evangelism at Opera Software and web development, IT training and project management in both the UK and Japan. Currently a staff member of the Japanese "html5j" developer community with a soft spot for ukuleles.

More articles by Daniel Davis…

About Robert Nyman [Editor emeritus]

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

More articles by Robert Nyman [Editor emeritus]…


25 comments

  1. Arindam Chakraborty

    That is awesome. Certainly going to give it a spin this weekend. I’m a little unclear of the use-case. Am I right in thinking, its to allow code to have access to the camera and create a hang-out kinda thing in-browser, w/o plugin etc?

    February 13th, 2013 at 08:12

    1. Robert Nyman [Editor]

      Basically, anything you would want to do with video in the web browser, without the requirement of a plugin!

      February 13th, 2013 at 09:05

  2. Brett Zamir

    Slick…

    February 13th, 2013 at 19:49

  3. VN

    The code runs smooth on chrome and opera though theres nothing on safari and moz.

    February 14th, 2013 at 11:56

    1. Robert Nyman [Editor]

      Did you try Firefox Nightly and enabling the preference, as described in the article?

      February 15th, 2013 at 05:11

    2. Daniel Davis

      At the time of writing Safari doesn’t have support for getUserMedia yet (see http://caniuse.com/#feat=stream for current browser support).

      As for Firefox, the flag in about:config depends on the version you’re using. If it’s a Firefox 17 nightly then you need to create a preference called “media.navigator.enabled” and set it to “true”. If you’re using Firefox 18 stable then you need to set the “media.peerconnection.enabled” preference to “true”.

      February 15th, 2013 at 07:01

  4. Florent F.

    Nice! Thanks for the article.

    > With Firefox, we can’t check for the existence of video.mozSrcObject because it’s undefined until it receives a stream
    Actually, `video.mozSrcObject` property is null (not undefined) the first time you call successCallback. So the code below should work (I’ve not tested though):

    function successCallback(stream) {
    if (video.mozSrcObject !== undefined) {

    Florent

    February 14th, 2013 at 13:16

  5. Daniel Davis

    Good suggestion, Florent, thank you. We’ve incorporated it into the article.

    February 15th, 2013 at 07:43

  6. udev

    in Mozilla 18.0.2 getUserMedia works setting either media.navigator.enabled or media.peerconnection.enabled to true, however the second flag is probably meant for activating peer to peer connections, isn’t it?

    February 17th, 2013 at 00:57

    1. Robert Nyman [Editor]

      Yes, exactly.

      February 18th, 2013 at 03:00

  7. Craig

    Recognizing a barcode image in the canvas with javascript:

    http://badassjs.com/post/654334959/barcode-scanning-in-javascript

    February 18th, 2013 at 16:58

    1. Robert Nyman [Editor]

      That’s very nice!

      February 19th, 2013 at 01:44

  8. jamie McDonnell

    Great article, thanks guys!

    @Daniel / Robert,
    I am currently working on a funded project that uses getUserMedia / PeerConnection quite intensively. We are having trouble getting cross browser peer to peer video connection working, and would appreciate some of your time in helping us to achieve this.

    Please PM or email me if you have the time to assist us in this endeavour!

    Many thanks indeed.

    Jamie

    February 21st, 2013 at 03:43

    1. Robert Nyman [Editor]

      Thanks, glad you liked it!
      I think the best bet is to join and send an e-mail to the dev-media mailing list and see if they can assist you.

      Good luck!

      February 21st, 2013 at 04:11

      1. jamie McDonnell

        Thanks Robert, much appreciated.
        Regards
        Jamie

        February 21st, 2013 at 07:25

        1. jamie McDonnell

          Looks like the list is down at the moment though ;( Will try back later. In the mean time, any idea at all how long it is going to take for PeerConnection implementation between FireFox and Chrome to be standardised? It still seems pretty buggy.
          Cheers
          Jamie

          February 21st, 2013 at 07:28

          1. Robert Nyman [Editor]

            List still down for you? I can access it.
            When it comes to Firefox and Google Chrome connection, I recommend checking out our Hello Chrome, it’s Firefox calling! article.

            In there, there’s a link to the code and more for that demo.

            February 21st, 2013 at 13:28

  9. Merih Akar

    Does getUserMedia work on Firefox for Android? With Firefox for Android updated to version 19, Firefox for Android Beta updated to version 20 and Firefox for Android Aurora updated to 21, I could not manage to make it work on any of them. I checked about:config and even if media.navigator.enabled is set to true, it does not work.

    Here is a demo page I’ve created for qrcode decoding with getUserMedia (with the help of the code here: https://github.com/LazarSoft/jsqrcode):

    http://www.ceng.metu.edu.tr/~e1559848/demos/qrdecode/index.html

    For now it seems to work with Firefox 18+, Chrome 21+, Opera 12+ as you mentioned and Opera Mobile 12+. For mobile browsers I added a fallback with file input with accept attributes, but I think decoding with realtime visual aid is more user friendly.

    February 26th, 2013 at 08:57

    1. Robert Nyman [Editor]

      WebRTC in Firefox for Android is a work in progress, but it’s not there yet.

      February 26th, 2013 at 16:47

      1. Merih Akar

        Looking forward to it :) Are there any bugzilla entries about WebRTC in Firefox for Android so that we can follow the progress?

        February 26th, 2013 at 18:04

        1. Robert Nyman [Editor]

          We’re currently in Barcelona where we are showing the first test examples of it. There is a bug as well to follow the progress.

          February 26th, 2013 at 18:09

  10. Hasit Mistry

    When I click on ‘Take Photo’, the photo doesn’t show. Can you help me with this? I’m curious. I am using Firefox 20.

    April 4th, 2013 at 11:58

  11. Robert Nyman [Editor]

    Where do you see Take Photo?

    April 5th, 2013 at 01:28

  12. guska076

    Example doesn’t work on firefox 20 mobile (on android phone). Is this normal?

    April 10th, 2013 at 06:14

    1. Robert Nyman [Editor]

      Yes. WebRTC/getUserMedia isn’t supported on mobile yet.

      April 10th, 2013 at 07:49

Comments are closed for this article.