From 6764f6c4a09f7f09eef4ab3893222cb0d3f899f4 Mon Sep 17 00:00:00 2001 From: codex Date: Thu, 11 Jun 2026 01:34:07 +0200 Subject: [PATCH] Add microphone noise suppression toggle --- public/app.js | 34 ++++++++++++++++++++++++++++++---- public/index.html | 10 ++++++++-- public/styles.css | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/public/app.js b/public/app.js index ef9e438..18e433e 100644 --- a/public/app.js +++ b/public/app.js @@ -27,6 +27,7 @@ const shareButton = document.querySelector("#share-button"); const announceButton = document.querySelector("#announce-button"); const selfMicVolumeInput = document.querySelector("#self-mic-volume"); const selfMicMeter = document.querySelector("#self-mic-meter"); +const noiseSuppressionInput = document.querySelector("#noise-suppression"); const announcementDialog = document.querySelector("#announcement-dialog"); const announcementForm = document.querySelector("#announcement-form"); const announcementInput = document.querySelector("#announcement-input"); @@ -38,6 +39,7 @@ const clientIdStorageKey = "kkkmeet.clientId"; const cameraStorageKey = "kkkmeet.cameraDeviceId"; const micStorageKey = "kkkmeet.micDeviceId"; const micVolumeStorageKey = "kkkmeet.selfMicVolume"; +const noiseSuppressionStorageKey = "kkkmeet.noiseSuppression"; const watchHeightStorageKey = "kkkmeet.watchPanelHeight"; const watchCollapsedStorageKey = "kkkmeet.watchPanelCollapsed"; const rtcConfig = { @@ -672,7 +674,7 @@ function mediaConstraints() { const micDeviceId = localStorage.getItem(micStorageKey); return { - audio: micDeviceId ? { deviceId: { exact: micDeviceId } } : true, + audio: audioConstraints(micDeviceId), video: { width: { ideal: 1280 }, height: { ideal: 720 }, @@ -681,6 +683,17 @@ function mediaConstraints() { }; } +function savedNoiseSuppression() { + return localStorage.getItem(noiseSuppressionStorageKey) !== "false"; +} + +function audioConstraints(deviceId = "") { + return { + noiseSuppression: savedNoiseSuppression(), + ...(deviceId ? { deviceId: { exact: deviceId } } : {}) + }; +} + async function ensureLocalStream() { if (localStream) return localStream; @@ -819,10 +832,12 @@ function outboundAudioTrack() { } async function replaceLocalTrack(kind, deviceId) { - if (!deviceId) return; + if (kind !== "audio" && !deviceId) return; if (!localStream) { - localStorage.setItem(kind === "video" ? cameraStorageKey : micStorageKey, deviceId); + if (deviceId) { + localStorage.setItem(kind === "video" ? cameraStorageKey : micStorageKey, deviceId); + } return; } @@ -830,7 +845,7 @@ async function replaceLocalTrack(kind, deviceId) { const wasEnabled = oldTrack?.enabled ?? true; const constraints = kind === "video" ? { video: { deviceId: { exact: deviceId }, width: { ideal: 1280 }, height: { ideal: 720 } } } - : { audio: { deviceId: { exact: deviceId } } }; + : { audio: audioConstraints(deviceId) }; const stream = await navigator.mediaDevices.getUserMedia(constraints); const newTrack = stream.getTracks()[0]; newTrack.enabled = wasEnabled; @@ -1414,6 +1429,16 @@ selfMicVolumeInput.addEventListener("input", () => { setSelfMicVolume(selfMicVolumeInput.value); }); +noiseSuppressionInput.addEventListener("change", () => { + localStorage.setItem(noiseSuppressionStorageKey, String(noiseSuppressionInput.checked)); + replaceLocalTrack("audio", micSelect.value).catch((error) => { + console.error(error); + noiseSuppressionInput.checked = !noiseSuppressionInput.checked; + localStorage.setItem(noiseSuppressionStorageKey, String(noiseSuppressionInput.checked)); + setStatus("Could not change noise suppression."); + }); +}); + cameraSelect.addEventListener("change", () => { replaceLocalTrack("video", cameraSelect.value).catch((error) => { console.error(error); @@ -1474,6 +1499,7 @@ shareButton.addEventListener("click", async () => { selfMicVolumeInput.value = String(savedSelfMicVolume()); selfMicMeter.value = 0; +noiseSuppressionInput.checked = savedNoiseSuppression(); const savedName = localStorage.getItem(nameStorageKey); if (savedName) { diff --git a/public/index.html b/public/index.html index 8850c1d..4bc51d7 100644 --- a/public/index.html +++ b/public/index.html @@ -113,10 +113,16 @@
- +
+ + +
diff --git a/public/styles.css b/public/styles.css index 1b68a25..aeb6483 100644 --- a/public/styles.css +++ b/public/styles.css @@ -609,10 +609,10 @@ button[aria-pressed="false"] { .self-mic-control { display: grid; - grid-template-columns: minmax(120px, 160px) 56px; + grid-template-columns: minmax(120px, 160px) minmax(88px, 112px); align-items: end; gap: 8px; - min-width: 190px; + min-width: 238px; } .self-mic-control label { @@ -624,18 +624,43 @@ button[aria-pressed="false"] { font-weight: 650; } -.self-mic-control input { +.self-mic-control input[type="range"] { width: 100%; height: 18px; padding: 0; } -.self-mic-control meter { - width: 56px; +.self-mic-level { + display: grid; + grid-template-rows: 18px 14px; + gap: 5px; +} + +.self-mic-level meter { + width: 100%; height: 18px; align-self: end; } +.noise-suppression-toggle { + display: flex; + align-items: center; + gap: 5px; + min-width: 0; + color: #aeb7c3; + font-size: 10px; + font-weight: 650; + white-space: nowrap; +} + +.noise-suppression-toggle input { + width: 12px; + height: 12px; + margin: 0; + padding: 0; + flex: 0 0 auto; +} + .self-preview { position: relative; overflow: hidden;