Media Devices Survival Guide

June 13, 2026

MediaDevices.enumerateDevices() is a Web API listing cameras, microphones, and speakers available for use. It powers device pickers in every browser-based conferencing app you've ever used.

It's awful.

The API is nothing but edge cases, browser quirks, OS differences, and hardware limitations.

This is a survival guide to building your own picker.

Default Devices

MediaDeviceInfo {
deviceId: 'default', // ???
kind: 'audioinput',
label: 'Default',
groupId: '...',
}

Chromium-based browsers started the trend. Devices labeled "Default" appear duplicated in the list. Sometimes the label includes a device name, sometimes it's translated, sometimes it's just called "Default".

These options aren't noise; they're exactly what you want. They represent the user's preferred input device and automatically track what's available. Disconnect earbuds, re-request "default", and the next best option slots in.

Default devices are configurable in system settings. Respect the preference. It's good signal.

Note: Consider lifting default devices to the top of your list. Rename them for clarity as "System Default (<label>)", but only if you have the patience to parse the matching device label from supporting platforms.

Communications Devices

MediaDeviceInfo {
deviceId: 'communications', // ?!?
kind: 'audioinput',
label: 'Communications',
groupId: '...',
}

Windows saw default devices and decided it needed to make it more complicated with a "communications" type. They show up alongside "default" devices.

It actually makes sense: the microphone you use for recording might not be appropriate for conferencing. Windows lets you override defaults for real-time communications.

In practice, the setting isn't very discoverable and I don't think many users know about it. Either hide it or transparently replace your "default" option.

Missing Device IDs and Labels

According to the spec, device IDs are stable between refreshes unless the user clears site data. Persistent unique IDs are a dream for fingerprinting software, so browsers replace them with an empty string. You only see real values after the first permission grant.

Some browsers even partition visibility by device type. For example, camera details stay hidden if you only ask for a microphone.

Make sure your device selector handles null values and empty strings. Derive your own labels and refresh the list after any successful track request. Permissions.query() can hint whether the device list is trustworthy.

Private Devices

Most browsers hide devices until the first request. If you have three available cameras, only one will be in the list. Other cameras show after a permission grant.

The first request has to act with incomplete information. Leave the query open-ended and let the user choose from the browser's built-in picker.

Virtual Devices

Synthetic devices appear in the list. Zoom's audio input is pretty common. These are app-specific, and (depending on the app) won't even work. It's a footgun.

Synthetic inputs give desktop apps a lot of power. Device proxies can enhance, transform, and mux from multiple sources. Some features just aren't possible without a custom source.

Operating systems don't know any of this. All they know is it looks a heck of a lot like an input device, and browsers might like to join the fun.

Maintain a denylist for the ones that demonstrably break. Pray they don't change.

Note: Not all synthetic devices cause trouble. Some are genuinely useful, providing virtual backgrounds or audio streaming from other apps.

Continuity Camera

Imagine joining a conferencing call and your video automatically broadcasts your kid playing games on your iPad. That's the magic of Continuity Camera.

iOS devices linked to macOS advertise their camera as another device. Cool feature, but needless to say, pretty surprising for users.

Hide the device or visually distinguish it to avoid the surprise. Be especially careful if your app automatically tries a fallback.

Switching Tracks on Mobile

Desktop hardware can stream several tracks of the same kind. Mobile devices aren't so generous.

If you support mobile, you'll probably hit this in the form of GUM errors, especially if your picker UX offers live previews.

Accept the cutover delay. Close your tracks before requesting new ones.

Wired Speakers

Internal speakers show as a unique audiooutput device, but plug in wired headphones and watch the label swap in place while showing the same device ID.

If you're lucky, 'devicechange' tells you about the update. It might not.

Defensively refresh when the picker opens, or poll to catch silent updates.

HFP Mode

"Hands-free Profile". Old hat for anyone working in real-time media, but worth noting: most Bluetooth audio devices downgrade quality as soon as you request their microphone.

It's a common source of support tickets. Not much to do about it except switch microphones.

The Rest...

This reference isn't exhaustive. I didn't mention groupId in Safari v14, track.clone() in React Native, z-index compositing bugs, autoplay policies, fullscreen affinity in iOS, or myriad other traps.

For a platform as stable as the web, video conferencing is one of the few moving treadmills. You'll never patch every issue or solve every hardware bug. It's a dynamic field that always keeps you guessing.

So don't stress it. We're all clowns at the circus. Honk and keep going.


Note: I maintain media-devices, a library that papers over some of the quirks.