From 229a2bb68e811b43ebf54e5767f4e40d19bce4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Thu, 2 Oct 2025 02:02:45 +0200 Subject: [PATCH 1/3] settings: add advanced audio settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- src/settings/settings.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 7c7f1250d..ef2fd4af9 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -100,6 +100,10 @@ export const videoInput = new Setting( undefined, ); +export const autoGainControl = new Setting("auto-gain-control", true); +export const noiseSuppression = new Setting("noise-suppression", true); +export const echoCancellation = new Setting("echo-cancellation", true); + export const backgroundBlur = new Setting("background-blur", false); export const showHandRaisedTimer = new Setting( From ed9c11e97482b0c029aec6f790148fda9ce117a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Thu, 2 Oct 2025 02:04:11 +0200 Subject: [PATCH 2/3] SettingsModal: show Advanced audio settings under Audio tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- locales/en/app.json | 6 +++- src/settings/SettingsModal.tsx | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/locales/en/app.json b/locales/en/app.json index 007e372a0..b1841ca31 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -170,9 +170,13 @@ "room_auth_view_ssla_caption": "By clicking \"Join call now\", you agree to our <2>Software and Services License Agreement (SSLA)", "screenshare_button_label": "Share screen", "settings": { + "advanced_header": "Advanced", "audio_tab": { + "auto_gain_control_label": "Automatically adjust the microphone volume", + "echo_cancellation_label": "Echo cancellation", "effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.", - "effect_volume_label": "Sound effect volume" + "effect_volume_label": "Sound effect volume", + "noise_suppression_label": "Noise suppression" }, "background_blur_header": "Background", "background_blur_label": "Blur the background of the video", diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 3272200df..68bf8770f 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -23,6 +23,9 @@ import { useSetting, soundEffectVolume as soundEffectVolumeSetting, backgroundBlur as backgroundBlurSetting, + autoGainControl as autoGainControlSetting, + noiseSuppression as noiseSuppressionSetting, + echoCancellation as echoCancellationSetting, developerMode, } from "./settings"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; @@ -94,6 +97,51 @@ export const SettingsModal: FC = ({ ); }; + // Generate a `Checkbox` input to turn blur on or off. + const AdvancedAudio: React.FC = (): ReactNode => { + const [autoGainControl, setAutoGainControl] = useSetting(autoGainControlSetting); + const [noiseSuppression, setNoiseSuppression] = useSetting(noiseSuppressionSetting); + const [echoCancellation, setEchoCancellation] = useSetting(echoCancellationSetting); + + return ( + <> +
+

{t("settings.advanced_header")}

+ + + setAutoGainControl(b.target.checked)} + /> + + + + setNoiseSuppression(b.target.checked)} + /> + + + + setEchoCancellation(b.target.checked)} + /> + +
+ + ); + }; + const devices = useMediaDevices(); useEffect(() => { if (open) devices.requestDeviceNames(); @@ -160,6 +208,8 @@ export const SettingsModal: FC = ({ step={0.01} /> + + ), From 7be997d5cd3ebab5976383bf94ca98667b0f4812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Thu, 2 Oct 2025 02:06:56 +0200 Subject: [PATCH 3/3] livekit: apply user-configured AudioCaptureOptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- src/livekit/options.ts | 16 ++++++++++++++++ src/livekit/useECConnectionState.ts | 8 +++++++- src/livekit/useLivekit.ts | 5 ++++- src/room/LobbyView.tsx | 4 ++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/livekit/options.ts b/src/livekit/options.ts index 1d4cad774..d039d6078 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details. */ import { + type AudioCaptureOptions, AudioPresets, DefaultReconnectPolicy, type RoomOptions, @@ -15,6 +16,12 @@ import { VideoPresets, } from "livekit-client"; +import { + autoGainControl as autoGainControlSetting, + noiseSuppression as noiseSuppressionSetting, + echoCancellation as echoCancellationSetting, +} from "../settings/settings"; + const defaultLiveKitPublishOptions: TrackPublishDefaults = { audioPreset: AudioPresets.music, dtx: true, @@ -52,3 +59,12 @@ export const defaultLiveKitOptions: RoomOptions = { disconnectOnPageLeave: true, webAudioMix: false, }; + +export function getLiveKitAudioCaptureOptions(): AudioCaptureOptions { + return { + autoGainControl: { ideal: autoGainControlSetting.getValue() }, + noiseSuppression: { ideal: noiseSuppressionSetting.getValue() }, + echoCancellation: { ideal: echoCancellationSetting.getValue() }, + voiceIsolation: { ideal: noiseSuppressionSetting.getValue() }, + }; +} diff --git a/src/livekit/useECConnectionState.ts b/src/livekit/useECConnectionState.ts index 83b247e9b..571f6c5e4 100644 --- a/src/livekit/useECConnectionState.ts +++ b/src/livekit/useECConnectionState.ts @@ -26,6 +26,7 @@ import { UnknownCallError, } from "../utils/errors.ts"; import { AbortHandle } from "../utils/abortHandle.ts"; +import { getLiveKitAudioCaptureOptions } from "./options"; /* * Additional values for states that a call can be in, beyond what livekit @@ -73,11 +74,16 @@ async function doConnect( return; } + const liveKitAudioCaptureOptions = getLiveKitAudioCaptureOptions(); + logger.info("Pre-creating microphone track"); let preCreatedAudioTrack: LocalTrack | undefined; try { const audioTracks = await livekitRoom!.localParticipant.createTracks({ - audio: { deviceId: initialDeviceId }, + audio: { + deviceId: initialDeviceId, + ...liveKitAudioCaptureOptions, + }, }); if (audioTracks.length < 1) { diff --git a/src/livekit/useLivekit.ts b/src/livekit/useLivekit.ts index 4c669b47d..3287fa995 100644 --- a/src/livekit/useLivekit.ts +++ b/src/livekit/useLivekit.ts @@ -28,7 +28,7 @@ import { switchMap, } from "rxjs"; -import { defaultLiveKitOptions } from "./options"; +import { defaultLiveKitOptions, getLiveKitAudioCaptureOptions } from "./options"; import { type SFUConfig } from "./openIDSFU"; import { type MuteStates } from "../room/MuteStates"; import { useMediaDevices } from "../MediaDevicesContext"; @@ -101,6 +101,8 @@ export function useLivekit( }; } + const liveKitAudioCaptureOptions = getLiveKitAudioCaptureOptions(); + const roomOptions: RoomOptions = { ...defaultLiveKitOptions, videoCaptureDefaults: { @@ -111,6 +113,7 @@ export function useLivekit( audioCaptureDefaults: { ...defaultLiveKitOptions.audioCaptureDefaults, deviceId: initialAudioInputId, + ...liveKitAudioCaptureOptions, }, audioOutput: { // When using controlled audio devices, we don't want to set the diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index 391cb391f..5e77e1cc6 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -52,6 +52,7 @@ import { import { usePageTitle } from "../usePageTitle"; import { useLatest } from "../useLatest"; import { getValue } from "../utils/observable"; +import { getLiveKitAudioCaptureOptions } from "../livekit/options"; interface Props { client: MatrixClient; @@ -128,6 +129,8 @@ export const LobbyView: FC = ({ devices.videoInput.selected$, )?.id; + + const liveKitAudioCaptureOptions = getLiveKitAudioCaptureOptions(); // Capture the audio options as they were when we first mounted, because // we're not doing anything with the audio anyway so we don't need to // re-open the devices when they change (see below). @@ -135,6 +138,7 @@ export const LobbyView: FC = ({ () => muteStates.audio.enabled && { deviceId: getValue(devices.audioInput.selected$)?.id, + ...liveKitAudioCaptureOptions, }, );