diff --git a/src/controls.ts b/src/controls.ts index b5209ab0..4592445c 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -44,10 +44,14 @@ export interface OutputDevice { export const setPipEnabled$ = new Subject(); // BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state) // We want the devices that have been set during loading to be available immediately once loaded. -export const availableOutputDevices$ = new BehaviorSubject([]); +export const controlledAvailableOutputDevices$ = new BehaviorSubject< + OutputDevice[] +>([]); // BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state) // We want the device that has been set during loading to be available immediately once loaded. -export const outputDevice$ = new BehaviorSubject(undefined); +export const controlledAudioDevice$ = new BehaviorSubject( + undefined, +); /** * This allows the os to mute the call if the user * presses the volume down button when it is at the minimum volume. @@ -75,10 +79,10 @@ window.controls = { setPipEnabled$.next(false); }, setAvailableAudioDevices(devices: OutputDevice[]): void { - availableOutputDevices$.next(devices); + controlledAvailableOutputDevices$.next(devices); }, setAudioDevice(id: string): void { - outputDevice$.next(id); + controlledAudioDevice$.next(id); }, setAudioEnabled(enabled: boolean): void { if (!setAudioEnabled$.observed) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 04526f4f..3f2ca196 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -34,7 +34,10 @@ import { alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting, type Setting, } from "../settings/settings"; -import { outputDevice$, availableOutputDevices$ } from "../controls"; +import { + controlledAudioDevice$, + controlledAvailableOutputDevices$, +} from "../controls"; import { useUrlParams } from "../UrlParams"; // This hardcoded id is used in EX ios! It can only be changed in coordination with @@ -331,9 +334,9 @@ export const MediaDevicesProvider: FC = ({ children }) => { function useControlledOutput(): MediaDeviceHandle { const { available } = useObservableEagerState( useObservable(() => { - const outputDeviceData$ = availableOutputDevices$.pipe( + const outputDeviceData$ = controlledAvailableOutputDevices$.pipe( map((devices) => { - const deviceForEarpiece = devices.find((d) => d.forEarpiece); + const hasDeviceForEarpiece = devices.find((d) => d.forEarpiece); const deviceMapTuple: [string, DeviceLabel][] = devices.map( ({ id, name, isEarpiece, isSpeaker /*,isExternalHeadset*/ }) => { let deviceLabel: DeviceLabel = { type: "name", name }; @@ -345,22 +348,22 @@ function useControlledOutput(): MediaDeviceHandle { ); return { devicesMap: new Map(deviceMapTuple), - deviceForEarpiece, + hasDeviceForEarpiece, }; }), ); return combineLatest( [outputDeviceData$, iosDeviceMenu$], - ({ devicesMap, deviceForEarpiece }, iosShowEarpiece) => { + ({ devicesMap, hasDeviceForEarpiece }, iosShowEarpiece) => { let available = devicesMap; - if (iosShowEarpiece && !!deviceForEarpiece) { + if (iosShowEarpiece && !!hasDeviceForEarpiece) { available = new Map([ ...devicesMap.entries(), [EARPIECE_CONFIG_ID, { type: "earpiece" }], ]); } - return { available, deviceForEarpiece }; + return { available, deviceForEarpiece: hasDeviceForEarpiece }; }, ); }), @@ -368,11 +371,11 @@ function useControlledOutput(): MediaDeviceHandle { const [preferredId, setPreferredId] = useSetting(audioOutputSetting); - useSubscription(outputDevice$, (id) => { + useSubscription(controlledAudioDevice$, (id) => { if (id) setPreferredId(id); }); - const selectedId = useSelectedId(available, preferredId); + // const selectedId = useSelectedId(available, preferredId); const [asEarpiece, setAsEarpiece] = useState(false); @@ -381,23 +384,23 @@ function useControlledOutput(): MediaDeviceHandle { // This information is probably only of interest if the earpiece mode has been // selected - for example, Element X iOS listens to this to determine whether it // should enable the proximity sensor. - if (selectedId) { - window.controls.onAudioDeviceSelect?.(selectedId); + if (preferredId) { + window.controls.onAudioDeviceSelect?.(preferredId); // Call deprecated method for backwards compatibility. - window.controls.onOutputDeviceSelect?.(selectedId); + window.controls.onOutputDeviceSelect?.(preferredId); } - setAsEarpiece(selectedId === EARPIECE_CONFIG_ID); - }, [selectedId]); + setAsEarpiece(preferredId === EARPIECE_CONFIG_ID); + }, [preferredId]); return useMemo( () => ({ available: available, - selectedId, + selectedId: preferredId, selectedGroupId: undefined, select: setPreferredId, useAsEarpiece: asEarpiece, }), - [available, selectedId, setPreferredId, asEarpiece], + [available, preferredId, setPreferredId, asEarpiece], ); } diff --git a/src/livekit/useLivekit.ts b/src/livekit/useLivekit.ts index 4589063e..aebcf61b 100644 --- a/src/livekit/useLivekit.ts +++ b/src/livekit/useLivekit.ts @@ -99,10 +99,14 @@ export function useLivekit( }, audioCaptureDefaults: { ...defaultLiveKitOptions.audioCaptureDefaults, - deviceId: initialDevices.audioInput.selectedId, + deviceId: controlledAudioDevices + ? undefined + : initialDevices.audioInput.selectedId, }, audioOutput: { - deviceId: initialDevices.audioOutput.selectedId, + deviceId: controlledAudioDevices + ? undefined + : initialDevices.audioOutput.selectedId, }, e2ee, }; @@ -157,7 +161,7 @@ export function useLivekit( ); const connectionState = useECConnectionState( - initialDevices.audioInput.selectedId, + controlledAudioDevices ? undefined : initialDevices.audioInput.selectedId, initialMuteStates.audio.enabled, room, sfuConfig, diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index f5b47cdd..7b5214e3 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -54,6 +54,7 @@ import { } from "../livekit/TrackProcessorContext"; import { usePageTitle } from "../usePageTitle"; import { useLatest } from "../useLatest"; +import { useUrlParams } from "../UrlParams"; interface Props { client: MatrixClient; @@ -99,6 +100,7 @@ export const LobbyView: FC = ({ [muteStates], ); + const { controlledAudioDevices } = useUrlParams(); const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsTab, setSettingsTab] = useState(defaultSettingsTab); @@ -132,7 +134,11 @@ export const LobbyView: FC = ({ // re-open the devices when they change (see below). const initialAudioOptions = useInitial( () => - muteStates.audio.enabled && { deviceId: devices.audioInput.selectedId }, + muteStates.audio.enabled && { + deviceId: controlledAudioDevices + ? undefined + : devices.audioInput.selectedId, + }, ); const { processor } = useTrackProcessor(); @@ -148,13 +154,16 @@ export const LobbyView: FC = ({ // which would cause the devices to be re-opened on the next render. audio: Object.assign({}, initialAudioOptions), video: muteStates.video.enabled && { - deviceId: devices.videoInput.selectedId, + deviceId: controlledAudioDevices + ? undefined + : devices.videoInput.selectedId, processor: initialProcessor, }, }), [ initialAudioOptions, muteStates.video.enabled, + controlledAudioDevices, devices.videoInput.selectedId, initialProcessor, ],