From 6b6b67977435e52b74d9ecc4dac038cb71c0b421 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:59:16 +0200 Subject: [PATCH] Make camera unavailable if using earpice mode (#3351) --- src/MediaDevicesContext.ts | 30 +++++++++++++++++------------- src/room/MuteStates.ts | 9 ++++++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/MediaDevicesContext.ts b/src/MediaDevicesContext.ts index cc3af742..3cf54c2a 100644 --- a/src/MediaDevicesContext.ts +++ b/src/MediaDevicesContext.ts @@ -23,6 +23,14 @@ export function useMediaDevices(): 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. * It will check the `useAsEarpiece` of the `audioOutput` device and return @@ -36,17 +44,13 @@ export const useEarpieceAudioConfig = (): { } => { const devices = useMediaDevices(); const audioOutput = useObservableEagerState(devices.audioOutput.selected$); - // We use only the right speaker (pan = 1) for the earpiece. - // This mimics the behavior of the native earpiece speaker (only the top speaker on an iPhone) - const pan = useMemo( - () => (audioOutput?.virtualEarpiece ? 1 : 0), - [audioOutput?.virtualEarpiece], - ); - // 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. - const volume = useMemo( - () => (audioOutput?.virtualEarpiece ? 0.1 : 1), - [audioOutput?.virtualEarpiece], - ); - return { pan, volume }; + const isVirtualEarpiece = audioOutput?.virtualEarpiece ?? false; + return { + // We use only the right speaker (pan = 1) for the earpiece. + // This mimics the behavior of the native earpiece speaker (only the top speaker on an iPhone) + pan: useMemo(() => (isVirtualEarpiece ? 1 : 0), [isVirtualEarpiece]), + // 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. + volume: useMemo(() => (isVirtualEarpiece ? 0.1 : 1), [isVirtualEarpiece]), + }; }; diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index b9b32c35..16ba171a 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -21,7 +21,7 @@ import { type SelectedDevice, type MediaDevice, } from "../state/MediaDevices"; -import { useMediaDevices } from "../MediaDevicesContext"; +import { useIsEarpiece, useMediaDevices } from "../MediaDevicesContext"; import { useReactiveState } from "../useReactiveState"; import { ElementWidgetActions, widget } from "../widget"; import { Config } from "../config/Config"; @@ -58,6 +58,7 @@ export interface MuteStates { function useMuteState( device: MediaDevice, enabledByDefault: () => boolean, + forceUnavailable: boolean = false, ): MuteState { const available = useObservableEagerState(device.available$); const [enabled, setEnabled] = useReactiveState( @@ -67,13 +68,13 @@ function useMuteState( ); return useMemo( () => - available.size === 0 + available.size === 0 || forceUnavailable ? deviceUnavailable : { enabled: enabled ?? false, setEnabled: setEnabled as Dispatch>, }, - [available.size, enabled, setEnabled], + [available.size, enabled, forceUnavailable, setEnabled], ); } @@ -85,9 +86,11 @@ export function useMuteStates(isJoined: boolean): MuteStates { const audio = useMuteState(devices.audioInput, () => { return Config.get().media_devices.enable_audio && !skipLobby && !isJoined; }); + const isEarpiece = useIsEarpiece(); const video = useMuteState( devices.videoInput, () => Config.get().media_devices.enable_video && !skipLobby && !isJoined, + isEarpiece, // Force video to be unavailable if using earpiece ); useEffect(() => {