selectedId == PreferredId for controlled output

This skips the usage of the useSelectedId hook that only really makes sense for non controlled audio.

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-06-12 13:50:22 +02:00
parent 84ff12e4de
commit 98bfab47dc
4 changed files with 45 additions and 25 deletions

View File

@@ -44,10 +44,14 @@ export interface OutputDevice {
export const setPipEnabled$ = new Subject<boolean>(); export const setPipEnabled$ = new Subject<boolean>();
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state) // 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. // We want the devices that have been set during loading to be available immediately once loaded.
export const availableOutputDevices$ = new BehaviorSubject<OutputDevice[]>([]); export const controlledAvailableOutputDevices$ = new BehaviorSubject<
OutputDevice[]
>([]);
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state) // 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. // We want the device that has been set during loading to be available immediately once loaded.
export const outputDevice$ = new BehaviorSubject<string | undefined>(undefined); export const controlledAudioDevice$ = new BehaviorSubject<string | undefined>(
undefined,
);
/** /**
* This allows the os to mute the call if the user * This allows the os to mute the call if the user
* presses the volume down button when it is at the minimum volume. * presses the volume down button when it is at the minimum volume.
@@ -75,10 +79,10 @@ window.controls = {
setPipEnabled$.next(false); setPipEnabled$.next(false);
}, },
setAvailableAudioDevices(devices: OutputDevice[]): void { setAvailableAudioDevices(devices: OutputDevice[]): void {
availableOutputDevices$.next(devices); controlledAvailableOutputDevices$.next(devices);
}, },
setAudioDevice(id: string): void { setAudioDevice(id: string): void {
outputDevice$.next(id); controlledAudioDevice$.next(id);
}, },
setAudioEnabled(enabled: boolean): void { setAudioEnabled(enabled: boolean): void {
if (!setAudioEnabled$.observed) if (!setAudioEnabled$.observed)

View File

@@ -34,7 +34,10 @@ import {
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting, alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
type Setting, type Setting,
} from "../settings/settings"; } from "../settings/settings";
import { outputDevice$, availableOutputDevices$ } from "../controls"; import {
controlledAudioDevice$,
controlledAvailableOutputDevices$,
} from "../controls";
import { useUrlParams } from "../UrlParams"; import { useUrlParams } from "../UrlParams";
// This hardcoded id is used in EX ios! It can only be changed in coordination with // This hardcoded id is used in EX ios! It can only be changed in coordination with
@@ -331,9 +334,9 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
function useControlledOutput(): MediaDeviceHandle { function useControlledOutput(): MediaDeviceHandle {
const { available } = useObservableEagerState( const { available } = useObservableEagerState(
useObservable(() => { useObservable(() => {
const outputDeviceData$ = availableOutputDevices$.pipe( const outputDeviceData$ = controlledAvailableOutputDevices$.pipe(
map((devices) => { map((devices) => {
const deviceForEarpiece = devices.find((d) => d.forEarpiece); const hasDeviceForEarpiece = devices.find((d) => d.forEarpiece);
const deviceMapTuple: [string, DeviceLabel][] = devices.map( const deviceMapTuple: [string, DeviceLabel][] = devices.map(
({ id, name, isEarpiece, isSpeaker /*,isExternalHeadset*/ }) => { ({ id, name, isEarpiece, isSpeaker /*,isExternalHeadset*/ }) => {
let deviceLabel: DeviceLabel = { type: "name", name }; let deviceLabel: DeviceLabel = { type: "name", name };
@@ -345,22 +348,22 @@ function useControlledOutput(): MediaDeviceHandle {
); );
return { return {
devicesMap: new Map<string, DeviceLabel>(deviceMapTuple), devicesMap: new Map<string, DeviceLabel>(deviceMapTuple),
deviceForEarpiece, hasDeviceForEarpiece,
}; };
}), }),
); );
return combineLatest( return combineLatest(
[outputDeviceData$, iosDeviceMenu$], [outputDeviceData$, iosDeviceMenu$],
({ devicesMap, deviceForEarpiece }, iosShowEarpiece) => { ({ devicesMap, hasDeviceForEarpiece }, iosShowEarpiece) => {
let available = devicesMap; let available = devicesMap;
if (iosShowEarpiece && !!deviceForEarpiece) { if (iosShowEarpiece && !!hasDeviceForEarpiece) {
available = new Map([ available = new Map([
...devicesMap.entries(), ...devicesMap.entries(),
[EARPIECE_CONFIG_ID, { type: "earpiece" }], [EARPIECE_CONFIG_ID, { type: "earpiece" }],
]); ]);
} }
return { available, deviceForEarpiece }; return { available, deviceForEarpiece: hasDeviceForEarpiece };
}, },
); );
}), }),
@@ -368,11 +371,11 @@ function useControlledOutput(): MediaDeviceHandle {
const [preferredId, setPreferredId] = useSetting(audioOutputSetting); const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
useSubscription(outputDevice$, (id) => { useSubscription(controlledAudioDevice$, (id) => {
if (id) setPreferredId(id); if (id) setPreferredId(id);
}); });
const selectedId = useSelectedId(available, preferredId); // const selectedId = useSelectedId(available, preferredId);
const [asEarpiece, setAsEarpiece] = useState(false); 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 // 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 // selected - for example, Element X iOS listens to this to determine whether it
// should enable the proximity sensor. // should enable the proximity sensor.
if (selectedId) { if (preferredId) {
window.controls.onAudioDeviceSelect?.(selectedId); window.controls.onAudioDeviceSelect?.(preferredId);
// Call deprecated method for backwards compatibility. // Call deprecated method for backwards compatibility.
window.controls.onOutputDeviceSelect?.(selectedId); window.controls.onOutputDeviceSelect?.(preferredId);
} }
setAsEarpiece(selectedId === EARPIECE_CONFIG_ID); setAsEarpiece(preferredId === EARPIECE_CONFIG_ID);
}, [selectedId]); }, [preferredId]);
return useMemo( return useMemo(
() => ({ () => ({
available: available, available: available,
selectedId, selectedId: preferredId,
selectedGroupId: undefined, selectedGroupId: undefined,
select: setPreferredId, select: setPreferredId,
useAsEarpiece: asEarpiece, useAsEarpiece: asEarpiece,
}), }),
[available, selectedId, setPreferredId, asEarpiece], [available, preferredId, setPreferredId, asEarpiece],
); );
} }

