diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 4f83284b..c6d380eb 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -312,7 +312,7 @@ export const InCallView: FC = ({ const showHeader = useObservableEagerState(vm.showHeader$); const showFooter = useObservableEagerState(vm.showFooter$); const earpieceMode = useObservableEagerState(vm.earpieceMode$); - const toggleEarpieceMode = useObservableEagerState(vm.toggleEarpieceMode$); + const audioOutputSwitcher = useObservableEagerState(vm.audioOutputSwitcher$); const switchCamera = useSwitchCamera(vm.localVideo$); // Ideally we could detect taps by listening for click events and checking @@ -457,27 +457,26 @@ export const InCallView: FC = ({ useAppBarSecondaryButton( useMemo(() => { - if (toggleEarpieceMode === null) return null; - const Icon = earpieceMode ? VolumeOnSolidIcon : EarpieceIcon; + if (audioOutputSwitcher === null) return null; + const isEarpieceTarget = audioOutputSwitcher.targetOutput === "earpiece"; + const Icon = isEarpieceTarget ? EarpieceIcon : VolumeOnSolidIcon; + const label = isEarpieceTarget + ? t("settings.devices.earpiece") + : t("settings.devices.loudspeaker"); + return ( - + { e.preventDefault(); - toggleEarpieceMode(); + audioOutputSwitcher.switch(); }} > ); - }, [t, earpieceMode, toggleEarpieceMode]), + }, [t, audioOutputSwitcher]), ); useAppBarHidden(!showHeader); @@ -789,7 +788,7 @@ export const InCallView: FC = ({ {footer} diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 9caa0925..fc1222c4 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -1261,29 +1261,36 @@ export class CallViewModel extends ViewModel { /** * Callback to toggle between the earpiece and the loudspeaker. + * + * This will be `null` in case the target does not exist in the list + * of available audio outputs. */ - public readonly toggleEarpieceMode$: Observable<(() => void) | null> = - combineLatest( - [ - this.mediaDevices.audioOutput.available$, - this.mediaDevices.audioOutput.selected$, - ], - (available, selected) => { - const selectionType = selected && available.get(selected.id)?.type; - if (!(selectionType === "speaker" || selectionType === "earpiece")) - return null; + public readonly audioOutputSwitcher$: Observable<{ + targetOutput: "earpiece" | "speaker"; + switch: () => void; + } | null> = combineLatest( + [ + this.mediaDevices.audioOutput.available$, + this.mediaDevices.audioOutput.selected$, + ], + (available, selected) => { + const selectionType = selected && available.get(selected.id)?.type; - const newSelectionType = - selectionType === "speaker" ? "earpiece" : "speaker"; - const newSelection = [...available].find( - ([, d]) => d.type === newSelectionType, - ); - if (newSelection === undefined) return null; + // If we are in any output mode other than spaeker switch to speaker. + const newSelectionType = + selectionType === "speaker" ? "earpiece" : "speaker"; + const newSelection = [...available].find( + ([, d]) => d.type === newSelectionType, + ); + if (newSelection === undefined) return null; - const [id] = newSelection; - return () => this.mediaDevices.audioOutput.select(id); - }, - ); + const [id] = newSelection; + return { + targetOutput: newSelectionType, + switch: () => this.mediaDevices.audioOutput.select(id), + }; + }, + ); public readonly reactions$ = this.reactionsSubject$.pipe( map((v) =>