Skip to content

Commit 652b86c

Browse files
authored
Fix local VAD flickering on cleanup (#520)
## Description `useLocalVAD` polls `getLocalTrackAudioLevel` in a `setTimeout` loop. On cleanup it calls `clearTimeout`, but that can't cancel a poll that's already mid-`await`. The in-flight promise still resolves after cleanup runs, calls `setIsSpeaking`, and schedules a fresh `setTimeout` so `isSpeaking` flickers and the poll loop leaks past the disabled state. Fix: added an `AbortController` and check `signal.aborted` around each `await` boundary. The poll exits cleanly on cleanup and won't schedule another timeout. ## Motivation and Context `isSpeaking` was flickering when the hook got toggled off or the component unmounted, and a stale poll could keep running against aborted state. Users saw the speaking indicator flash briefly after going silent or leaving the room. ## Documentation impact - [ ] Documentation update required - [ ] Documentation updated [in another PR](_) - [x] No documentation update required ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
1 parent ae22852 commit 652b86c

1 file changed

Lines changed: 9 additions & 1 deletion

File tree

packages/react-client/src/hooks/useLocalVAD.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@ export const useLocalVAD = (options: { disabled: boolean }): Record<PeerId, bool
3838
if (options.disabled || !localPeerId || !microphoneTrackId) return;
3939

4040
let silenceTicks = 0;
41-
let timeoutId: ReturnType<typeof setTimeout>;
41+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
42+
const controller = new AbortController();
43+
const { signal } = controller;
4244

4345
const poll = async () => {
46+
if (signal.aborted) return;
47+
4448
const trackAudio = await fishjamClient?.current?.getLocalTrackAudioLevel(microphoneTrackId);
49+
if (signal.aborted) return;
50+
4551
if (trackAudio != null && trackAudio.level > THRESHOLD) {
4652
silenceTicks = 0;
4753
setIsSpeaking(true);
@@ -52,12 +58,14 @@ export const useLocalVAD = (options: { disabled: boolean }): Record<PeerId, bool
5258
}
5359
}
5460

61+
if (signal.aborted) return;
5562
timeoutId = setTimeout(poll, 100);
5663
};
5764

5865
timeoutId = setTimeout(poll, 0);
5966

6067
return () => {
68+
controller.abort();
6169
clearTimeout(timeoutId);
6270
setIsSpeaking(false);
6371
};

0 commit comments

Comments
 (0)