
Getting Started with getUserMedia In 2025
One of the features enabling today's modern web is getUserMedia()
, a powerful JavaScript method that allows developers to access the user's camera and microphone directly from the browser.
The method has been implemented in browsers since 2012, and over the last 13 years, it has evolved significantly. The method is now core to WebRTC video conferencing, live streaming, taking photos, grabbing video frames, augmented reality, and audio and video recording via powerful services like the Pipe Recording Platform.
This article will cover what you need to know, as a developer, before and after calling getUserMedia()
.
Table of Contents
- Before The Call: Secure Contexts and Permissions
- Old variant: navigator.getUserMedia()
- Getting a Media Stream
- Getting a List of Available Devices
- Listening for Device Changes
- Working with Media Streams
- Video and Audio Codecs
- Understanding the Constraints Object
- Handling getUserMedia Errors
- Browser Compatibility
Before The Call: Secure Contexts and Permissions
Several gatekeepers safeguard the ability to access a user's devices to protect the user's privacy.
Secure Context Requirement
The first thing you need to know is that the getUserMedia
method is only available in secure contexts. What this means exactly is that your code must be served in one of the following ways:
- Using HTTPS: the page is served via
https://
- Local resources, such as the following origins:
http://localhost
,http://127.0.0.1
, or file URLS (file://
)
Failing to run/host your code in one of the above secure contexts will prevent getUserMedia
from accessing the user's camera or microphone on modern browsers.
Permissions Policy (former Feature Policy)
Chromium browsers (Chrome, Edge, Opera, etc.) implement the Permissions Policy (formerly known as Feature Policy). Such a policy can be used to control which features are enabled in different parts of a website. For example, the policy can be configured to block access to the camera and/or microphone, as a result, interfering with your getUserMedia
calls.
Some environments, like cross origin iframes, will have restrictive policies by default and will block capturing the camera or microphone. In other environments, you might hit a manually imposed permission policy.
To further familiarize yourself with how it works, check out the following materials we've written on the topic:
- Camera and Microphone Access In Cross Origin Iframes With getUserMedia
- Recommended Permissions-Policy Settings
Browser and OS Permissions
To access the user's media devices, the user must grant explicit permission at both the browser and OS levels. These permission prompts will change over time as browsers evolve and mature, so it is essential to keep track of them to ensure this important step is handled carefully and covers both grant and deny cases.
Browser Level Permissions
Every browser has a different permission dialog which behaves a bit differently. Chrome and Firefox permissions are persistent by default at the browser tab/session level. Firefox will ask permission for every individual device and always allowed you to change the shared device directly in the permission prompt. Safari's permissions are the least persistent.
OS Level Permissions
OS level permissions are prominent on macOS, where every non-Safari browser needs to be given individual permission by the user to capture the microphone, camera, and the screen & system sounds and then it needs to be restarted. But Windows 10 and 11 also have controls for preventing apps from accessing the camera and microphone.
Future <permission>
tag
Calling getUserMedia()
is what currently triggers the permission prompt but this might change in the near future with the introduction of the <permission>
HTML tag.
Permissions API
You can use the Permissions API to track the status of permissions in the current context. If you're interested in learning how to detect how often users allow or deny camera and microphone access, you can read this post, and you can further delve into finding out more about getUserMedia
camera and microphone initialization times.
Old Variant: navigator.getUserMedia()
You might encounter very old tutorials, code or documentation that mention the callback based navigator.getUserMedia()
. The functionality has been moved to the promise based navigator.mediaDevices.getUserMedia()
- with Chrome this happened in Chrome 53 - and it's the version you should be using (even tough the older syntax might still be supported).
Getting a Media Stream
Now that you are in a secure context and have navigated through all the permission hoops, you can finally gain access to the user's devices and obtain the prize: the media stream.
To do so, you must call navigator.mediaDevices.getUserMedia()
. This method takes a MediaStreamConstraints
object as a parameter, which allows you to specify the type of media you want to access: audio, video, or both. Optionally, you can add specific granular requirements for both video and audio like the frame rate, resolution or preferred camera (front or back) on mobile devices.
We will cover constraints in a bit more detail later. For now, here is a basic example of how to get the video and audio stream from a webcam:
const constraints = {
video: true,
audio: true
};
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
// You have a media stream, attribute it to the HTML video element
const videoElement = document.querySelector('video');
videoElement.srcObject = stream;
})
.catch(error => {
// Handle errors
console.error('Error accessing media devices.', error);
});
Note that getUserMedia
returns a Promise
. This means you need to use .then()
and .catch()
to handle both success and failure cases.
Handling getUserMedia
Errors
In the code above, there's a catch
statement, which, in a production environment, needs to be extended to gracefully handle a few common situations:
- When no camera or microphone device exists you'll be met with a
NotFoundError
. - The camera device is already in use by another app results in a
NotReadableError
. - When the user has not granted permissions at the browser or OS level will result in a
NotAllowedError
. - Specific constraints cannot be met (exact resolution, aspect ratio or fps, etc.), an
OverconstrainedError
will be thrown.
We go over the getUserMedia
errors in more detail in a separate post.
Even after a successful getUserMedia
call, you might encounter error situations that need to be handled:
- User mutes, disables or pulls out the camera or microphone device
- User's device disconnects from Bluetooth or is out of battery (the iPhone can be used as a camera)
- Computer/laptop is left unattended and it goes to sleep while device is accessed
Our own Pipe recording client handles many of these errors.
Getting a List of Available Devices
It is crucial to give your users the option to choose which camera and microphone they want to grant access to and use. Once permission is given, you can list the available media devices by calling navigator.mediaDevices.enumerateDevices()
. This method will return an array of MediaDeviceInfo
objects, and each of them will contain information about one particular device.
Here is a quick example:
navigator.mediaDevices.enumerateDevices()
.then(devices => {
devices.forEach(device => {
console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
});
})
.catch(error => {
console.error('Error enumerating devices.', error);
});
This apparently simple functionality is important for any app. Allowing users to choose their preferred camera and microphone devices is crucial for ensuring privacy and that the correct device is used.
We've worked hard to build one of the best camera and microphone selectors for our Pipe recording client.
Listening for Device Changes
But what happens if a user plugs in a new microphone or connects a new Bluetooth headset while your application is running or after you've called enumerateDevices()
? This is where the ondevicechange
event handler comes in, which can be attached to the navigator.mediaDevices
object:
navigator.mediaDevices.ondevicechange = event => {
// Handle device change
console.log('A media device has been connected or disconnected.');
// You can then call enumerateDevices() again to get the updated list
};
With it, you can gracefully handle changes in the list of devices.
Working with Media Streams
After getUserMedia
is called successfully, you receive a MediaStream
object that allows you to perform various actions. Here are a few examples:
- Show the stream in an HTML video element: This is the most basic use case, but it is a great way to provide immediate feedback of the user's camera and microphone.
- Send the stream over WebRTC: This implementation will enable real-time peer-to-peer communication with other users, allowing for video and audio calls. Integrations with services like OpenAI's Realtime API are another good example of streaming over WebRTC.
- Record the stream using the MediaStream Recording API to easily create and store video or audio files.
- Process the stream in real time with the Canvas API for advanced visual effects (e.g., background blurring), image recognition, or analytical data gathering.
- Capture a frame using
ImageCapture.grabFrame()
or a photo usingImageCapture.takePhoto()
At Pipe, we've developed a robust raudio and video recording client that does much of the heavy lifting for you, allowing for seamless recording of video, audio, and screen directly from the browser. This way, our clients can focus on implementing their solutions instead of navigating the complexity of accessing the devices, recording, streaming, storage, etc..
Video and Audio Codecs
After capturing the audio and video streams via getUserMedia
method, using those streams in WebRTC or recording them will result in the data being encoded using varying audio and video codecs (and containers).
For example, when recording the stream using the MediaStream Recording API, there's a bunch of codecs and containers that are available for audio and video encoding.
Here's a summary with the maximal audio and video capabilities of current major browsers:
Browser | Container | Video Codecs | Audio Codecs |
---|---|---|---|
Google Chrome | mp4 or webm | VP8, VP9, H.264, HEVC (H.265), AV1 | Opus, PCM |
Mozilla Firefox | webm | VP8 | Opus |
Apple Safari | webm, mp4 | H264, VP8, VP9, HEVC (H.265), AV1 | AAC, Opus |
Keep in mind, the available codecs differ based on the browser, OS and hardware capabilities. On a simple getUserMedia()
implementation you might get mp4 with H.264 and AAC audio from Safari and a webm with H.265 and Opus from Chrome. You will definitely get VP8 video from Firefox.
Our MediaStream Recording API demo has been updated so that you can play with these codecs and test what your current browser/OS/hardware support.
Given the variety of possible codec combinations, in order to ensure maximum compatibility across devices, dedicated video recording platforms like Pipe will transcode the captured stream, server side, to a cross platform compatible .mp4 file.
Understanding the Constraints Object
Remember when we talked earlier about the constraints object (MediaStreamConstraints
actually) that is being passed to getUserMedia
? This object is key to getting the media stream that best fits the needs of your applications.
The most important thing to know about the constraints
object is that it allows you to request certain media characteristics, but these are requests, not commands. The browser will do its best to meet your constraints, but the results may vary depending on the device's hardware capabilities.
The simplest constraints that you can specify are, as seen in the example we gave earlier, { video: true, audio: true }
.
You can also specify detailed requirements for:
- Video constraints: You can request a specific resolution (
width
,height
),framerate
,aspectRatio
orfacingMode
(useful for selecting specific cameras on mobile devices) - Audio constraints: You can control features like
echoCancellation
,noiseSuppression
, andautoGainControl
a - Screen constraints: When it comes to screen capture, the counterpart for
getUserMedia
, isgetDisplayMedia
. Similarly, you can specify constraints for it. We will cover the specifics in an upcoming post.
For a deep dive into each type of constraint, you can check out our detailed articles on video constraints, audio constraints, and our examples page to see some of them in action.
Browser-Level Support: getSupportedConstraints()
There is a helpful function to learn which constraints the browser itself supports, before even attempting to request a certain constraint.
navigator.mediaDevices.getSupportedConstraints()
will return an object containing keys with the name of the constraint and their values, either true
or false
:
const supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
console.log(supportedConstraints);
// { "width": true, "height": true, "aspectRatio": true, ... }
Device-Level Capabilities: getCapabilities()
You can query the capabilities of the active camera or microphone directly. This can only be done once you get hold of the media stream, by calling the function getCapabilities()
on the MediaStreamTrack
.
This method will return a range of values that the device can support for each constraint (e.g, the minimum and maximum video resolution).
// We assume you already have a stream object from getUserMedia()
const videoTrack = stream.getVideoTracks()[0];
const capabilities = videoTrack.getCapabilities();
console.log(capabilities);
// {
// "width": { "min": 1, "max": 1920 },
// "height": { "min": 1, "max": 1080 },
// "frameRate": { "min": 0, "max": 30 },
// ...
// }
Together, these two functions can help you build more robust applications. For example, you can display only the options that the user's hardware actually supports.
Browser Compatibility
The core getUserMedia
functionality has the advantage of being well-established with wide browser support including Chrome, Firefox, Safari, Edge, Brave, Opera, etc. .
Other parts of the getUserMedia
tech might not have as wide a support, so it is always wise to check with services like caniuse.com when making your implementation.