Make camera unavailable if using earpice mode (#3351)

This commit is contained in:
Timo
2025-06-24 10:59:16 +02:00
committed by GitHub
parent 3ffb118dc7
commit 6b6b679774
2 changed files with 23 additions and 16 deletions

View File

@@ -23,6 +23,14 @@ export function useMediaDevices(): MediaDevices {
return mediaDevices; return mediaDevices;
} }
export const useIsEarpiece = (): boolean => {
const devices = useMediaDevices();
const audioOutput = useObservableEagerState(devices.audioOutput.selected$);
const available = useObservableEagerState(devices.audioOutput.available$);
if (!audioOutput?.id) return false;
return available.get(audioOutput.id)?.type === "earpiece";
};
/** /**
* A convenience hook to get the audio node configuration for the earpiece. * A convenience hook to get the audio node configuration for the earpiece.
* It will check the `useAsEarpiece` of the `audioOutput` device and return * It will check the `useAsEarpiece` of the `audioOutput` device and return
@@ -36,17 +44,13 @@ export const useEarpieceAudioConfig = (): {
} => { } => {
const devices = useMediaDevices(); const devices = useMediaDevices();
const audioOutput = useObservableEagerState(devices.audioOutput.selected$); const audioOutput = useObservableEagerState(devices.audioOutput.selected$);
// We use only the right speaker (pan = 1) for the earpiece. const isVirtualEarpiece = audioOutput?.virtualEarpiece ?? false;
// This mimics the behavior of the native earpiece speaker (only the top speaker on an iPhone) return {
const pan = useMemo( // We use only the right speaker (pan = 1) for the earpiece.
() => (audioOutput?.virtualEarpiece ? 1 : 0), // This mimics the behavior of the native earpiece speaker (only the top speaker on an iPhone)
[audioOutput?.virtualEarpiece], pan: useMemo(() => (isVirtualEarpiece ? 1 : 0), [isVirtualEarpiece]),
); // We also do lower the volume by a factor of 10 to optimize for the usecase where
// We also do lower the volume by a factor of 10 to optimize for the usecase where // a user is holding the phone to their ear.
// a user is holding the phone to their ear. volume: useMemo(() => (isVirtualEarpiece ? 0.1 : 1), [isVirtualEarpiece]),
const volume = useMemo( };
() => (audioOutput?.virtualEarpiece ? 0.1 : 1),
[audioOutput?.virtualEarpiece],
);
return { pan, volume };
}; };

View File

@@ -21,7 +21,7 @@ import {
type SelectedDevice, type SelectedDevice,
type MediaDevice, type MediaDevice,
} from "../state/MediaDevices"; } from "../state/MediaDevices";
import { useMediaDevices } from "../MediaDevicesContext"; import { useIsEarpiece, useMediaDevices } from "../MediaDevicesContext";
import { useReactiveState } from "../useReactiveState"; import { useReactiveState } from "../useReactiveState";
import { ElementWidgetActions, widget } from "../widget"; import { ElementWidgetActions, widget } from "../widget";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
@@ -58,6 +58,7 @@ export interface MuteStates {
function useMuteState( function useMuteState(
device: MediaDevice<DeviceLabel, SelectedDevice>, device: MediaDevice<DeviceLabel, SelectedDevice>,
enabledByDefault: () => boolean, enabledByDefault: () => boolean,
forceUnavailable: boolean = false,
): MuteState { ): MuteState {
const available = useObservableEagerState(device.available$); const available = useObservableEagerState(device.available$);
const [enabled, setEnabled] = useReactiveState<boolean | undefined>( const [enabled, setEnabled] = useReactiveState<boolean | undefined>(
@@ -67,13 +68,13 @@ function useMuteState(
); );
return useMemo( return useMemo(
() => () =>
available.size === 0 available.size === 0 || forceUnavailable
? deviceUnavailable ? deviceUnavailable
: { : {
enabled: enabled ?? false, enabled: enabled ?? false,
setEnabled: setEnabled as Dispatch<SetStateAction<boolean>>, setEnabled: setEnabled as Dispatch<SetStateAction<boolean>>,
}, },
[available.size, enabled, setEnabled], [available.size, enabled, forceUnavailable, setEnabled],
); );
} }
@@ -85,9 +86,11 @@ export function useMuteStates(isJoined: boolean): MuteStates {
const audio = useMuteState(devices.audioInput, () => { const audio = useMuteState(devices.audioInput, () => {
return Config.get().media_devices.enable_audio && !skipLobby && !isJoined; return Config.get().media_devices.enable_audio && !skipLobby && !isJoined;
}); });
const isEarpiece = useIsEarpiece();
const video = useMuteState( const video = useMuteState(
devices.videoInput, devices.videoInput,
() => Config.get().media_devices.enable_video && !skipLobby && !isJoined, () => Config.get().media_devices.enable_video && !skipLobby && !isJoined,
isEarpiece, // Force video to be unavailable if using earpiece
); );
useEffect(() => { useEffect(() => {