Disable device switching when in controlled audio devices mode (#3290)

* Disable device switching when in controlled audio devices mode

* Temporarily switch matrix-js-sdk to robin/embedded-no-update-state

To allow us to test this change on Element X, which does not yet support the update_state action.

* Also add a check for controlled audio devices in useAudioContext

* use develop branch

* fix tests

---------

Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
Timo
2025-05-23 17:54:47 +02:00
committed by Timo
parent c2ce1fd382
commit 8f424452fc
4 changed files with 32 additions and 12 deletions

View File

@@ -42,6 +42,7 @@ import {
} from "./TrackProcessorContext";
import { useInitial } from "../useInitial";
import { observeTrackReference$ } from "../state/MediaViewModel";
import { useUrlParams } from "../UrlParams";
interface UseLivekitResult {
livekitRoom?: Room;
@@ -54,6 +55,8 @@ export function useLivekit(
sfuConfig: SFUConfig | undefined,
e2eeSystem: EncryptionSystem,
): UseLivekitResult {
const { controlledAudioDevices } = useUrlParams();
const e2eeOptions = useMemo((): E2EEManagerOptions | undefined => {
if (e2eeSystem.kind === E2eeType.NONE) return undefined;
@@ -303,7 +306,11 @@ export function useLivekit(
useEffect(() => {
// Sync the requested devices with LiveKit's devices
if (room !== undefined && connectionState === ConnectionState.Connected) {
if (
room !== undefined &&
connectionState === ConnectionState.Connected &&
!controlledAudioDevices
) {
const syncDevice = (
kind: MediaDeviceKind,
device: MediaDeviceHandle,
@@ -363,7 +370,7 @@ export function useLivekit(
syncDevice("audiooutput", devices.audioOutput);
syncDevice("videoinput", devices.videoInput);
}
}, [room, devices, connectionState]);
}, [room, devices, connectionState, controlledAudioDevices]);
return {
connState: connectionState,

View File

@@ -9,6 +9,7 @@ import { expect, vi, afterEach, beforeEach, test } from "vitest";
import { type FC } from "react";
import { render } from "@testing-library/react";
import userEvent, { type UserEvent } from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import { deviceStub, MediaDevicesContext } from "./livekit/MediaDevicesContext";
import { useAudioContext } from "./useAudioContext";
@@ -38,6 +39,13 @@ const TestComponent: FC = () => {
</>
);
};
const TestComponentWrapper: FC = () => {
return (
<BrowserRouter>
<TestComponent />
</BrowserRouter>
);
};
const gainNode = vi.mocked(
{
@@ -94,13 +102,13 @@ afterEach(() => {
});
test("can play a single sound", async () => {
const { findByText } = render(<TestComponent />);
const { findByText } = render(<TestComponentWrapper />);
await user.click(await findByText("Valid sound"));
expect(testAudioContext.createBufferSource).toHaveBeenCalledOnce();
});
test("will ignore sounds that are not registered", async () => {
const { findByText } = render(<TestComponent />);
const { findByText } = render(<TestComponentWrapper />);
await user.click(await findByText("Invalid sound"));
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
});
@@ -122,7 +130,7 @@ test("will use the correct device", () => {
stopUsingDeviceNames: () => {},
}}
>
<TestComponent />
<TestComponentWrapper />
</MediaDevicesContext.Provider>,
);
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
@@ -131,7 +139,7 @@ test("will use the correct device", () => {
test("will use the correct volume level", async () => {
soundEffectVolumeSetting.setValue(0.33);
const { findByText } = render(<TestComponent />);
const { findByText } = render(<TestComponentWrapper />);
await user.click(await findByText("Valid sound"));
expect(testAudioContext.gain.gain.setValueAtTime).toHaveBeenCalledWith(
0.33,
@@ -157,7 +165,7 @@ test("will use the pan if earpiece is selected", async () => {
stopUsingDeviceNames: () => {},
}}
>
<TestComponent />
<TestComponentWrapper />
</MediaDevicesContext.Provider>,
);
await user.click(await findByText("Valid sound"));

View File

@@ -17,6 +17,7 @@ import {
useMediaDevices,
} from "./livekit/MediaDevicesContext";
import { type PrefetchedSounds } from "./soundUtils";
import { useUrlParams } from "./UrlParams";
/**
* Play a sound though a given AudioContext. Will take
@@ -71,7 +72,7 @@ export function useAudioContext<S extends string>(
): UseAudioContext<S> | null {
const [soundEffectVolume] = useSetting(soundEffectVolumeSetting);
const { audioOutput } = useMediaDevices();
const { controlledAudioDevices } = useUrlParams();
const [audioContext, setAudioContext] = useState<AudioContext>();
const [audioBuffers, setAudioBuffers] = useState<Record<S, AudioBuffer>>();
@@ -110,14 +111,18 @@ export function useAudioContext<S extends string>(
// Update the sink ID whenever we change devices.
useEffect(() => {
if (audioContext && "setSinkId" in audioContext) {
if (
audioContext &&
"setSinkId" in audioContext &&
!controlledAudioDevices
) {
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId
// @ts-expect-error - setSinkId doesn't exist yet in types, maybe because it's not supported everywhere.
audioContext.setSinkId(audioOutput.selectedId).catch((ex) => {
logger.warn("Unable to change sink for audio context", ex);
});
}
}, [audioContext, audioOutput.selectedId]);
}, [audioContext, audioOutput.selectedId, controlledAudioDevices]);
const { pan: earpiecePan, volume: earpieceVolume } = useEarpieceAudioConfig();
// Don't return a function until we're ready.

View File

@@ -9612,7 +9612,7 @@ __metadata:
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=develop":
version: 37.6.0
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=93982716951ce2583904bfc26b27d6a86ba17a87"
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=bf6dc16ad32d47f2c6e167236f4c853ceef01d4f"
dependencies:
"@babel/runtime": "npm:^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^14.2.0"
@@ -9629,7 +9629,7 @@ __metadata:
sdp-transform: "npm:^2.14.1"
unhomoglyph: "npm:^1.0.6"
uuid: "npm:11"
checksum: 10c0/22a28099d2deaf0ca7f609a5859fe00fbd20c314d3b607a95b4a623a8aa159e428310b1849c77ab7b9875a29fc2d924cddbb6fc83dc4e42a74c4713401dcb0be
checksum: 10c0/2877e7c5b2779200b48f3152bb7b510e58899b1e779e7e6d2bc1a236ed178fa8858f0547b644a2029f48f55dc7cc3954f48e2598c8963d45293c8280ccc23039
languageName: node
linkType: hard