What Web Can Do Today?

Can I rely on the Web Platform features to build my app?

An overview of the device integration HTML5 APIs

How to choose between front and back camera stream

Most of the browsers nowadays, including mobile browsers, allow the applications to retrieve and use the data stream coming directly from the user's device camera. But it's very common for the devices nowadays to have more than one camera available and we might have a preference which of these cameras is better suited for our app's needs. Fortunately, the Media Stream API has us covered.

Media Stream API

Let's first remind ourselves how do we access the video stream from the user's camera at all. The API sits in navigator.mediaDevices and is generic in terms of what kind of media stream it serves. We must specify. that we're particularly interested in video stream. This selects the proper device as well as displays the informative permission prompt for the user. If we requested video only, the browser will ask for a camera access. If we also wants audio, the prompt will include both camera and microphone.

Chrome's permissions dialog for video and audio stream
Chrome's permissions dialog for video and audio stream
const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
})

The API is asynchronous – we'll get the stream when the user accepts the prompt and the browser finds the proper devices. If any of these fail, our Promise will get rejected.

In case we successfully acquired the stream, we can start manipulating it. The simplest thing we can do with it is to redirect it to a <video> element. In most cases, the <video> element has its source specified as a static file, but it also can have any stream as its source – including the one we get from the user's device. We need to attach it programatically:

const video = document.querySelector('video');
video.srcObject = stream;
video.play();

Now, our <video> element will transmit the stream from the camera. So if the front camera is in use it will act as a mirror. But how do we ensure the proper one is used?

Selecting facing mode of the video stream

When we were acquiring the stream, we passed an object as a getUserMedia parameter. This object is a stream constraint definition. We only constrained the stream to include video (and audio) so far. But we can apply tighter constraint:

const stream = await navigator.mediaDevices.getUserMedia({
    video: {
        facingMode: 'user'
    }
})

Here, we put an object including facingMode property as a video constraint, instead of simple true that accepted any kind of video input. With constraint, we suggest the browser for what kind of video stream it should look for. In the example, we set it to user-facing camera – a.k.a. selfie camera, or the front one. Alternatively, we can specify we want to face environment, which is what is observed by the back camera.

The constraint set this way only works as a suggestion, though – in case we run this code on a device equipped with back camera only, we'd get the environment-facing stream anyway, as it's the only one available and it's still better to use whatever we have available instead of failing in most scenarios. But if our requirement is strict and we need to use the front camera or no camera, we can set an additional constraint using exact:

const stream = await navigator.mediaDevices.getUserMedia({
    video: {
        facingMode: {
            exact: 'user'
        }
    }
})

Now, if the device only has an environment-facing camera, we'd not get any stream and the Promise will be rejected with OverconstrainedError.

More constraints

Facing mode is not the only constraint specified by the Media Stream API. We may see the full list of constraint options using navigator.mediaDevices.getSupportedConstraints() call. And this list might be quite impressive. This is what Chrome 80 on macOS lists as available:

{
    aspectRatio: true
    autoGainControl: true
    brightness: true
    channelCount: true
    colorTemperature: true
    contrast: true
    deviceId: true
    echoCancellation: true
    exposureCompensation: true
    exposureMode: true
    exposureTime: true
    facingMode: true
    focusDistance: true
    focusMode: true
    frameRate: true
    groupId: true
    height: true
    iso: true
    latency: true
    noiseSuppression: true
    pointsOfInterest: true
    resizeMode: true
    sampleRate: true
    sampleSize: true
    saturation: true
    sharpness: true
    torch: true
    whiteBalanceMode: true
    width: true
    zoom: true
    videoKind: true
    pan: true
    tilt: true
}

Obviously, some of these constraint only make sense for audio and others for video streams.

The ones that might also be frequently used are these related with video resolution:

const stream = await navigator.mediaDevices.getUserMedia({
    video: {
        width: 1280,
        height: 720
    }
})

Note that like with facingMode, by default this is only a suggestion for what kind of stream we are looking for and the browser might as well provide the one with different size. We might again use exact modifier to ask for exactly the size specified, but in case of resolution it's probably wiser to specify the minimum size we accept, using min modifier:

const stream = await navigator.mediaDevices.getUserMedia({
    video: {
        width: {
            min: 1280
        },
        height: {
            min: 720
        }
    }
})

The browser now will not provide a stream if it's not possible to find one with at least 1280x720 size.

Selecting both size and facing mode

As the constraint is an object, nothing stops us from specifying more requirements, possibly with different modifiers. The browser will try to match as many as possible when providing the stream and in case we used exact or min modifiers that can't be satisfied, it will again reject the promise with OverconstrainedError.

const stream = await navigator.mediaDevices.getUserMedia({
    video: {
        facingMode: 'environment',
        width: {
            min: 1280
        },
        height: {
            min: 720
        }
    }
})

In this example, we're asking for the stream that should face the environment (back camera) when possible, but we also accept if only the front camera is available. For size we do not accept the sizes smaller than 1280 in width or 720 in height.

Get in touch