Choosing cameras in JavaScript with the mediaDevices API
Tags: javascript, webrtc, twilio
Published: 2018-04-30 10:34:49

---
title: Choosing cameras in JavaScript with the mediaDevices API
published: true
description: Learn how to choose or switch between cameras in JavaScript using the mediaDevices API and media constraints.
tags: javascript, webrtc, twilio
canonical_url: https://www.twilio.com/blog/2018/04/choosing-cameras-javascript-mediadevices-api.html
cover_image: https://thepracticaldev.s3.amazonaws.com/i/midw694rrhtlcg5nza1z.jpg
---

Most smart phones come with a front and back camera, when you're building a video application for mobile you may want to choose or switch between them.

If you're building a chat app you probably want the front camera, but if you're building a camera app then you're more interested in the rear camera. In this post we're going to see how to choose or switch between cameras using the `mediaDevices` API and media constraints.

## What you'll need

To follow along with this post you'll need:

* An iOS or Android device with two cameras to test with, if you have two webcams this will work on your laptop too
* <a href="https://ngrok.com/">ngrok</a> so you can easily access the project from your mobile device (and because I think <a href="https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html">ngrok is awesome</a>)
* The code from <a href="https://github.com/philnash/mediadevices-camera-selection">this GitHub repo</a> to get you started

To get the code, clone the project and checkout the starter project tag.

```bash
git clone https://github.com/philnash/mediadevices-camera-selection.git -b initial-project
cd mediadevices-camera-selection
```

This starter project gives you some HTML and CSS so we can concentrate on the JavaScript. You can open the `index.html` file directly, but I recommend you serve these files with a webserver. I like to use the <a href="https://www.npmjs.com/package/serve">npm module serve</a>. I've included serve in the repo too, to use it first install the dependency with npm and then start the server.

```bash
npm install
npm start
```

Once you are running the server, open up a tunnel to it using ngrok. `serve` hosts the files on port 5000, to tunnel to that port with ngrok enter this on the command line in a new window:

```bash
ngrok http 5000
```

Now you have a publicly available version of the site you can open this on your mobile device so that you can test it later. Make sure you open the HTTPS URL as the APIs we are using only run in a secure context.

<img alt="The ngrok window shows two URLs you can use, pick the HTTPS one." src="https://twilioinc.wpengine.com/wp-content/uploads/2018/04/I_rv7Y2-mmWS5nsXOggIlsOtxhuUr3JAn0eRAtUtB7D-fm1e1nUF-VbUkGlihAhzgRuBJXZVeoeVgKvvXux5rjVx6SMWzMtFk8T3VvYtzdKTRlLYLe3sXnlYzBodFxMyZ2m17dQ.png" class="aligncenter">

The app should look like this:

<img alt="The app should have a title saying 'Camera fun' with a button and an empty drop down box." src="https://twilioinc.wpengine.com/wp-content/uploads/2018/04/h0iHFp3LTpszVoQGdl6tWCLGGZZCPW8xBTfuyrHPGl0idifAcMHRPe0lRIyO0we8fTWDbFed_Omw_7JQp6RCJrCHSzd-KCCPW3W6750qrihsKDReT3hZs7Jk6PI6GkR8AA9LAzQ.png" class="aligncenter">

## Getting the media stream

Our first challenge is getting the video stream from any camera onto the screen. Once that is complete we will investigate the options for selecting the specific camera. Open up `app.js` and start by selecting the button and video elements from the DOM:

```javascript
// app.js
const video = document.getElementById('video');
const button = document.getElementById('button');
```

We'll request access to the camera using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices">`mediaDevices` API</a> when the user clicks or touches the button. To do so, we call <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia">`navigator.mediaDevices.getUserMedia`</a> passing an object of media constraints. We'll start with a simple set of constraints, we only want video, so we'll set video to `true` and audio to `false`.

`getUserMedia` returns a promise, when that resolves we have access to a media stream from the camera. Set the video's `srcObj` to the stream and we will see it on screen.

```javascript
button.addEventListener('click', event => {
  const constraints = {
    video: true,
    audio: false
  };
  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(stream => {
      video.srcObject = stream;
    })
    .catch(error => {
      console.error(error);
    });
});
```

Save the file, reload the page and click the button. You should be presented with a permissions dialog requesting access to your camera, once the permissions are granted your video will appear on screen. Try this on your computer and your phone, when I tried with my iPhone the camera selected was the front facing camera. 

