diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 3d85b165..a26cf722 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -24,6 +24,7 @@ import { audioInput as audioInputSetting, audioOutput as audioOutputSetting, videoInput as videoInputSetting, + Setting, } from "../settings/settings"; import { isFirefox } from "../Platform"; @@ -58,7 +59,7 @@ function useObservableState( function useMediaDevice( kind: MediaDeviceKind, - fallbackDevice: string | undefined, + setting: Setting, usingNames: boolean, alwaysDefault: boolean = false, ): MediaDevice { @@ -84,15 +85,19 @@ function useMediaDevice( [kind, requestPermissions], ); const available = useObservableState(deviceObserver, []); - const [selectedId, select] = useState(fallbackDevice); + const [selectedId, select] = useSetting(setting); return useMemo(() => { - let devId; - if (available) { - devId = available.some((d) => d.deviceId === selectedId) - ? selectedId - : available.some((d) => d.deviceId === fallbackDevice) - ? fallbackDevice + let devId: string | undefined = undefined; + if (!alwaysDefault && available) { + // If the selected device is available, use it. Or if every available + // device ID is falsy, the browser is probably just being paranoid about + // fingerprinting and we should still try using the selected device. + // Otherwise, fall back to the first available device. + devId = + available.some((d) => d.deviceId === selectedId) || + available.every((d) => d.deviceId === "") + ? selectedId : available.at(0)?.deviceId; } @@ -102,10 +107,10 @@ function useMediaDevice( // device entries for the exact same device ID; deduplicate them [...new Map(available.map((d) => [d.deviceId, d])).values()] : [], - selectedId: alwaysDefault ? undefined : devId, + selectedId: devId, select, }; - }, [available, selectedId, fallbackDevice, select, alwaysDefault]); + }, [available, selectedId, select, alwaysDefault]); } const deviceStub: MediaDevice = { @@ -141,36 +146,22 @@ export const MediaDevicesProvider: FC = ({ children }) => { // for ouput devices because the selector wont be shown on FF. const useOutputNames = usingNames && !isFirefox(); - const [storedAudioInput, setStoredAudioInput] = useSetting(audioInputSetting); - const [storedAudioOutput, setStoredAudioOutput] = - useSetting(audioOutputSetting); - const [storedVideoInput, setStoredVideoInput] = useSetting(videoInputSetting); - - const audioInput = useMediaDevice("audioinput", storedAudioInput, usingNames); + const audioInput = useMediaDevice( + "audioinput", + audioInputSetting, + usingNames, + ); const audioOutput = useMediaDevice( "audiooutput", - storedAudioOutput, + audioOutputSetting, useOutputNames, alwaysUseDefaultAudio, ); - const videoInput = useMediaDevice("videoinput", storedVideoInput, usingNames); - - useEffect(() => { - if (audioInput.selectedId !== undefined) - setStoredAudioInput(audioInput.selectedId); - }, [setStoredAudioInput, audioInput.selectedId]); - - useEffect(() => { - // Skip setting state for ff output. Redundent since it is set to always return 'undefined' - // but makes it clear while debugging that this is not happening on FF. + perf ;) - if (audioOutput.selectedId !== undefined && !isFirefox()) - setStoredAudioOutput(audioOutput.selectedId); - }, [setStoredAudioOutput, audioOutput.selectedId]); - - useEffect(() => { - if (videoInput.selectedId !== undefined) - setStoredVideoInput(videoInput.selectedId); - }, [setStoredVideoInput, videoInput.selectedId]); + const videoInput = useMediaDevice( + "videoinput", + videoInputSetting, + usingNames, + ); const startUsingDeviceNames = useCallback( () => setNumCallersUsingNames((n) => n + 1),