Skip to content

Commit 34b330a

Browse files
committed
fix(react): guard processor cast and detach on cleanup
Two correctness fixes from PR review: - Guard the `KrispNoiseFilterProcessor` cast in the returned `processor` value with a `name === 'livekit-noise-filter'` check. The unconditional cast assumed nothing else could attach a `TrackProcessor` to the track; if anything did, the hook handed consumers a value typed as a Krisp processor that wasn't one. - Add a separate cleanup effect that detaches the processor we own when the underlying track changes or the hook unmounts. Tracks the attached processor + its track via refs so cleanup can verify identity (`track.getProcessor() === processor`) before calling `stopProcessor()` — avoids accidentally detaching a Krisp processor that some other module owns. Refs are populated only after `setProcessor` lands, so a cancelled mid-attach leaves them clear. Closes the gap where the track outlives the hook (caller-owned `trackRef` unmount or in-place swap to a different live track), while leaving the existing toggle-reuse path on `shouldEnable` unchanged.
1 parent 63fa161 commit 34b330a

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

.changeset/krisp-processor-per-track.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'@livekit/components-react': patch
33
---
44

5-
Fix `useKrispNoiseFilter` throwing `InvalidAccessError` on the second session when `Room.disconnect()` + reconnect republishes the microphone. The hook now treats `LocalAudioTrack.getProcessor()` as the source of truth and creates a fresh `KrispNoiseFilterProcessor` per track, avoiding reuse of a processor whose internal audio graph is bound to a now-closed `AudioContext`.
5+
Fix `useKrispNoiseFilter` throwing `InvalidAccessError` on the second session when `Room.disconnect()` + reconnect republishes the microphone. The hook now treats `LocalAudioTrack.getProcessor()` as the source of truth and creates a fresh `KrispNoiseFilterProcessor` per track, avoiding reuse of a processor whose internal audio graph is bound to a now-closed `AudioContext`. Also tightens the cancellation race when `setProcessor` resolves after the effect was cancelled, guards the returned `processor` against non-Krisp `TrackProcessor`s set by other modules, and detaches the processor on track change / unmount so a caller-owned `trackRef` is left in the state it started in.

packages/react/src/hooks/cloud/krisp/useKrispNoiseFilter.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export function useKrispNoiseFilter(options: useKrispNoiseFilterOptions = {}) {
4848
micPublication = options.trackRef.publication;
4949
}
5050

51+
const managedProcessorRef = React.useRef<KrispNoiseFilterProcessor | undefined>(undefined);
52+
const managedProcessorTrackRef = React.useRef<LocalAudioTrack | undefined>(undefined);
53+
5154
const setNoiseFilterEnabled = React.useCallback(async (enable: boolean) => {
5255
if (enable) {
5356
const { isKrispNoiseFilterSupported } = await import('@livekit/krisp-noise-filter');
@@ -105,12 +108,22 @@ export function useKrispNoiseFilter(options: useKrispNoiseFilterOptions = {}) {
105108
// component unmounted with the track still alive via a caller-owned
106109
// trackRef). Undo the attach so the track is left as we found it.
107110
if (cancelled) {
108-
await track.stopProcessor();
111+
if (track.getProcessor() === processor) {
112+
await track.stopProcessor();
113+
}
109114
return;
110115
}
116+
managedProcessorRef.current = processor;
117+
managedProcessorTrackRef.current = track;
111118
await processor.setEnabled(true);
112119
if (cancelled) {
113-
await track.stopProcessor();
120+
if (track.getProcessor() === processor) {
121+
await track.stopProcessor();
122+
}
123+
if (managedProcessorRef.current === processor) {
124+
managedProcessorRef.current = undefined;
125+
managedProcessorTrackRef.current = undefined;
126+
}
114127
return;
115128
}
116129
setIsNoiseFilterEnabled(true);
@@ -126,9 +139,29 @@ export function useKrispNoiseFilter(options: useKrispNoiseFilterOptions = {}) {
126139
};
127140
}, [shouldEnable, micPublication?.track, options.filterOptions]);
128141

129-
const processor =
142+
React.useEffect(
143+
() => () => {
144+
const track = managedProcessorTrackRef.current;
145+
const processor = managedProcessorRef.current;
146+
if (!track || !processor) return;
147+
if (track.getProcessor() === processor) {
148+
track.stopProcessor().catch((e) => {
149+
log.warn('Krisp hook: error detaching processor on cleanup', e);
150+
});
151+
}
152+
managedProcessorRef.current = undefined;
153+
managedProcessorTrackRef.current = undefined;
154+
},
155+
[micPublication?.track],
156+
);
157+
158+
const trackProcessor =
130159
micPublication?.track instanceof LocalAudioTrack
131-
? (micPublication.track.getProcessor() as KrispNoiseFilterProcessor | undefined)
160+
? micPublication.track.getProcessor()
161+
: undefined;
162+
const processor =
163+
trackProcessor?.name === 'livekit-noise-filter'
164+
? (trackProcessor as KrispNoiseFilterProcessor)
132165
: undefined;
133166

134167
return {

0 commit comments

Comments
 (0)