From edb56f28f5347f6c38048466319d379d2efd627a Mon Sep 17 00:00:00 2001 From: jonadimovska Date: Mon, 24 Nov 2025 00:22:26 +0100 Subject: [PATCH 1/2] fix: Fix noise cancellation toggle before krisp initialization - await ready promise before enabling /disabling --- .../src/NoiseCancellation.ts | 21 +++++++++++++------ .../client/src/devices/MicrophoneManager.ts | 19 +++++++++++++---- .../__tests__/NoiseCancellationStub.ts | 8 +++++-- .../src/index.ts | 8 +++---- .../src/types.ts | 4 ++-- .../NoiseCancellationProvider.tsx | 8 +++++-- .../NoiseCancellationProvider.tsx | 8 +++++-- 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/packages/audio-filters-web/src/NoiseCancellation.ts b/packages/audio-filters-web/src/NoiseCancellation.ts index de61946c6c..4e2daf30c3 100644 --- a/packages/audio-filters-web/src/NoiseCancellation.ts +++ b/packages/audio-filters-web/src/NoiseCancellation.ts @@ -51,8 +51,8 @@ export interface INoiseCancellation { init: (options?: { tracer?: Tracer }) => Promise; isEnabled: () => Promise; canAutoEnable?: () => Promise; - enable: () => void; - disable: () => void; + enable: () => Promise; + disable: () => Promise; dispose: () => Promise; resume: () => void; setSuppressionLevel: (level: number) => void; @@ -87,6 +87,7 @@ export class NoiseCancellation implements INoiseCancellation { private audioContext?: AudioContext; private restoreTimeoutId?: number; private tracer?: Tracer; + private isReady?: Promise; private readonly basePath: string; private readonly restoreTimeoutMs: number; @@ -188,6 +189,7 @@ export class NoiseCancellation implements INoiseCancellation { ); filterNode.addEventListener('buffer_overflow', this.handleBufferOverflow); this.filterNode = filterNode; + this.isReady = ready; return ready; }; @@ -202,8 +204,9 @@ export class NoiseCancellation implements INoiseCancellation { /** * Enables the noise cancellation. */ - enable = () => { + enable = async () => { if (!this.filterNode) return; + await this.isReady; this.filterNode.enable(); this.dispatch('change', true); }; @@ -211,8 +214,9 @@ export class NoiseCancellation implements INoiseCancellation { /** * Disables the noise cancellation. */ - disable = () => { + disable = async () => { if (!this.filterNode) return; + await this.isReady; this.filterNode.disable(); this.dispatch('change', false); }; @@ -241,6 +245,7 @@ export class NoiseCancellation implements INoiseCancellation { this.sdk.dispose(); this.sdk = undefined; } + this.isReady = undefined; }; /** @@ -352,11 +357,15 @@ export class NoiseCancellation implements INoiseCancellation { this.tracer?.trace('noiseCancellation.bufferOverflowCount', String(count)); window.clearTimeout(this.restoreTimeoutId); - this.disable(); + this.disable().catch((err) => + console.error('Failed to disable noise cancellation ', err), + ); if (count < this.restoreAttempts) { this.restoreTimeoutId = window.setTimeout(() => { - this.enable(); + this.enable().catch((err) => + console.error('Failed to enable noise cancellation ', err), + ); }, this.restoreTimeoutMs); } }; diff --git a/packages/client/src/devices/MicrophoneManager.ts b/packages/client/src/devices/MicrophoneManager.ts index a97b2b3c04..253f178563 100644 --- a/packages/client/src/devices/MicrophoneManager.ts +++ b/packages/client/src/devices/MicrophoneManager.ts @@ -92,7 +92,9 @@ export class MicrophoneManager extends AudioDeviceManager { if (canAutoEnable) { - this.noiseCancellation?.enable(); + this.noiseCancellation?.enable().catch((err) => { + this.logger.warn('Failed to enable noise cancellation', err); + }); } }) .catch((err) => { @@ -175,7 +177,9 @@ export class MicrophoneManager extends AudioDeviceManager { + this.logger.warn('Failed to enable noise cancellation', err); + }); } } } catch (e) { @@ -268,9 +272,16 @@ export class MicrophoneManager extends AudioDeviceManager { + this.logger.warn( + 'Failed to disable noise cancellation for music mode', + err, + ); + }); // disable for high quality music mode } else { - this.noiseCancellation.enable(); // restore it for other modes if available + this.noiseCancellation.enable().catch((err) => { + this.logger.warn('Failed to enable noise cancellation', err); + }); // restore it for other modes if available } } } diff --git a/packages/client/src/devices/__tests__/NoiseCancellationStub.ts b/packages/client/src/devices/__tests__/NoiseCancellationStub.ts index 7bd1d3283c..af21002c25 100644 --- a/packages/client/src/devices/__tests__/NoiseCancellationStub.ts +++ b/packages/client/src/devices/__tests__/NoiseCancellationStub.ts @@ -7,8 +7,12 @@ export class NoiseCancellationStub implements INoiseCancellation { isSupported = () => true; init = () => Promise.resolve(undefined); isEnabled = async () => true; - enable = () => this.listeners['change']?.forEach((l) => l(true)); - disable = () => this.listeners['change']?.forEach((l) => l(false)); + enable = async () => { + this.listeners['change']?.forEach((l) => l(true)); + }; + disable = async () => { + this.listeners['change']?.forEach((l) => l(false)); + }; setSuppressionLevel = () => {}; dispose = () => Promise.resolve(undefined); toFilter = () => (ms: MediaStream) => ({ output: ms }); diff --git a/packages/noise-cancellation-react-native/src/index.ts b/packages/noise-cancellation-react-native/src/index.ts index 3998788c71..669fcdca2d 100644 --- a/packages/noise-cancellation-react-native/src/index.ts +++ b/packages/noise-cancellation-react-native/src/index.ts @@ -41,16 +41,16 @@ export class NoiseCancellation implements INoiseCancellation { /** * Enables the noise cancellation. */ - enable = () => { - NoiseCancellationReactNative.setEnabled(true); + enable = async () => { + await NoiseCancellationReactNative.setEnabled(true); this.dispatch('change', true); }; /** * Disables the noise cancellation. */ - disable = () => { - NoiseCancellationReactNative.setEnabled(false); + disable = async () => { + await NoiseCancellationReactNative.setEnabled(false); this.dispatch('change', false); }; diff --git a/packages/noise-cancellation-react-native/src/types.ts b/packages/noise-cancellation-react-native/src/types.ts index ab9aed41a2..541fdcdc47 100644 --- a/packages/noise-cancellation-react-native/src/types.ts +++ b/packages/noise-cancellation-react-native/src/types.ts @@ -3,8 +3,8 @@ export interface INoiseCancellation { init: () => Promise; canAutoEnable?: () => Promise; isEnabled: () => Promise; - enable: () => void; - disable: () => void; + enable: () => Promise; + disable: () => Promise; dispose: () => Promise; resume: () => void; setSuppressionLevel: (level: number) => void; diff --git a/packages/react-native-sdk/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx b/packages/react-native-sdk/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx index 114a37f4cc..5c48041761 100644 --- a/packages/react-native-sdk/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx +++ b/packages/react-native-sdk/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx @@ -130,9 +130,13 @@ export const NoiseCancellationProvider = (props: PropsWithChildren<{}>) => { ? enabledOrSetter(isEnabled) : enabledOrSetter; if (enable) { - ncInstance.enable(); + ncInstance.enable().catch((err) => { + console.error('Failed to enable noise cancellation', err); + }); } else { - ncInstance.disable(); + ncInstance.disable().catch((err) => { + console.error('Failed to disable noise cancellation', err); + }); } }, }} diff --git a/packages/react-sdk/src/components/NoiseCancellation/NoiseCancellationProvider.tsx b/packages/react-sdk/src/components/NoiseCancellation/NoiseCancellationProvider.tsx index a1443639d1..ce03f55ed8 100644 --- a/packages/react-sdk/src/components/NoiseCancellation/NoiseCancellationProvider.tsx +++ b/packages/react-sdk/src/components/NoiseCancellation/NoiseCancellationProvider.tsx @@ -138,9 +138,13 @@ export const NoiseCancellationProvider = ( ? enabledOrSetter(isEnabled) : enabledOrSetter; if (enable) { - noiseCancellation.enable(); + noiseCancellation.enable().catch((err) => { + console.error('Failed to enable noise cancellation', err); + }); } else { - noiseCancellation.disable(); + noiseCancellation.disable().catch((err) => { + console.error('Failed to disable noise cancellation', err); + }); } }, }), From 8d2d5912286a174187c83868464251c3e1591811 Mon Sep 17 00:00:00 2001 From: jonadimovska Date: Mon, 24 Nov 2025 12:28:08 +0100 Subject: [PATCH 2/2] fix(react): Fix InvalidStateException when track is stopped - fix race condition --- .../src/NoiseCancellation.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/audio-filters-web/src/NoiseCancellation.ts b/packages/audio-filters-web/src/NoiseCancellation.ts index 4e2daf30c3..4b66b95201 100644 --- a/packages/audio-filters-web/src/NoiseCancellation.ts +++ b/packages/audio-filters-web/src/NoiseCancellation.ts @@ -87,7 +87,9 @@ export class NoiseCancellation implements INoiseCancellation { private audioContext?: AudioContext; private restoreTimeoutId?: number; private tracer?: Tracer; - private isReady?: Promise; + + private readonly initializing: Promise; + private readonly resolveInitialized!: () => void; private readonly basePath: string; private readonly restoreTimeoutMs: number; @@ -105,6 +107,10 @@ export class NoiseCancellation implements INoiseCancellation { restoreAttempts = 3, krispSDKParams, }: NoiseCancellationOptions = {}) { + const { promise, resolve } = promiseWithResolvers(); + this.initializing = promise; + this.resolveInitialized = resolve; + this.basePath = basePath; this.restoreTimeoutMs = restoreTimeoutMs; this.restoreAttempts = restoreAttempts; @@ -178,19 +184,18 @@ export class NoiseCancellation implements INoiseCancellation { document.addEventListener('click', resume); } - const { promise: ready, resolve: filterReady } = promiseWithResolvers(); const filterNode = await sdk.createNoiseFilter( this.audioContext, () => { this.tracer?.trace('noiseCancellation.started', 'true'); - filterReady(); + this.resolveInitialized(); }, () => document.removeEventListener('click', resume), ); filterNode.addEventListener('buffer_overflow', this.handleBufferOverflow); this.filterNode = filterNode; - this.isReady = ready; - return ready; + + return this.initializing; }; /** @@ -206,7 +211,7 @@ export class NoiseCancellation implements INoiseCancellation { */ enable = async () => { if (!this.filterNode) return; - await this.isReady; + await this.initializing; this.filterNode.enable(); this.dispatch('change', true); }; @@ -216,7 +221,7 @@ export class NoiseCancellation implements INoiseCancellation { */ disable = async () => { if (!this.filterNode) return; - await this.isReady; + await this.initializing; this.filterNode.disable(); this.dispatch('change', false); }; @@ -245,7 +250,6 @@ export class NoiseCancellation implements INoiseCancellation { this.sdk.dispose(); this.sdk = undefined; } - this.isReady = undefined; }; /**