Firefox audio output issues fix (#1510)
--------- Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
@@ -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)
|
||||||
available,
|
|
||||||
selectedId: available.some((d) => d.deviceId === selectedId)
|
|
||||||
? selectedId
|
? selectedId
|
||||||
: available.some((d) => d.deviceId === fallbackDevice)
|
: available.some((d) => d.deviceId === fallbackDevice)
|
||||||
? fallbackDevice
|
? fallbackDevice
|
||||||
: available.at(0)?.deviceId,
|
: available.at(0)?.deviceId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
available,
|
||||||
|
selectedId: alwaysDefault ? undefined : devId,
|
||||||
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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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> => {
|
||||||
|
|||||||
Reference in New Issue
Block a user