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>();
// 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<OutputDevice[]>([]);
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<string | undefined>(undefined);
export const controlledAudioDevice$ = new BehaviorSubject<string | undefined>(
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)

View File

@@ -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<Props> = ({ 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<string, DeviceLabel>(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],
);
}

View File

@@ -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,

View File

@@ -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<Props> = ({
[muteStates],
);
const { controlledAudioDevices } = useUrlParams();
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
@@ -132,7 +134,11 @@ export const LobbyView: FC<Props> = ({
// 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<Props> = ({
// 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,
],