Firefox audio output issues fix (#1510)

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2023-09-19 15:10:14 +02:00
committed by GitHub
parent c2bc4651cf
commit 4aec5c34f3
4 changed files with 38 additions and 18 deletions

View File

@@ -28,6 +28,7 @@ import { createMediaDeviceObserver } from "@livekit/components-core";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { import {
isFirefox,
useAudioInput, useAudioInput,
useAudioOutput, useAudioOutput,
useVideoInput, useVideoInput,
@@ -65,7 +66,8 @@ function useObservableState<T>(
function useMediaDevice( function useMediaDevice(
kind: MediaDeviceKind, kind: MediaDeviceKind,
fallbackDevice: string | undefined, fallbackDevice: string | undefined,
usingNames: boolean usingNames: boolean,
alwaysDefault: boolean = false
): MediaDevice { ): MediaDevice {
// Make sure we don't needlessly reset to a device observer without names, // Make sure we don't needlessly reset to a device observer without names,
// once permissions are already given // once permissions are already given
@@ -86,18 +88,19 @@ function useMediaDevice(
const available = useObservableState(deviceObserver, []); const available = useObservableState(deviceObserver, []);
const [selectedId, select] = useState(fallbackDevice); const [selectedId, select] = useState(fallbackDevice);
return useMemo( return useMemo(() => {
() => ({ const devId = available.some((d) => d.deviceId === selectedId)
? selectedId
: available.some((d) => d.deviceId === fallbackDevice)
? fallbackDevice
: available.at(0)?.deviceId;
return {
available, available,
selectedId: available.some((d) => d.deviceId === selectedId) selectedId: alwaysDefault ? undefined : devId,
? selectedId
: available.some((d) => d.deviceId === fallbackDevice)
? fallbackDevice
: available.at(0)?.deviceId,
select, select,
}), };
[available, selectedId, fallbackDevice, select] }, [available, selectedId, fallbackDevice, select, alwaysDefault]);
);
} }
const deviceStub: MediaDevice = { const deviceStub: MediaDevice = {
@@ -120,10 +123,17 @@ interface Props {
} }
export const MediaDevicesProvider: FC<Props> = ({ children }) => { export const MediaDevicesProvider: FC<Props> = ({ children }) => {
// Counts the number of callers currently using device names // Counts the number of callers currently using device names.
const [numCallersUsingNames, setNumCallersUsingNames] = useState(0); const [numCallersUsingNames, setNumCallersUsingNames] = useState(0);
const usingNames = numCallersUsingNames > 0; const usingNames = numCallersUsingNames > 0;
// Use output device names for output devices on all platforms except FF.
const useOutputNames = usingNames && !isFirefox();
// Setting the audio device to sth. else than 'undefined' breaks echo-cancellation
// and even can introduce multiple different output devices for one call.
const alwaysUseDefaultAudio = isFirefox();
const [audioInputSetting, setAudioInputSetting] = useAudioInput(); const [audioInputSetting, setAudioInputSetting] = useAudioInput();
const [audioOutputSetting, setAudioOutputSetting] = useAudioOutput(); const [audioOutputSetting, setAudioOutputSetting] = useAudioOutput();
const [videoInputSetting, setVideoInputSetting] = useVideoInput(); const [videoInputSetting, setVideoInputSetting] = useVideoInput();
@@ -136,7 +146,8 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
const audioOutput = useMediaDevice( const audioOutput = useMediaDevice(
"audiooutput", "audiooutput",
audioOutputSetting, audioOutputSetting,
usingNames useOutputNames,
alwaysUseDefaultAudio
); );
const videoInput = useMediaDevice( const videoInput = useMediaDevice(
"videoinput", "videoinput",
@@ -150,7 +161,9 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
}, [setAudioInputSetting, audioInput.selectedId]); }, [setAudioInputSetting, audioInput.selectedId]);
useEffect(() => { useEffect(() => {
if (audioOutput.selectedId !== undefined) // Skip setting state for ff output. Redundent since it is set to always return 'undefined'
// but makes it clear while debugging that this is not happening on FF. + perf ;)
if (audioOutput.selectedId !== undefined && !isFirefox())
setAudioOutputSetting(audioOutput.selectedId); setAudioOutputSetting(audioOutput.selectedId);
}, [setAudioOutputSetting, audioOutput.selectedId]); }, [setAudioOutputSetting, audioOutput.selectedId]);

View File

@@ -224,7 +224,8 @@ export function GroupCallView({
leaveRTCSession(rtcSession); leaveRTCSession(rtcSession);
if (widget) { if (widget) {
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked. // we need to wait until the callEnded event is tracked on posthog.
// Otherwise the iFrame gets killed before the callEnded event got tracked.
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
widget.api.setAlwaysOnScreen(false); widget.api.setAlwaysOnScreen(false);
PosthogAnalytics.instance.logout(); PosthogAnalytics.instance.logout();

View File

@@ -35,6 +35,7 @@ import {
useDeveloperSettingsTab, useDeveloperSettingsTab,
useShowConnectionStats, useShowConnectionStats,
useEnableE2EE, useEnableE2EE,
isFirefox,
} from "./useSetting"; } from "./useSetting";
import { FieldRow, InputField } from "../input/Input"; import { FieldRow, InputField } from "../input/Input";
import { Button } from "../button"; import { Button } from "../button";
@@ -130,7 +131,8 @@ export const SettingsModal = (props: Props) => {
} }
> >
{generateDeviceSelection(devices.audioInput, t("Microphone"))} {generateDeviceSelection(devices.audioInput, t("Microphone"))}
{generateDeviceSelection(devices.audioOutput, t("Speaker"))} {!isFirefox() &&
generateDeviceSelection(devices.audioOutput, t("Speaker"))}
</TabItem> </TabItem>
); );

View File

@@ -58,8 +58,12 @@ export const getSetting = <T>(name: string, defaultValue: T): T => {
export const setSetting = <T>(name: string, newValue: T) => export const setSetting = <T>(name: string, newValue: T) =>
setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue)); setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue));
const canEnableSpatialAudio = () => { export const isFirefox = () => {
const { userAgent } = navigator; const { userAgent } = navigator;
return userAgent.includes("Firefox");
};
const canEnableSpatialAudio = () => {
// Spatial audio means routing audio through audio contexts. On Chrome, // Spatial audio means routing audio through audio contexts. On Chrome,
// this bypasses the AEC processor and so breaks echo cancellation. // this bypasses the AEC processor and so breaks echo cancellation.
// We only allow spatial audio to be enabled on Firefox which we know // We only allow spatial audio to be enabled on Firefox which we know
@@ -69,7 +73,7 @@ const canEnableSpatialAudio = () => {
// widely enough, we can allow spatial audio everywhere. It's currently in a // widely enough, we can allow spatial audio everywhere. It's currently in a
// chrome flag, so we could enable this in Electron if we enabled the chrome flag // chrome flag, so we could enable this in Electron if we enabled the chrome flag
// in the Electron wrapper. // in the Electron wrapper.
return userAgent.includes("Firefox"); return isFirefox();
}; };
export const useSpatialAudio = (): DisableableSetting<boolean> => { export const useSpatialAudio = (): DisableableSetting<boolean> => {