<img alt="The camera app, now with my face in the previously blank space!" src="https://twilioinc.wpengine.com/wp-content/uploads/2018/04/Kda5qsIH1Wasn1kXz1lcn9hHd6p4p1wRaUeVLS_nheQgWnjJ9Y8l6lUvdSrKAwF28oLMNJptZ2u64N_4WmuYn7mp4TZKbk-CC0T5y87AhzCOD2Vv6qgQdBZMajxx2ZyNBwKKYf8.png" class="aligncenter">

If you are using an iPhone, make sure you check in Safari as this doesn't seem to work with other browsers.

## What cameras are available?

The `mediaDevices` API gives us a way to enumerate all the available devices for both audio and video input. We'll use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices">`enumerateDevices`</a> function to build up a set of options for a `<select>` box so we can use it to choose the camera we want to see. Open up `app.js` again and start by selecting the `<select>` from the DOM:

```javascript
const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');
```

`enumerateDevices` returns a promise, so let's write a function we can use to receive the result of the promise. The function will take a list of media devices as an argument.

The first thing to do is empty the `<select>` of any existing options and append one empty `<option>`. Then we loop through the devices, filtering out any that aren't of `kind` "videoinput". We then create an `<option>` using the device's ID as the value and the device's label for the text. We also handle the case where a device doesn't report a label by generating a simple "Camera n" label.

```javascript
const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');

function gotDevices(mediaDevices) {
  select.innerHTML = '';
  select.appendChild(document.createElement('option'));
  let count = 1;
  mediaDevices.forEach(mediaDevice => {
    if (mediaDevice.kind === 'videoinput') {
      const option = document.createElement('option');
      option.value = mediaDevice.deviceId;
      const label = mediaDevice.label || `Camera ${count  }`;
      const textNode = document.createTextNode(label);
      option.appendChild(textNode);
      select.appendChild(option);
    }
  });
}
```

At the end of `app.js` make the call to `enumerateDevices`.

```javascript
navigator.mediaDevices.enumerateDevices().then(gotDevices);
```

Refresh the page and take a look at the drop down select next to the button. If you're on Android, or using Chrome or Firefox, you will see the name of the cameras you have available.

On an iPhone however, you will see the generically named "Camera 1" and "Camera 2" from our function. On iOS you will not get the labels of the cameras until you have granted permission for the site to use at least one of the cameras. This makes our interface less useful for selecting a camera as, even though you do get the ID of the devices, you can't tell which camera is which.

<img alt="On the iPhone you only see the labels we made up, 'Camera 1' and 'Camera 2'." src="https://twilioinc.wpengine.com/wp-content/uploads/2018/04/lkBjDsPZk87_aN3ONBWXs22xmkmQg_plaua_hNfWTFRxQFiieHyB3EXxZwn5zncsEAKKVdONDx3drBnlcYJDU3niHGu1naOEdujZHLWvUE4aRt4Fro-K-Km1diPqUZGM-5KkE.png" class="aligncenter">

We have not yet hooked up the drop down select to change the camera. Before we do, let's look at another way we can influence which camera we want to select.

## Facing mode

An alternative approach that we can use to select a camera is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode">`facingMode`</a> constraint. This is a less exact way of picking a camera than getting its ID from the `enumerateDevices` function, but works really well for mobile devices. There are four options you can use for the constraint: `user`, `environment`, `left` and `right`. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode">constraints are explained in the MDN documentation</a>, for the purposes of this post we're going to use `user` and `environment` as they map nicely to front facing and back facing cameras on a mobile device.

To use the `facingMode` constraint we need to change the `constraints` we are using in our call to `getUserMedia`. Rather than just saying `true` for `video` we need an object of these constraints. Update the code to select the front facing camera like this:

```javascript
button.addEventListener('click', event => {
  const videoConstraints = {
    facingMode: 'user'
  };
  const constraints = {
    video: videoConstraints,
    audio: false
  };
```

Test from your mobile device now. You should find the front facing camera is selected. Update the `facingMode` to `environment` and try again. Now the rear facing camera should be selected.
Let's put this code together with the results we got from `enumerateDevices` above to build a camera switcher once we've got permission to read the camera data.

## Switching cameras

We have the code to pick a user or environment camera on the first selection, but if we want to switch cameras there's a little more work to do.

First up, we should retain a reference to the current stream so that we can stop it when we switch to another one. Add one more variable and a utility function to stop the tracks in a stream to the top of `app.js`. 

