voice: audit + noise suppression toggle + stability fixes
Gateway: - transmitter.ts: cap backpressure queue at 500 chunks (prevents memory leak) - transmitter.ts: fix Redis race — assign redisSub AFTER subscribe completes - voice.handler.ts: static import Redis instead of dynamic (cleaner, no eval) Frontend: - mic-transmit.ts: MicAccessError with specific reasons (permission-denied, no-mic, timeout) - mic-transmit.ts: noiseSuppression option in getUserMedia constraints - mic-transmit.ts: proper DOMException handling for all getUserMedia failure modes - use-voice.ts: noiseSuppression state + toggleNoiseSuppression exposed - use-voice.ts: cleanup on unmount (stops transmitter, clears refs) - voice/view.tsx: noise suppression toggle button (ShieldCheck/ShieldOff icons) - voice/view.tsx: improved mic error toasts (permission denied / no mic specific) - voice/view.tsx: NS status in codec footer (NS_ACTIVE when enabled)
This commit is contained in:
@@ -144,10 +144,13 @@ export function useMicTransmit(ws: {
|
||||
}) {
|
||||
const transmitterRef = useRef<MicTransmitter | null>(null);
|
||||
const [micLevel, setMicLevel] = useState(0);
|
||||
const [noiseSuppression, setNoiseSuppressionState] = useState(true);
|
||||
|
||||
const action = useAction(async (active: boolean) => {
|
||||
if (active) {
|
||||
const transmitter = new MicTransmitter((frame) => ws.sendBinary(frame));
|
||||
const transmitter = new MicTransmitter((frame) => ws.sendBinary(frame), {
|
||||
noiseSuppression,
|
||||
});
|
||||
transmitterRef.current = transmitter;
|
||||
await transmitter.start();
|
||||
await voiceApi.sendCommand("voice:transmit:start");
|
||||
@@ -163,6 +166,14 @@ export function useMicTransmit(ws: {
|
||||
transmitterRef.current?.setVolume(volume / 100);
|
||||
}, []);
|
||||
|
||||
const toggleNoiseSuppression = useCallback((enabled: boolean) => {
|
||||
setNoiseSuppressionState(enabled);
|
||||
// If mic is already active, toggling NS requires restart
|
||||
if (transmitterRef.current?.isActive) {
|
||||
transmitterRef.current.setNoiseSuppression(enabled);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Poll the analyser RMS so the UI can render a live input meter.
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@@ -171,7 +182,21 @@ export function useMicTransmit(ws: {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return { ...action, setVolume, micLevel };
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
transmitterRef.current?.stop();
|
||||
transmitterRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...action,
|
||||
setVolume,
|
||||
micLevel,
|
||||
noiseSuppression,
|
||||
toggleNoiseSuppression,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user