feat(lotus-denoise): AGC off for ML tier + init/build leak hardening
CI / Build embedded bundle (push) Successful in 2m34s
CI / Publish to Gitea npm registry (push) Has been skipped

AEC/AGC audit fix + two hardening items from the engine review.

- Add an `autoGainControl` capture param (UrlParams -> CallViewModel ->
  ConnectionFactory audioCaptureDefaults), mirroring echoCancellation/
  noiseSuppression. Defaults true (unchanged); the host sets it false only for
  the ML tier so the browser's auto gain control doesn't fight the in-source ML
  denoiser (pumping). Echo cancellation stays on. Tests cover the URL parse and
  the audioCaptureDefaults wiring.
- L1: init() now closes the owned AudioContext on a build failure (was orphaned;
  browsers cap live contexts, so repeated failures could exhaust them).
- L2: buildGraph() disposes its partially-built nodes on failure (disposeGraph
  previously only cleaned the prior graph).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lotus CI
2026-07-01 00:46:39 -04:00
co-authored by Claude Opus 4.8
parent 6ab52d9926
commit 940d71da92
6 changed files with 117 additions and 64 deletions
+1
View File
@@ -504,6 +504,7 @@ export function createCallViewModel$(
options.livekitRoomFactory,
getUrlParams().echoCancellation,
getUrlParams().noiseSuppression,
getUrlParams().autoGainControl,
);
const connectionManager = createConnectionManager$({
@@ -55,6 +55,7 @@ export class ECConnectionFactory implements ConnectionFactory {
* @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used.
* @param echoCancellation - Whether to enable echo cancellation for audio capture.
* @param noiseSuppression - Whether to enable noise suppression for audio capture.
* @param autoGainControl - Whether to enable auto gain control for audio capture.
*/
public constructor(
private client: OpenIDClientParts,
@@ -66,6 +67,7 @@ export class ECConnectionFactory implements ConnectionFactory {
livekitRoomFactory?: () => LivekitRoom,
echoCancellation: boolean = true,
noiseSuppression: boolean = true,
autoGainControl: boolean = true,
) {
const defaultFactory = (): LivekitRoom =>
new LivekitRoom(
@@ -81,6 +83,7 @@ export class ECConnectionFactory implements ConnectionFactory {
controlledAudioDevices: this.controlledAudioDevices,
echoCancellation,
noiseSuppression,
autoGainControl,
}),
);
this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory;
@@ -127,6 +130,7 @@ function generateRoomOption({
controlledAudioDevices,
echoCancellation,
noiseSuppression,
autoGainControl,
}: {
devices: MediaDevices;
processorState: ProcessorState;
@@ -137,6 +141,7 @@ function generateRoomOption({
controlledAudioDevices: boolean;
echoCancellation: boolean;
noiseSuppression: boolean;
autoGainControl: boolean;
}): RoomOptions {
return {
...defaultLiveKitOptions,
@@ -150,6 +155,7 @@ function generateRoomOption({
deviceId: devices.audioInput.selected$.value?.id,
echoCancellation,
noiseSuppression,
autoGainControl,
},
audioOutput: {
// When using controlled audio devices, we don't want to set the
@@ -53,14 +53,13 @@ beforeEach(() => {
describe("ECConnectionFactory - Audio inputs options", () => {
test.each([
{ echo: true, noise: true },
{ echo: true, noise: false },
{ echo: false, noise: true },
{ echo: false, noise: false },
{ echo: true, noise: true, agc: true },
{ echo: true, noise: false, agc: false },
{ echo: false, noise: true, agc: false },
{ echo: false, noise: false, agc: true },
])(
"it sets echoCancellation=$echo and noiseSuppression=$noise based on constructor parameters",
({ echo, noise }) => {
// test("it sets echoCancellation and noiseSuppression based on constructor parameters", () => {
"it sets echoCancellation=$echo, noiseSuppression=$noise, autoGainControl=$agc based on constructor parameters",
({ echo, noise, agc }) => {
const RoomConstructor = vi.mocked(LivekitRoom);
const ecConnectionFactory = new ECConnectionFactory(
@@ -76,6 +75,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
undefined,
echo,
noise,
agc,
);
ecConnectionFactory.createConnection(
testScope,
@@ -90,6 +90,7 @@ describe("ECConnectionFactory - Audio inputs options", () => {
audioCaptureDefaults: expect.objectContaining({
echoCancellation: echo,
noiseSuppression: noise,
autoGainControl: agc,
}),
}),
);