View File

@@ -99,10 +99,14 @@ export function useLivekit(
}, },
audioCaptureDefaults: { audioCaptureDefaults: {
...defaultLiveKitOptions.audioCaptureDefaults, ...defaultLiveKitOptions.audioCaptureDefaults,
deviceId: initialDevices.audioInput.selectedId, deviceId: controlledAudioDevices
? undefined
: initialDevices.audioInput.selectedId,
}, },
audioOutput: { audioOutput: {
deviceId: initialDevices.audioOutput.selectedId, deviceId: controlledAudioDevices
? undefined
: initialDevices.audioOutput.selectedId,
}, },
e2ee, e2ee,
}; };
@@ -157,7 +161,7 @@ export function useLivekit(
); );
const connectionState = useECConnectionState( const connectionState = useECConnectionState(
initialDevices.audioInput.selectedId, controlledAudioDevices ? undefined : initialDevices.audioInput.selectedId,
initialMuteStates.audio.enabled, initialMuteStates.audio.enabled,
room, room,
sfuConfig, sfuConfig,

View File

@@ -54,6 +54,7 @@ import {
} from "../livekit/TrackProcessorContext"; } from "../livekit/TrackProcessorContext";
import { usePageTitle } from "../usePageTitle"; import { usePageTitle } from "../usePageTitle";
import { useLatest } from "../useLatest"; import { useLatest } from "../useLatest";
import { useUrlParams } from "../UrlParams";
interface Props { interface Props {
client: MatrixClient; client: MatrixClient;
@@ -99,6 +100,7 @@ export const LobbyView: FC<Props> = ({
[muteStates], [muteStates],
); );
const { controlledAudioDevices } = useUrlParams();
const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab); const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
@@ -132,7 +134,11 @@ export const LobbyView: FC<Props> = ({
// re-open the devices when they change (see below). // re-open the devices when they change (see below).
const initialAudioOptions = useInitial( const initialAudioOptions = useInitial(
() => () =>
muteStates.audio.enabled && { deviceId: devices.audioInput.selectedId }, muteStates.audio.enabled && {
deviceId: controlledAudioDevices
? undefined
: devices.audioInput.selectedId,
},
); );
const { processor } = useTrackProcessor(); const { processor } = useTrackProcessor();
@@ -148,13 +154,16 @@ export const LobbyView: FC<Props> = ({
// which would cause the devices to be re-opened on the next render. // which would cause the devices to be re-opened on the next render.
audio: Object.assign({}, initialAudioOptions), audio: Object.assign({}, initialAudioOptions),
video: muteStates.video.enabled && { video: muteStates.video.enabled && {
deviceId: devices.videoInput.selectedId, deviceId: controlledAudioDevices
? undefined
: devices.videoInput.selectedId,
processor: initialProcessor, processor: initialProcessor,
}, },
}), }),
[ [
initialAudioOptions, initialAudioOptions,
muteStates.video.enabled, muteStates.video.enabled,
controlledAudioDevices,
devices.videoInput.selectedId, devices.videoInput.selectedId,
initialProcessor, initialProcessor,
], ],