Files
element-call/src/lotus/lotusAudioConstraints.test.ts
T

131 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-09-12 11:35:57 -04:00
/*
Copyright 2026 Lotus Guild
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { Room as LivekitRoom } from "livekit-client";
import { BehaviorSubject } from "rxjs";
import { logger } from "matrix-js-sdk/lib/logger";
import EventEmitter from "events";
import { ObservableScope } from "../state/ObservableScope.ts";
import { ECConnectionFactory } from "../state/CallViewModel/remoteMembers/ConnectionFactory.ts";
import type { OpenIDClientParts } from "../livekit/openIDSFU.ts";
import {
exampleTransport,
mockMediaDevices,
ownMemberMock,
} from "../utils/test.ts";
import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx";
import {
autoGainControlSetting,
echoCancellationSetting,
noiseSuppressionSetting,
} from "../settings/settings.ts";
// [lotus] Upstream v0.25.0 moved the audio-capture constraints to Settings.
// The Lotus host still drives them per-call via URL params (it turns browser
// noiseSuppression/AGC OFF for the in-source ML denoise tier), so
// ConnectionFactory ANDs the Setting with the URL param. These tests pin that
// contract.
const { getUrlParams } = vi.hoisted(() => ({ getUrlParams: vi.fn() }));
vi.mock("../UrlParams", () => ({ getUrlParams }));
vi.mock("livekit-client", async (importOriginal) => ({
...(await importOriginal()),
Room: vi.fn().mockImplementation(function (this: LivekitRoom) {
const emitter = new EventEmitter();
return {
on: emitter.on.bind(emitter),
off: emitter.off.bind(emitter),
emit: emitter.emit.bind(emitter),
disconnect: vi.fn(),
remoteParticipants: new Map(),
} as unknown as LivekitRoom;
}),
}));
let testScope: ObservableScope;
const mockClient: OpenIDClientParts = {
getOpenIdToken: vi.fn().mockReturnValue(""),
getDeviceId: vi.fn().mockReturnValue("DEV000"),
};
function createRoom(): void {
new ECConnectionFactory(
mockClient,
"!roomid:example.org",
mockMediaDevices({}),
new BehaviorSubject<ProcessorState>({
supported: true,
processor: undefined,
}),
undefined,
false,
).createConnection(testScope, exampleTransport, ownMemberMock, logger);
}
function capturedAudioDefaults(): Record<string, unknown> {
const RoomConstructor = vi.mocked(LivekitRoom);
const options = RoomConstructor.mock.calls.at(-1)?.[0];
return (options?.audioCaptureDefaults ?? {}) as Record<string, unknown>;
}
beforeEach(() => {
testScope = new ObservableScope();
echoCancellationSetting.setValue(true);
noiseSuppressionSetting.setValue(true);
autoGainControlSetting.setValue(true);
});
afterEach(() => {
testScope.end();
vi.mocked(LivekitRoom).mockClear();
});
describe("[lotus] audio-capture URL param overrides", () => {
test("with params defaulted to true, the Settings govern (upstream behaviour)", () => {
getUrlParams.mockReturnValue({
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
});
autoGainControlSetting.setValue(false);
createRoom();
expect(capturedAudioDefaults()).toMatchObject({
echoCancellation: true,
noiseSuppression: true,
autoGainControl: false,
});
});
test("host-passed noiseSuppression=false / autoGainControl=false force OFF regardless of Settings", () => {
getUrlParams.mockReturnValue({
echoCancellation: true,
noiseSuppression: false,
autoGainControl: false,
});
createRoom();
expect(capturedAudioDefaults()).toMatchObject({
echoCancellation: true,
noiseSuppression: false,
autoGainControl: false,
});
});
test("a URL param cannot force a constraint ON when the Setting is off", () => {
getUrlParams.mockReturnValue({
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
});
echoCancellationSetting.setValue(false);
createRoom();
expect(capturedAudioDefaults()).toMatchObject({ echoCancellation: false });
});
});