diff --git a/frontend/src/hooks/useMediaStreamASR.ts b/frontend/src/hooks/useMediaStreamASR.ts index 31766b0..38c3283 100644 --- a/frontend/src/hooks/useMediaStreamASR.ts +++ b/frontend/src/hooks/useMediaStreamASR.ts @@ -28,6 +28,7 @@ export function useMediaStreamASR({ wsUrl }: UseMediaStreamASRProps): UseMediaSt const wsRef = useRef(null) const audioContextRef = useRef(null) const processorRef = useRef(null) + const gainNodeRef = useRef(null) const sourceRef = useRef(null) const streamRef = useRef(null) const isStreamingRef = useRef(false) @@ -63,8 +64,10 @@ export function useMediaStreamASR({ wsUrl }: UseMediaStreamASRProps): UseMediaSt } processorRef.current?.disconnect() + gainNodeRef.current?.disconnect() sourceRef.current?.disconnect() processorRef.current = null + gainNodeRef.current = null sourceRef.current = null if (wsRef.current) { @@ -114,18 +117,22 @@ export function useMediaStreamASR({ wsUrl }: UseMediaStreamASRProps): UseMediaSt const processor = audioContext.createScriptProcessor(4096, 1, 1) processorRef.current = processor - // onaudioprocess — mirrors useVideoASR lines 126-132 exactly + // Zero-gain node mutes audio output to prevent echo/feedback. The processor + // must remain in the graph (connected to destination) so onaudioprocess fires. + const zeroGain = audioContext.createGain() + zeroGain.gain.value = 0 + gainNodeRef.current = zeroGain + processor.onaudioprocess = (e) => { const float32Data = e.inputBuffer.getChannelData(0) - const outputData = e.outputBuffer.getChannelData(0) - outputData.set(float32Data) if (!isStreamingRef.current) return if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return wsRef.current.send(float32Data.buffer) } source.connect(processor) - processor.connect(audioContext.destination) + processor.connect(zeroGain) + zeroGain.connect(audioContext.destination) const ws = new WebSocket(wsUrl) wsRef.current = ws @@ -181,6 +188,7 @@ export function useMediaStreamASR({ wsUrl }: UseMediaStreamASRProps): UseMediaSt }) } processorRef.current?.disconnect() + gainNodeRef.current?.disconnect() sourceRef.current?.disconnect() wsRef.current?.close() audioContextRef.current?.close()