Skip to content

Devices

const { getMediaDevices, audioInputDevices, videoInputDevices, audioOutputDevices } = useMediaStream();
useEffect(() => {
getMediaDevices();
}, [getMediaDevices]);

getMediaDevices() returns the full list and also populates devices, plus three arrays split by kind:

MediaDeviceInfo.kind
audioInputDevices audioinput — microphones
audioOutputDevices audiooutput — speakers
videoInputDevices videoinput — cameras

If you only need the microphone list, ask for audio alone so the camera never lights up:

useMediaStream({ mediaDeviceConstraints: { video: false } });

The selected* values read the live track’s settings, which is what the browser actually gave you — not what you asked for:

const {
selectedAudioTrackDeviceId,
selectedVideoTrackDeviceId,
selectedVideoTrackDeviceWidth,
selectedVideoTrackDeviceHeight,
selectedVideoTrackDeviceAspectRatio,
} = useMediaStream();

Ask for 1280×720 on a webcam that can’t do it and you’ll get something else — these tell you what. They’re undefined while no stream is running.

function CameraPicker() {
const { videoInputDevices, selectedVideoTrackDeviceId, updateMediaDeviceConstraints } = useMediaStream();
return (
<select
value={selectedVideoTrackDeviceId ?? ''}
onChange={(e) =>
updateMediaDeviceConstraints({
constraints: { video: { deviceId: e.target.value } },
resetStream: true,
})
}
>
{videoInputDevices.map((d) => (
<option key={d.deviceId} value={d.deviceId}>
{d.label || d.deviceId}
</option>
))}
</select>
);
}

resetStream: true is what makes the switch take effect: the current stream is released and a new one acquired with the merged constraints. Without it the constraints are recorded but the live stream keeps running on the old device, which is what you want if you’re setting up before starting.

isStreaming is preserved across the swap, so a stream that was running stays running.

Phones expose facingMode rather than useful device labels, so prefer it over deviceId:

updateMediaDeviceConstraints({
constraints: { video: { facingMode: 'environment' } }, // rear camera
resetStream: true,
});

'user' is the selfie camera and the default.

audioOutputDevices is listed for completeness, but output can’t be chosen through getUserMedia — it isn’t an input. To route audio to a specific speaker, use HTMLMediaElement.setSinkId() on the element itself, passing the deviceId from this list.

The arrays don’t refresh by themselves. To follow devices being plugged in or removed, listen to the browser event and re-run the query:

useEffect(() => {
const onChange = () => getMediaDevices();
navigator.mediaDevices?.addEventListener('devicechange', onChange);
return () => navigator.mediaDevices?.removeEventListener('devicechange', onChange);
}, [getMediaDevices]);

The three arrays keep a stable identity between renders, so using them as effect dependencies is safe and won’t loop.