```javascript
const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');
let currentStream;

function stopMediaTracks(stream) {
  stream.getTracks().forEach(track => {
    track.stop();
  });
}
```

The function `stopMediaTracks` takes a stream and loops through each media track in the stream, stopping each of them.

We'll change cameras when we press the same button, so we need to update the event listener. First, if we have a `currentStream` then we should stop it. Then we'll check the `<select>` to see if we are choosing a particular device and build up the video constraints based on that.

Update the button's click handler and the video constraints like so:

```javascript
button.addEventListener('click', event => {
  if (typeof currentStream !== 'undefined') {
    stopMediaTracks(currentStream);
  }
  const videoConstraints = {};
  if (select.value === '') {
    videoConstraints.facingMode = 'environment';
  } else {
    videoConstraints.deviceId = { exact: select.value };
  }
  const constraints = {
    video: videoConstraints,
    audio: false
  };
```

When we want to select a device by its deviceId we use the `exact` constraint. We avoid that for the `facingMode` constraint though, as that could fail on a device that doesn't recognise having a "user" or "environment" facing mode, leaving us with no media at all.

Still within the click handler, when we get permission to use the video we are going to change a couple more things. Set the `currentStream` to the new stream passed to the function, so that we can stop it later, and set off another call to `enumerateDevices`.

`enumerateDevices` returns a promise, so we can return it from our `then` function and chain a new `then` for the result which will then be handled by our `gotDevices` function.

Replace your existing call to `getUserMedia` with the following:

```javascript
button.addEventListener('click', event => {
  if (typeof currentStream !== 'undefined') {
    stopMediaTracks(currentStream);
  }
  const videoConstraints = {};
  if (select.value === '') {
    videoConstraints.facingMode = 'environment';
  } else {
    videoConstraints.deviceId = { exact: select.value };
  }
  const constraints = {
    video: videoConstraints,
    audio: false
  };

  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(stream => {
      currentStream = stream;
      video.srcObject = stream;
      return navigator.mediaDevices.enumerateDevices();
    })
    .then(gotDevices)
    .catch(error => {
      console.error(error);
    });
});
```

When you've added all that code, your `app.js` should look like <a href="https://github.com/philnash/mediadevices-camera-selection/blob/master/app.js">this completed one</a>. Refresh the page and you can play about selecting and changing cameras. This works on both mobile and desktop too.

<img alt="The finished result, this is an animation showing that you can select one camera then change and go from viewing the back camera to the front camera." src="https://twilioinc.wpengine.com/wp-content/uploads/2018/04/bKT1LdWTyA_RANLksFX5srORt6RRgCmVDSWoJj8Pzf3OSNkhfAi0_KWssgerqSaV-_Ty9WRKI8p8cQ-xFJd5nPFEyYQ70OS8nTefBpLrle54e3Tm1pKaJQX40BM-zgqLhTK9haI.png" class="aligncenter">

## Next steps

We've seen how to select a user's camera with the `facingMode` or `deviceId` constraint. Remember, `facingMode` is more reliable before you have permission to use the camera, but selecting a `deviceId` is more accurate. You can get <a href="https://github.com/philnash/mediadevices-camera-selection">all the code from this blog post in the GitHub repo</a> and you <a href="https://philnash.github.io/mediadevices-camera-selection/">try out the application live here</a>.

If you are using <a href="https://www.twilio.com/docs/api/video">Twilio Video</a> to build a video application, you can use these constraints when calling either <a href="https://media.twiliocdn.com/sdk/js/video/releases/1.8.0/docs/global.html#connect">`connect`</a> or <a href="https://media.twiliocdn.com/sdk/js/video/releases/1.8.0/docs/global.html#createLocalVideoTrack">`createLocalVideoTrack`</a>.

Selecting or switching cameras is a useful feature for video chat, allowing users to pick the exact camera they want to use within your application's interface, and it could go hand in hand with <a href="https://www.twilio.com/blog/2018/01/screen-sharing-twilio-video.html">sharing your screen during a video call too</a>.

Are there other video features you'd like to see that would be useful in video chats? Or any questions about this feature? Let me know in the comments or on Twitter at <a href="https://twitter.com/philnash">@philnash</a>.

___

_[Choosing cameras in JavaScript with the mediaDevices API](https://www.twilio.com/blog/2018/04/choosing-cameras-javascript-mediadevices-api.html) was originally published on the [Twilio blog](https://www.twilio.com/blog) on April 19, 2018._