diff --git a/static/shared/app-core.js b/static/shared/app-core.js index deee4bc..8531b48 100644 --- a/static/shared/app-core.js +++ b/static/shared/app-core.js @@ -29,6 +29,7 @@ const state = { viewMode: 'chat', // 'chat' or 'video' iceServers: [{ urls: "stun:stun.l.google.com:19302" }], peerGainNodes: new Map(), // userId -> GainNode + peerAudioNodes: new Map(), // userId -> { sourceNode, compressorNode, gainNode } visibleVolumeSliders: new Set(), // userIds whose sliders are visible }, voicePresencePollId: null, @@ -1039,7 +1040,24 @@ function shouldInitiateOffer(peerId) { return state.me.id > peerId; } +function disconnectPeerAudioNodes(peerId) { + const nodes = state.voice.peerAudioNodes.get(peerId); + if (!nodes) return; + + for (const node of [nodes.sourceNode, nodes.compressorNode, nodes.gainNode]) { + try { + node?.disconnect(); + } catch { } + } + + state.voice.peerAudioNodes.delete(peerId); + state.voice.peerGainNodes.delete(peerId); +} + function stopAndClearAudioPipeline() { + for (const peerId of state.voice.peerAudioNodes.keys()) { + disconnectPeerAudioNodes(peerId); + } if (state.voice.deepFilterProcessor) { state.voice.deepFilterProcessor.destroy(); state.voice.deepFilterProcessor = null; @@ -1265,7 +1283,7 @@ async function createLocalVoiceStream() { sampleRate: 48000, echoCancellation: true, noiseSuppression: false, - autoGainControl: false, + autoGainControl: true, }, video: false, }; @@ -1388,16 +1406,27 @@ function ensurePeerConnection(peerId) { const ctx = state.voice.audioContext; if (ctx.state === 'suspended') ctx.resume(); + disconnectPeerAudioNodes(peerId); + const sourceNode = ctx.createMediaStreamSource(audio.srcObject); + const compressorNode = ctx.createDynamicsCompressor(); const gainNode = ctx.createGain(); + compressorNode.threshold.setValueAtTime(-24, ctx.currentTime); + compressorNode.knee.setValueAtTime(18, ctx.currentTime); + compressorNode.ratio.setValueAtTime(4, ctx.currentTime); + compressorNode.attack.setValueAtTime(0.003, ctx.currentTime); + compressorNode.release.setValueAtTime(0.25, ctx.currentTime); + const currentVolume = state.userVolumes.get(peerId) ?? 1.0; gainNode.gain.setValueAtTime(currentVolume, ctx.currentTime); - sourceNode.connect(gainNode); + sourceNode.connect(compressorNode); + compressorNode.connect(gainNode); gainNode.connect(ctx.destination); state.voice.peerGainNodes.set(peerId, gainNode); + state.voice.peerAudioNodes.set(peerId, { sourceNode, compressorNode, gainNode }); // Mute the original element as we play through Web Audio destination audio.volume = 0; @@ -1582,6 +1611,7 @@ async function joinVoice() { pc.close(); state.voice.peerConnections.delete(msg.user_id); } + disconnectPeerAudioNodes(msg.user_id); document.getElementById(`audio-${msg.user_id}`)?.remove(); } else if (msg.type === "signal") { await handleSignal(msg.from_user_id, msg.kind, msg.data);