MY WEBCAM TEST GUIDE

Choosing a Camera in the Browser by deviceId

How to select a specific webcam and its built-in microphone with getUserMedia and deviceId, including exact vs ideal constraints, groupId pairing and checks.

The short answer

To open a particular webcam, pass its deviceId inside the video constraint of getUserMedia. Use the exact form when the app must have that device. A plain value or the ideal form is only a preference, so the browser may hand back a different camera. Two conditions limit this. First, browsers usually expose usable device IDs and labels only after the user has granted camera or microphone permission. Second, choosing a camera does not choose a microphone. If you want speech captured by the webcam's built-in microphone, you need to request that audio input by its own deviceId.

const stream = await navigator.mediaDevices.getUserMedia({
  video: { deviceId: { exact: cameraId } },
  audio: { deviceId: { exact: micId } }
});

Where deviceId values come from

Device IDs come from navigator.mediaDevices.enumerateDevices(). That call returns a list of MediaDeviceInfo objects, one for each input or output the browser is willing to reveal. Each entry has these fields:

  • deviceId: an opaque string that identifies the device for the current origin. It is not a hardware serial number.
  • kind: one of videoinput (camera), audioinput (microphone) or audiooutput (speaker or headphones).
  • label: a human-readable name such as the product name of the webcam. It is often empty until permission is granted.
  • groupId: a value shared by devices that the browser considers part of the same physical device.

Before the user grants permission, browsers may return empty labels, empty or placeholder IDs, or a shortened list. A common pattern is to request access once, enumerate devices, and then show the user a picker built from the labels.

// Ask once so labels and IDs become available
const probe = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
probe.getTracks().forEach(t => t.stop());

const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
const mics = devices.filter(d => d.kind === 'audioinput');

Exact versus ideal constraints

The getUserMedia constraint syntax treats deviceId in one of two ways. Which one you pick decides what happens when the requested device is missing.

  • deviceId: { exact: id } is a hard requirement. If that device is unavailable, the call fails with an OverconstrainedError instead of quietly using another device.
  • deviceId: id, or deviceId: { ideal: id }, is a preference. The browser tries that device but may substitute another one, so your code should check which device it actually received.
try {
  stream = await navigator.mediaDevices.getUserMedia({
    video: { deviceId: { exact: savedCameraId } }
  });
} catch (err) {
  if (err.name === 'OverconstrainedError') {
    // Saved camera is gone; fall back and let the user choose again
    stream = await navigator.mediaDevices.getUserMedia({ video: true });
  } else {
    throw err;
  }
}

Pairing a webcam with its built-in microphone

Many webcams also contain a microphone, and the browser lists it as a separate audioinput entry. The groupId field is the documented way to link them: inputs the browser treats as one physical device share a groupId. You can look up the selected camera's groupId and then find an audio input with the same value. How browsers and operating systems assign groupId depends on the implementation, so treat a match as a useful hint rather than a guarantee. If no audio input matches, keep a manual microphone picker.

const camera = cameras.find(c => c.deviceId === cameraId);
const pairedMic = mics.find(m => m.groupId === camera?.groupId);

const stream = await navigator.mediaDevices.getUserMedia({
  video: { deviceId: { exact: cameraId } },
  audio: pairedMic ? { deviceId: { exact: pairedMic.deviceId } } : true
});

Pairing tells you which microphone is in the webcam. It does not tell you whether that microphone is the best choice for speech. Voice quality depends on the specific hardware, how far the speaker sits from it, and the room. This guide has no measured comparisons between webcam microphones and other inputs.

Confirming which device you actually got

The track itself reports which device it is using. Calling getSettings() on a MediaStreamTrack returns the deviceId in use, and the track's label gives a readable name. Run this check whenever you use ideal constraints, or when you need to show users the camera and microphone that are currently active.

const [videoTrack] = stream.getVideoTracks();
const [audioTrack] = stream.getAudioTracks();
console.log(videoTrack.getSettings().deviceId, videoTrack.label);
console.log(audioTrack?.getSettings().deviceId, audioTrack?.label);

Speech-related audio constraints alongside deviceId

Audio constraints can sit next to deviceId and affect how speech sounds. The standard ones are echoCancellation, noiseSuppression and autoGainControl. Automatic gain control is processing that raises or lowers the input level so speech stays roughly even. All three are requests, not guarantees. Call getCapabilities() to see what the selected microphone supports, and getSettings() to see what was actually applied.

audio: {
  deviceId: { exact: micId },
  echoCancellation: true,
  noiseSuppression: true,
  autoGainControl: true
}

Persistence and device changes

A deviceId is scoped to the origin, meaning the site that asked for it. Its value can change when the user clears site data or browses privately. If you save a user's camera or microphone choice, store it as a preference, try it with exact, and fall back cleanly when it fails. Devices can also be plugged in or removed while the page is open. The devicechange event lets you refresh the picker when that happens.

navigator.mediaDevices.addEventListener('devicechange', async () => {
  const devices = await navigator.mediaDevices.enumerateDevices();
  updatePicker(devices);
});

Common pitfalls

  • getUserMedia runs only in a secure context, meaning HTTPS or localhost. On a plain HTTP page, navigator.mediaDevices may be undefined.
  • Some Chromium-based browsers list pseudo-entries with IDs such as default or communications. These point to whatever the operating system currently selects, not to one fixed device, so a saved default ID can resolve to different hardware over time.
  • When switching cameras or microphones, stop the old tracks before opening new ones. This releases the hardware and avoids keeping two inputs active.
  • On phones, facingMode (user or environment) is often a simpler way to pick front or rear cameras than deviceId.

Evidence gaps

This guide covers the documented behavior of the Media Capture and Streams API. It contains no measurements of voice quality, no comparisons between browser versions and no results for specific webcam models. Permission prompts, label exposure and groupId assignment vary between browsers and operating systems. Test the selection flow in each browser you need to support before relying on it.