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

FormMeaning
{ 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 / falseSimple boolean request

Constraint Property Table

PropertyTypeNotes
deviceIdstringUse exact to force a device
groupIdstringSelects all devices in a physical group
widthConstrainULongPixels
heightConstrainULongPixels
frameRateConstrainDoublefps
aspectRatioConstrainDoublew/h
facingModestringFront/back camera
resizeModestringHow UA may resize
sampleRateConstrainULongHz (audio)
sampleSizeConstrainULongbits per sample
channelCountConstrainULong1=mono, 2=stereo
echoCancellationConstrainBooleanAEC
noiseSuppressionConstrainBooleanANS
autoGainControlConstrainBooleanAGC
latencyConstrainDoubleseconds

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

  • label is empty until permission granted — call getUserMedia first, then enumerateDevices.
  • Mobile facingMode — use { facingMode: { exact: 'environment' } } to force rear camera; without exact it may fall back to front.
  • Multiple getUserMedia calls — each returns an independent stream with new tracks; prior tracks remain open unless you stop() them.
  • srcObject not src — assigning a MediaStream to videoEl.src does nothing; always use videoEl.srcObject = stream.
  • Autoplay policy — muted videos autoplay; unmuted require a user gesture. Set videoEl.muted = true for the local preview.
  • Safari audio constraints — Safari ignores most audio processing constraints; test separately.
  • applyConstraints is asyncawait it; check getSettings() after to confirm what was actually applied.