The host can now change noise suppression on the live mic without a
rejoin: off / browser-native / ML, the ML model, the gate and stacking
browser NS in front of the model.
- Model swaps preload the new model while the old one keeps running,
then swap the processor (no mic re-capture).
- Browser NS on/off re-captures the mic via restartTrack with the new
constraint (passing the deviceId, without which LiveKit drops every
constraint). The choice is kept in lotusCaptureOverride, which
ConnectionFactory reads, so a later SFU reconnect captures the same way.
- Switches are serialized; a burst collapses into the last one. Each one
reports io.lotus.denoise_state (now with `mode`).
Fixes found while testing in Chromium:
- The mic level meter (and muted-speech nudge) kept a clone of the
ORIGINAL mic track and never noticed LiveKit's restartTrack. Chrome
makes all open tracks of a device share audio processing, so every
re-capture silently kept the old noiseSuppression (asked false, got
true). The meter now drops its clone around our re-capture and
re-taps on TrackEvent.Restarted, which also fixes it listening to the
old device after a mic switch.
- LiveKit defaults voiceIsolation ON, a further voice filter in "off"
and ML modes where the platform implements it. It now follows browser
noise suppression.
Verified in a two-browser call: joined as browser and as ML(dtln), then
switched dtln -> deepfilternet -> rnnoise -> browser -> off; each step
applied within ~0.5 s and every re-capture got the constraints it asked
for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5