navigator.mediaDevices.enumerateDevices(): Listing Webcam Microphones and Cameras
How navigator.mediaDevices.enumerateDevices() lists cameras and microphones, why labels are blank before permission, how to pair a webcam's mic with its camera, and how to handle devicechange events.
navigator.mediaDevices.enumerateDevices() returns a Promise that resolves to an array of MediaDeviceInfo objects, one for each media input or output the browser is willing to expose. Each entry has a kind (audioinput, videoinput or audiooutput), a deviceId, a groupId and a label. The main limitation is privacy: until the page has been granted camera or microphone permission, labels are empty strings and browsers may return a reduced list. It only works in a secure context, such as HTTPS or localhost. The method tells you which devices exist. It does not open them, and it says nothing about how good a microphone sounds.
What each device entry contains
| Property | What it is | Why it matters for speech capture |
| kind | audioinput, videoinput or audiooutput | Filter on audioinput to build a microphone picker |
| deviceId | An identifier scoped to your site's origin | Pass it to getUserMedia() to open that exact microphone; store it to remember the user's choice |
| groupId | Shared by devices that the browser treats as one physical unit | Can link a webcam's built-in microphone to its camera, but it is not guaranteed |
| label | Human-readable name such as the product name | Empty until permission is granted, so pickers show blank names before then |
Basic example: listing devices
async function listDevices() {
if (!navigator.mediaDevices?.enumerateDevices) {
console.warn('enumerateDevices() is not available in this context.');
return [];
}
const devices = await navigator.mediaDevices.enumerateDevices();
for (const d of devices) {
console.log(d.kind, d.label || '(label hidden)', d.deviceId, d.groupId);
}
return devices;
}
The feature check matters because navigator.mediaDevices is undefined on insecure origins. In that case the code logs a warning instead of throwing an error.
Why labels are blank and the list looks short
Browsers hide device names and may limit the list before the user grants access. This prevents sites from fingerprinting hardware without consent. To build a useful microphone picker, first request access with getUserMedia(), then enumerate. You can stop the temporary tracks right away so the camera light and microphone indicator turn off.
async function listWithLabels() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
const devices = await navigator.mediaDevices.enumerateDevices();
stream.getTracks().forEach((track) => track.stop());
return devices;
}
Pairing a webcam's microphone with its camera
When a webcam has a built-in microphone, browsers often give the camera and the microphone the same groupId. You can use this to suggest the matching microphone when a user picks a camera. Treat it as a hint, not a rule. groupId behaviour varies by browser and operating system, and some webcams appear without a shared group. The label, if it contains the product name, is a secondary clue. Let the user override the suggestion.
function findWebcamMic(devices, cameraId) {
const camera = devices.find((d) => d.kind === 'videoinput' && d.deviceId === cameraId);
if (!camera || !camera.groupId) return null;
return devices.find((d) => d.kind === 'audioinput' && d.groupId === camera.groupId) || null;
}
Opening the selected microphone for speech
async function openMic(deviceId) {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: { exact: deviceId },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
const [track] = stream.getAudioTracks();
console.log(track.label, track.getSettings());
return stream;
}
Using deviceId with exact tells the browser to open that specific microphone or fail with an OverconstrainedError. Without exact, the browser may quietly use a different microphone. The other constraints control the browser's built-in speech processing. Echo cancellation removes speaker audio picked up by the microphone. Noise suppression reduces steady background sound. Automatic gain control evens out loudness. These are requests, not guarantees, so call getSettings() to see what was actually applied. Whether turning them on improves a particular webcam's voice pickup depends on the hardware and the room. No measurements were supplied to support a general recommendation.
Reacting to device change events
navigator.mediaDevices fires a devicechange event when inputs or outputs are added or removed, for example when a USB webcam is plugged in or unplugged. The event does not say which device changed, so call enumerateDevices() again and compare the result with what you had before.
navigator.mediaDevices.addEventListener('devicechange', async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
const mics = devices.filter((d) => d.kind === 'audioinput');
const savedId = localStorage.getItem('preferredMic');
const stillPresent = mics.some((d) => d.deviceId === savedId);
updateMicPicker(mics, stillPresent ? savedId : mics[0]?.deviceId);
});
- The event can fire several times for a single plug or unplug, so make the handler safe to run repeatedly.
- If the microphone you are recording from is removed, its track ends. Listen for the track's ended event so you can switch devices or ask the user what to do.
- Stored deviceIds can become invalid if the user clears site data, and some browsers rotate them. Always check that a saved ID is still in the list before using it.
- Special entries such as a default or communications device appear in some browsers but not others. Do not hard-code them.
Limits and gaps
This guide covers documented browser API behaviour only. No specific browser version data, compatibility figures or audio quality measurements were available. Exact behaviour, such as how much of the list appears before permission, how groupId is assigned and whether audiooutput devices are listed, differs between browsers. Test it in the browsers your users rely on. Inside an iframe, camera and microphone access must also be allowed by the embedding page's permissions policy, or enumeration and capture will be limited.