diff --git a/src/state/CallViewModel/localMember/Publisher.ts b/src/state/CallViewModel/localMember/Publisher.ts index 3b6b8b8a..fd82b767 100644 --- a/src/state/CallViewModel/localMember/Publisher.ts +++ b/src/state/CallViewModel/localMember/Publisher.ts @@ -440,6 +440,7 @@ export class Publisher { let transformer: NoiseGateTransformer | null = null; let audioCtx: AudioContext | null = null; let vadGate: SileroVADGate | null = null; + let rawMicTrack: MediaStreamTrack | null = null; const currentParams = (): NoiseGateParams => ({ threshold: noiseGateThreshold.getValue(), @@ -460,14 +461,8 @@ export class Publisher { transformer?.setVADOpen(true); }; - const startVAD = (track: LocalAudioTrack, ctx: AudioContext): void => { + const startVAD = (rawTrack: MediaStreamTrack, ctx: AudioContext): void => { stopVAD(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const rawTrack: MediaStreamTrack | undefined = (track as any).mediaStreamTrack; - if (!rawTrack) { - this.logger.warn("[VAD] no underlying MediaStreamTrack — skipping VAD"); - return; - } const stream = new MediaStream([rawTrack]); vadGate = new SileroVADGate(stream, ctx, { positiveThreshold: vadPositiveThreshold.getValue(), @@ -488,6 +483,9 @@ export class Publisher { if (enabled && !audioTrack.getProcessor()) { const params = currentParams(); this.logger.info("[NoiseGate] attaching processor, params:", params); + // Capture the raw mic track BEFORE setProcessor replaces it + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rawMicTrack = (audioTrack as any).mediaStreamTrack ?? null; transformer = new NoiseGateTransformer(params); audioCtx = new AudioContext(); this.logger.info("[NoiseGate] AudioContext state before resume:", audioCtx.state); @@ -500,7 +498,7 @@ export class Publisher { .setProcessor(transformer as any); }).then(() => { this.logger.info("[NoiseGate] setProcessor resolved"); - if (vadEnabled.getValue() && audioCtx) startVAD(audioTrack, audioCtx); + if (vadEnabled.getValue() && audioCtx && rawMicTrack) startVAD(rawMicTrack, audioCtx); }).catch((e: unknown) => { this.logger.error("[NoiseGate] setProcessor failed", e); }); @@ -511,6 +509,7 @@ export class Publisher { void audioCtx?.close(); audioCtx = null; transformer = null; + rawMicTrack = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any (audioTrack as any).setAudioContext(undefined); } else { @@ -522,9 +521,9 @@ export class Publisher { combineLatest([audioTrack$, vadEnabled.value$]) .pipe(scope.bind()) .subscribe(([audioTrack, enabled]) => { - if (!audioTrack || !audioCtx) return; + if (!audioCtx || !rawMicTrack) return; if (enabled) { - startVAD(audioTrack, audioCtx); + startVAD(rawMicTrack, audioCtx); } else { stopVAD(); }