Fix mic processing toggles after LiveKit voiceIsolation default
This commit is contained in:
@@ -83,6 +83,9 @@ function loadAudioSettings(): AudioSettings {
|
||||
return DEFAULT_AUDIO_SETTINGS;
|
||||
}
|
||||
|
||||
/** Dispatched on window after audio settings are persisted. */
|
||||
export const AUDIO_SETTINGS_CHANGED_EVENT = 'paarrot-audio-settings-changed';
|
||||
|
||||
/**
|
||||
* Saves audio settings to localStorage
|
||||
* @param settings - The audio settings to save
|
||||
@@ -90,6 +93,7 @@ function loadAudioSettings(): AudioSettings {
|
||||
function saveAudioSettings(settings: AudioSettings): void {
|
||||
try {
|
||||
localStorage.setItem(AUDIO_SETTINGS_KEY, JSON.stringify(settings));
|
||||
window.dispatchEvent(new CustomEvent(AUDIO_SETTINGS_CHANGED_EVENT));
|
||||
} catch (e) {
|
||||
console.error('Failed to save audio settings:', e);
|
||||
}
|
||||
@@ -103,6 +107,40 @@ export function getAudioSettings(): AudioSettings {
|
||||
return loadAudioSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* LiveKit / getUserMedia capture options from stored settings.
|
||||
* Always sets voiceIsolation explicitly — LiveKit 2.20 defaults it to true,
|
||||
* which overrides noiseSuppression when left unset.
|
||||
*/
|
||||
export function getAudioCaptureOptions(): {
|
||||
deviceId?: string;
|
||||
noiseSuppression: boolean;
|
||||
echoCancellation: boolean;
|
||||
autoGainControl: boolean;
|
||||
voiceIsolation: boolean;
|
||||
} {
|
||||
const settings = getAudioSettings();
|
||||
return {
|
||||
deviceId: settings.microphoneId || undefined,
|
||||
noiseSuppression: settings.noiseSuppression,
|
||||
echoCancellation: settings.echoCancellation,
|
||||
autoGainControl: settings.autoGainControl,
|
||||
voiceIsolation: settings.noiseSuppression,
|
||||
};
|
||||
}
|
||||
|
||||
/** MediaTrackConstraints for getUserMedia / applyConstraints from UI settings. */
|
||||
function getMicTrackConstraints(settings: AudioSettings): MediaTrackConstraints {
|
||||
return {
|
||||
...(settings.microphoneId ? { deviceId: { exact: settings.microphoneId } } : {}),
|
||||
noiseSuppression: settings.noiseSuppression,
|
||||
echoCancellation: settings.echoCancellation,
|
||||
autoGainControl: settings.autoGainControl,
|
||||
// Experimental; Chromium / Electron. Keep in sync with noise suppression.
|
||||
voiceIsolation: settings.noiseSuppression,
|
||||
} as MediaTrackConstraints;
|
||||
}
|
||||
|
||||
type DeviceSelectorProps = {
|
||||
devices: AudioDevice[];
|
||||
selectedId: string | null;
|
||||
@@ -635,22 +673,9 @@ export function Audio({ requestClose }: AudioProps) {
|
||||
} else {
|
||||
// Start monitoring
|
||||
try {
|
||||
const constraints: MediaStreamConstraints = {
|
||||
audio: settings.microphoneId
|
||||
? {
|
||||
deviceId: { exact: settings.microphoneId },
|
||||
noiseSuppression: settings.noiseSuppression,
|
||||
echoCancellation: settings.echoCancellation,
|
||||
autoGainControl: settings.autoGainControl,
|
||||
}
|
||||
: {
|
||||
noiseSuppression: settings.noiseSuppression,
|
||||
echoCancellation: settings.echoCancellation,
|
||||
autoGainControl: settings.autoGainControl,
|
||||
},
|
||||
};
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: getMicTrackConstraints(settings),
|
||||
});
|
||||
const audioContext = new AudioContext();
|
||||
const analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 256;
|
||||
@@ -685,7 +710,7 @@ export function Audio({ requestClose }: AudioProps) {
|
||||
}
|
||||
}, [micMonitoring, settings.microphoneId, settings.noiseSuppression, settings.echoCancellation, settings.autoGainControl]);
|
||||
|
||||
// Cleanup monitoring on unmount or when settings change
|
||||
// Cleanup monitoring on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (micAnimFrameRef.current !== null) {
|
||||
@@ -700,14 +725,25 @@ export function Audio({ requestClose }: AudioProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Restart monitoring when mic settings change
|
||||
// Apply processing toggles to the live monitor track without tearing it down.
|
||||
useEffect(() => {
|
||||
if (micMonitoring) {
|
||||
handleMicMonitoringToggle(); // Stop
|
||||
setTimeout(() => handleMicMonitoringToggle(), 100); // Restart
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings.microphoneId, settings.noiseSuppression, settings.echoCancellation, settings.autoGainControl]);
|
||||
if (!micMonitoring) return;
|
||||
const track = micMonitorStreamRef.current?.getAudioTracks()[0];
|
||||
if (!track) return;
|
||||
track
|
||||
.applyConstraints({
|
||||
noiseSuppression: settings.noiseSuppression,
|
||||
echoCancellation: settings.echoCancellation,
|
||||
autoGainControl: settings.autoGainControl,
|
||||
voiceIsolation: settings.noiseSuppression,
|
||||
} as MediaTrackConstraints)
|
||||
.catch((e) => console.error('Failed to apply mic processing constraints:', e));
|
||||
}, [
|
||||
micMonitoring,
|
||||
settings.noiseSuppression,
|
||||
settings.echoCancellation,
|
||||
settings.autoGainControl,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
|
||||
Reference in New Issue
Block a user