Hardware capture comes to the browser
For years, the only way to tap into a user's camera or microphone from the web was through a browser plugin. HTML5 changed that trajectory: Geolocation, the Orientation API, WebGL, and the Web Audio API each opened a door to device hardware. The getUserMedia() API, now part of the MediaDevices interface, completes the picture by giving web apps a direct path to local audio and video input.
How we got here
The API wasn't always so cleanly defined. A flurry of competing "Media Capture" proposals led the W3C's Device APIs Policy (DAP) Working Group to consolidate the various specs. Several distinct approaches emerged along the way before the current standard took shape.
Round 1: HTML Media Capture
The DAP's first attempt reused the familiar file input element. By overloading accept, developers could let users record media from their devices:
<input type="file" accept="image/*;capture=camera">
Recording video or audio follows the same pattern:
<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">
The obvious benefit is semantic reuse of a control developers already knew. But the approach only handled end-to-end capture — it couldn't stream live webcam data into a <canvas> for real-time processing, which limited its use for effects and filters. Support arrived first in Android 3.0's browser, followed by Chrome for Android, Firefox Mobile 10.0, and partial support in iOS6 Safari and Chrome.
Round 2: The device element
The next proposal took a broader approach, introducing a dedicated element designed to work with any type of device, present or future:
<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
function update(stream) {
document.querySelector('video').src = stream.url;
}
</script>
Opera shipped early implementations based on the <device> element, but the WhatWG scrapped it the same day in favor of a JavaScript API, navigator.getUserMedia(). Opera rebuilt its implementation within a week, and Microsoft later joined with an IE9 Lab supporting the new spec. Despite its promise — it was both semantic and easily extendable — no released browser ever shipped <device>.
Round 3: WebRTC
The broader WebRTC (Web Real Time Communications) effort accelerated the push for a standardized capture mechanism. Oversight moved to the W3C WebRTC Working Group, with implementations coming from Google, Opera, Mozilla, and others. getUserMedia() became the gateway into the WebRTC API set — the way to obtain access to a user's camera and microphone stream. It has been supported in Chrome since version 21, Opera since 18, and Firefox since 17.
Using getUserMedia()
The modern entry point is navigator.mediaDevices.getUserMedia(). Feature detection is a straightforward existence check:
if (navigator.mediaDevices?.getUserMedia) {
// Good to go!
} else {
alert("navigator.mediaDevices.getUserMedia() is not supported");
}
Requesting input
The first parameter is an object describing what media you want. For the camera alone, pass {video: true}; for both camera and microphone:
<video autoplay></video>
<script>
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((localMediaStream) => {
const video = document.querySelector("video");
video.srcObject = localMediaStream;
})
.catch((error) => {
console.log("Rejected!", error);
});
</script>
The returned stream plugs into existing HTML5 media elements. Instead of setting a src attribute on a <video> element, you assign the stream object to srcObject. Set autoplay on the element to keep the video feed live rather than frozen on the first frame; adding controls works as expected.
Setting constraints
The same first parameter can enforce requirements on the returned stream. Beyond basic access, you can demand specific resolution:
const hdConstraints = {
video: { width: { exact: 1280} , height: { exact: 720 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
Or define explicit width and height requirements:
const vgaConstraints = {
video: { width: { exact: 640} , height: { exact: 360 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
The full constraints API covers additional configuration options, including aspect ratio and frame rate.
Selecting a media source
When a device produces multiple potential inputs, MediaDevices.enumerateDevices() lists available media input and output devices — cameras, microphones, headsets, and more. The method returns a Promise that resolves to an array of MediaDeviceInfo objects. The example below selects the last microphone and camera found as the stream's sources:
if (!navigator.mediaDevices?.enumerateDevices) {
console.log("enumerateDevices() not supported.");
} else {
// List cameras and microphones.
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
let audioSource = null;
let videoSource = null;
devices.forEach((device) => {
if (device.kind === "audioinput") {
audioSource = device.deviceId;
} else if (device.kind === "videoinput") {
videoSource = device.deviceId;
}
});
sourceSelected(audioSource, videoSource);
})
.catch((err) => {
console.error(`${err.name}: ${err.message}`);
});
}
async function sourceSelected(audioSource, videoSource) {
const constraints = {
audio: { deviceId: audioSource },
video: { deviceId: videoSource },
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
}
Security and fallbacks
Browsers are required to prompt users for permission when navigator.mediaDevices.getUserMedia() is called:
If the API is unsupported or permission is denied, a reasonable pattern is to fall back to a stored media file:
if (!navigator.mediaDevices?.getUserMedia) {
video.src = "fallbackvideo.webm";
} else {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
}
That approach keeps the page functional for users whose browsers can't access the hardware or who decline the prompt.



