WebRTC Cheatsheet
getUserMedia
Use this WebRTC reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
getUserMedia — Basic Usage
// Minimal: both video and audio const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // Video only const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); // Audio only const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // Attach to a <video> element const videoEl = document.getElementById('localVideo'); videoEl.srcObject = stream; videoEl.play(); // autoplay may require muted or user gesture
Constraints — Full Reference
Constraints shape what the browser requests from the hardware. All properties are optional.
Video Constraints
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280, max: 1920 }, height: { ideal: 720, max: 1080 }, frameRate: { ideal: 30, max: 60 }, aspectRatio: { ideal: 16/9 }, facingMode: 'user', // 'user' | 'environment' | 'left' | 'right' deviceId: { exact: 'abc123' }, // force a specific camera resizeMode: 'none', // 'none' | 'crop-and-scale' }, });
Audio Constraints
const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: 'mic-device-id' }, sampleRate: { ideal: 48000 }, sampleSize: { ideal: 16 }, channelCount: { ideal: 1 }, // mono echoCancellation: true, noiseSuppression: true, autoGainControl: true, latency: { ideal: 0.01 }, // seconds volume: { ideal: 1.0 }, // 0.0–1.0 (deprecated in some browsers) }, });
Constraint Syntax: exact vs ideal vs range
| Form | Meaning |
|---|---|
{ ideal: 1280 } | Prefer this; degrade gracefully |
{ exact: 1280 } | Must match or OverconstrainedError |
{ min: 640, max: 1920 } | Acceptable range |
{ min: 640, ideal: 1280, max: 1920 } | Range with preference |
true / false | Simple boolean request |
Constraint Property Table
| Property | Type | Notes |
|---|---|---|
deviceId | string | Use exact to force a device |
groupId | string | Selects all devices in a physical group |
width | ConstrainULong | Pixels |
height | ConstrainULong | Pixels |
frameRate | ConstrainDouble | fps |
aspectRatio | ConstrainDouble | w/h |
facingMode | string | Front/back camera |
resizeMode | string | How UA may resize |
sampleRate | ConstrainULong | Hz (audio) |
sampleSize | ConstrainULong | bits per sample |
channelCount | ConstrainULong | 1=mono, 2=stereo |
echoCancellation | ConstrainBoolean | AEC |
noiseSuppression | ConstrainBoolean | ANS |
autoGainControl | ConstrainBoolean | AGC |
latency | ConstrainDouble | seconds |
Enumerating Devices
// List all media devices const devices = await navigator.mediaDevices.enumerateDevices(); const cameras = devices.filter(d => d.kind === 'videoinput'); const mics = devices.filter(d => d.kind === 'audioinput'); const outputs = devices.filter(d => d.kind === 'audiooutput'); // Device object shape: // { deviceId: string, kind: string, label: string, groupId: string } // Labels are empty string until permission is granted — call getUserMedia first
Switching Devices at Runtime
// Replace video track without renegotiating the peer connection async function switchCamera(deviceId, peerConnection) { const newStream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } }, }); const [newTrack] = newStream.getVideoTracks(); const sender = peerConnection.getSenders().find(s => s.track?.kind === 'video'); await sender.replaceTrack(newTrack); // no renegotiation needed // Stop the old track to release the camera videoEl.srcObject.getVideoTracks().forEach(t => t.stop()); videoEl.srcObject = newStream; }
Applying Constraints After Capture
const [track] = stream.getVideoTracks(); // Read current settings const settings = track.getSettings(); // { width: 1280, height: 720, frameRate: 30, deviceId: '…', … } // Read browser-applied capabilities const caps = track.getCapabilities(); // { width: { min: 1, max: 4096 }, frameRate: { min: 1, max: 60 }, … } // Apply new constraints at any time await track.applyConstraints({ frameRate: { max: 15 } });
Handling Permission Errors
try { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); } catch (err) { switch (err.name) { case 'NotAllowedError': // User denied permission, or browser policy blocked it break; case 'NotFoundError': // No camera/mic found break; case 'NotReadableError': // Hardware error — device in use by another app break; case 'OverconstrainedError': console.log('Failed constraint:', err.constraint); break; case 'AbortError': // Some other problem prevented use of the device break; case 'SecurityError': // Not a secure context (requires HTTPS or localhost) break; default: console.error(err); } }
Stopping / Releasing Hardware
// Stop all tracks — releases camera/mic indicator light stream.getTracks().forEach(track => track.stop()); // Stop only video stream.getVideoTracks().forEach(t => t.stop()); // Stop only audio stream.getAudioTracks().forEach(t => t.stop()); // Mute without stopping (track stays alive, sends silence/black) stream.getAudioTracks().forEach(t => (t.enabled = false));
Checking Permission State
const camStatus = await navigator.permissions.query({ name: 'camera' }); const micStatus = await navigator.permissions.query({ name: 'microphone' }); // camStatus.state: 'granted' | 'denied' | 'prompt' camStatus.onchange = () => { console.log('Camera permission changed to:', camStatus.state); };
Device Change Listener
navigator.mediaDevices.addEventListener('devicechange', async () => { const devices = await navigator.mediaDevices.enumerateDevices(); // Rebuild your device picker UI updateDeviceList(devices); });
Common Gotchas
labelis empty until permission granted — callgetUserMediafirst, thenenumerateDevices.- Mobile
facingMode— use{ facingMode: { exact: 'environment' } }to force rear camera; withoutexactit may fall back to front. - Multiple
getUserMediacalls — each returns an independent stream with new tracks; prior tracks remain open unless youstop()them. srcObjectnotsrc— assigning aMediaStreamtovideoEl.srcdoes nothing; always usevideoEl.srcObject = stream.- Autoplay policy — muted videos autoplay; unmuted require a user gesture. Set
videoEl.muted = truefor the local preview. - Safari audio constraints — Safari ignores most audio processing constraints; test separately.
applyConstraintsis async —awaitit; checkgetSettings()after to confirm what was actually applied.