The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Request camera and microphone | const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) | View examples |
| Prefer a video size | video: { width: { ideal: 1280 }, height: { ideal: 720 } } | View examples |
| Require a facing mode | await track.applyConstraints({ facingMode: { exact: 'environment' } }) | View examples |
| Inspect active settings | const settings = track.getSettings() | View examples |
| Detect constraint names | const supported = navigator.mediaDevices.getSupportedConstraints() | View examples |
| List media devices | const devices = await navigator.mediaDevices.enumerateDevices() | View examples |
| Watch device changes | navigator.mediaDevices.addEventListener('devicechange', refreshDevices) | View examples |
| Prompt for a display surface | const display = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true }) | View examples |
| Delegate display capture | <iframe
src="https://tools.example"
allow="display-capture">
</iframe> | View examples |
| Mute outgoing media | track.enabled = false | View examples |
| Release a source | stream.getTracks().forEach(track => track.stop()) | View examples |
| Handle external termination | track.addEventListener('ended', handleShareEnded, { once: true }) | View examples |
| Record supported media | const recorder = new MediaRecorder(stream, { mimeType }) | View examples |
| Preview captured media | video.srcObject = stream; await video.play() | View examples |
Media capture exposes sensitive camera, microphone, and display content as MediaStream tracks. Requests require a secure context and a fully active document, and display capture requires a fresh user choice for every request. Ask only in response to an understandable user action, tolerate missing capabilities, show what is live, and stop tracks as soon as the feature ends.
Step by step
Detailed examples
Request the least media needed after clear intent
getUserMedia() is available only in secure contexts and requires at least one requested media type. Permission can be denied, ignored indefinitely, restricted by Permissions Policy, or unavailable because another application owns the device. Start from a click, explain why each device is needed, keep a cancelable UI even though the API itself has no signal parameter, and avoid retry loops that repeatedly prompt. Use ideal constraints for preferences and exact/min/max only for hard requirements.
async function startPreview() {
if (!isSecureContext || !navigator.mediaDevices?.getUserMedia) throw new Error('Capture unavailable');
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: { ideal: 1280 }, height: { ideal: 720 } },
audio: { echoCancellation: true },
});
preview.srcObject = stream;
await preview.play();
return stream;
} Negotiate capabilities instead of assuming hardware
Constraints select and configure a source; they do not guarantee a physical property that the user agent cannot verify. getCapabilities() reports a track's ranges and enumerations, getSettings() reports the chosen result, and applyConstraints() changes a live track. An unsatisfiable required constraint rejects with OverconstrainedError and may reveal capability information before permission, so do not probe devices for fingerprinting. Retain the previous configuration when a change fails and expose a usable fallback.
async function preferHd(track) {
const { width } = track.getCapabilities();
if (!width) return track.getSettings();
try {
await track.applyConstraints({ width: { ideal: Math.min(1280, width.max) } });
} catch (error) {
if (error.name !== 'OverconstrainedError') throw error;
}
return track.getSettings();
} Treat device identifiers and labels as permission-scoped data
enumerateDevices() returns only devices available to the document and ordered with defaults first. Labels and nondefault devices may be withheld until capture permission exists. deviceId values are origin-scoped and can change when permission or browsing data changes; never use them as durable user identity. Store a preference only as a hint, recover when it disappears, and unregister devicechange listeners during teardown.
async function listCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === 'videoinput').map(device => ({
value: device.deviceId,
label: device.label || 'Camera',
}));
} Let the user choose every shared display surface
getDisplayMedia() requires transient user activation and the browser must offer the user a choice each time; display permission cannot be persisted as granted. Options such as displaySurface and preferCurrentTab are hints and must not silently restrict the chooser. Video is mandatory, while requested audio may be omitted. The display-capture Permissions Policy defaults to self. Avoid monitor capture when a tab or window is sufficient, warn about recursive capture and confidential content, and clearly identify the live surface.
async function shareScreen() {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { displaySurface: 'window' },
audio: true,
});
const [videoTrack] = stream.getVideoTracks();
videoTrack.addEventListener('ended', () => endShare(stream), { once: true });
return stream;
} Distinguish muting from ending and release devices promptly
Setting enabled to false intentionally emits silence or black frames but keeps the track and often the hardware source alive. The muted property instead reflects that the source temporarily cannot provide data. stop() permanently sets readyState to ended; calling it does not fire the track's ended event. Clone tracks share a source, so the device may remain active until all dependent tracks stop. Track ownership explicitly, stop abandoned acquisitions, clear element srcObject values, and update UI when the browser ends a share.
function endShare(stream) {
for (const track of stream.getTracks()) track.stop();
if (preview.srcObject === stream) preview.srcObject = null;
shareButton.disabled = false;
liveIndicator.hidden = true;
}
window.addEventListener('pagehide', () => currentStream && endShare(currentStream), { once: true }); Record in bounded chunks and handle the final data event
MediaRecorder support and container/codec combinations vary. Test isTypeSupported(), omit mimeType when no preferred value is supported, and treat the recorder's mimeType as authoritative. dataavailable chunks may arrive after requestData(), timeslice intervals are approximate, and a final dataavailable precedes stop. Do not hold unbounded recordings in memory: stream chunks to durable storage or a server with size limits. Recording does not grant additional capture permissions and must stop when tracks or the session end.
const preferred = 'video/webm;codecs=vp8,opus';
const options = MediaRecorder.isTypeSupported(preferred) ? { mimeType: preferred } : {};
const chunks = [];
const recorder = new MediaRecorder(stream, options);
recorder.addEventListener('dataavailable', event => { if (event.data.size) chunks.push(event.data); });
const complete = new Promise(resolve => recorder.addEventListener('stop', () => resolve(new Blob(chunks, { type: recorder.mimeType })), { once: true }));
recorder.start(5000); Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



