parent
babbb33f69
commit
e954433289
6 changed files with 665 additions and 7 deletions
|
|
@ -23,6 +23,10 @@ const state = {
|
|||
muted: false,
|
||||
sharingVideo: false,
|
||||
sharingScreen: false,
|
||||
noiseFilterMode: 'deepfilter',
|
||||
activeNoiseFilter: 'off',
|
||||
deepFilterProcessor: null,
|
||||
deepFilterModule: null,
|
||||
viewMode: 'chat', // 'chat' or 'video'
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
},
|
||||
|
|
@ -114,6 +118,7 @@ const el = {
|
|||
vcChannelName: document.getElementById("vc-channel-name"),
|
||||
voiceVideoBtn: document.getElementById("voice-video-btn"),
|
||||
voiceScreenBtn: document.getElementById("voice-screen-btn"),
|
||||
voiceFilterBtn: document.getElementById("voice-filter-btn"),
|
||||
voiceMuteBtn: document.getElementById("voice-mute-btn"),
|
||||
voiceLeaveBtn: document.getElementById("voice-leave-btn"),
|
||||
videoGrid: document.getElementById("video-grid"),
|
||||
|
|
@ -703,6 +708,10 @@ function shouldInitiateOffer(peerId) {
|
|||
}
|
||||
|
||||
function stopAndClearAudioPipeline() {
|
||||
if (state.voice.deepFilterProcessor) {
|
||||
state.voice.deepFilterProcessor.destroy();
|
||||
state.voice.deepFilterProcessor = null;
|
||||
}
|
||||
if (state.voice.localStream) {
|
||||
for (const track of state.voice.localStream.getTracks()) track.stop();
|
||||
}
|
||||
|
|
@ -715,6 +724,8 @@ function stopAndClearAudioPipeline() {
|
|||
state.voice.localStream = null;
|
||||
state.voice.rawStream = null;
|
||||
state.voice.audioContext = null;
|
||||
state.voice.activeNoiseFilter = 'off';
|
||||
updateVoiceFilterButton();
|
||||
}
|
||||
|
||||
function stopAndClearVideoPipeline() {
|
||||
|
|
@ -786,11 +797,34 @@ async function buildAudioPipeline(rawStream) {
|
|||
const ctx = new AudioContext();
|
||||
state.voice.audioContext = ctx;
|
||||
const source = ctx.createMediaStreamSource(rawStream);
|
||||
let processedSource = source;
|
||||
state.voice.activeNoiseFilter = 'off';
|
||||
|
||||
if (state.voice.noiseFilterMode === 'deepfilter') {
|
||||
try {
|
||||
const deepFilter = await getDeepFilterModule();
|
||||
const processor = new deepFilter.DeepFilterNet3Core({
|
||||
sampleRate: 48000,
|
||||
noiseReductionLevel: 70,
|
||||
});
|
||||
await processor.initialize();
|
||||
const workletNode = await processor.createAudioWorkletNode(ctx);
|
||||
processor.setNoiseSuppressionEnabled(true);
|
||||
state.voice.deepFilterProcessor = processor;
|
||||
source.connect(workletNode);
|
||||
processedSource = workletNode;
|
||||
state.voice.activeNoiseFilter = 'deepfilter';
|
||||
} catch (err) {
|
||||
console.warn("DeepFilterNet3 unavailable, falling back to raw mic audio", err);
|
||||
state.voice.activeNoiseFilter = 'off';
|
||||
}
|
||||
}
|
||||
updateVoiceFilterButton();
|
||||
|
||||
// Metering/Speaking detection
|
||||
const analyser = ctx.createAnalyser();
|
||||
analyser.fftSize = 512;
|
||||
source.connect(analyser);
|
||||
processedSource.connect(analyser);
|
||||
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
let localIsSpeaking = false;
|
||||
|
|
@ -833,17 +867,60 @@ async function buildAudioPipeline(rawStream) {
|
|||
checkVolume();
|
||||
|
||||
const destination = ctx.createMediaStreamDestination();
|
||||
source.connect(destination);
|
||||
processedSource.connect(destination);
|
||||
return destination.stream;
|
||||
}
|
||||
|
||||
function resolveDeepFilterModuleUrl() {
|
||||
if (location.protocol === 'file:') {
|
||||
return new URL('vendor/deepfilternet3-noise-filter.esm.js', location.href).toString();
|
||||
}
|
||||
return '/static/vendor/deepfilternet3-noise-filter.esm.js';
|
||||
}
|
||||
|
||||
async function getDeepFilterModule() {
|
||||
if (state.voice.deepFilterModule) return state.voice.deepFilterModule;
|
||||
const moduleUrl = resolveDeepFilterModuleUrl();
|
||||
state.voice.deepFilterModule = await import(moduleUrl);
|
||||
return state.voice.deepFilterModule;
|
||||
}
|
||||
|
||||
function updateVoiceFilterButton() {
|
||||
if (!el.voiceFilterBtn) return;
|
||||
const requestedMode = state.voice.noiseFilterMode === 'deepfilter' ? 'DeepFilterNet3' : 'Off';
|
||||
const activeMode = state.voice.activeNoiseFilter === 'deepfilter' ? 'DeepFilterNet3' : 'Off';
|
||||
const activeSuffix = requestedMode === activeMode ? '' : ` (active: ${activeMode})`;
|
||||
el.voiceFilterBtn.title = `Noise Filter: ${requestedMode}${activeSuffix}`;
|
||||
el.voiceFilterBtn.innerHTML = state.voice.noiseFilterMode === 'deepfilter'
|
||||
? '<i data-lucide="filter"></i>'
|
||||
: '<i data-lucide="filter-x"></i>';
|
||||
el.voiceFilterBtn.style.color = state.voice.noiseFilterMode === 'deepfilter'
|
||||
? 'var(--green)'
|
||||
: 'var(--text-muted)';
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
async function toggleNoiseFilter() {
|
||||
state.voice.noiseFilterMode = state.voice.noiseFilterMode === 'deepfilter' ? 'off' : 'deepfilter';
|
||||
updateVoiceFilterButton();
|
||||
if (!state.voice.joinedChannelId) return;
|
||||
|
||||
try {
|
||||
await leaveVoice();
|
||||
await joinVoice();
|
||||
} catch (err) {
|
||||
console.error("failed to rejoin voice after filter change", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function createLocalVoiceStream() {
|
||||
const useDeepFilter = state.voice.noiseFilterMode === 'deepfilter';
|
||||
const constraints = {
|
||||
audio: {
|
||||
channelCount: 1,
|
||||
sampleRate: 48000,
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
noiseSuppression: !useDeepFilter,
|
||||
autoGainControl: false,
|
||||
},
|
||||
video: false,
|
||||
|
|
@ -1455,8 +1532,10 @@ async function init() {
|
|||
|
||||
el.voiceVideoBtn.onclick = toggleVideo;
|
||||
el.voiceScreenBtn.onclick = toggleScreenShare;
|
||||
if (el.voiceFilterBtn) el.voiceFilterBtn.onclick = toggleNoiseFilter;
|
||||
el.voiceMuteBtn.onclick = toggleMute;
|
||||
el.voiceLeaveBtn.onclick = leaveVoice;
|
||||
updateVoiceFilterButton();
|
||||
|
||||
el.addSoundBtn.onclick = () => {
|
||||
el.soundModal.classList.remove("hidden");
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
<div class="vc-actions">
|
||||
<button id="voice-video-btn" title="Turn on Camera"><i data-lucide="video-off"></i></button>
|
||||
<button id="voice-screen-btn" title="Share Screen"><i data-lucide="monitor-off"></i></button>
|
||||
<button id="voice-filter-btn" title="Noise Filter"><i data-lucide="filter"></i></button>
|
||||
<button id="voice-mute-btn" title="Mute"><i data-lucide="mic"></i></button>
|
||||
<button id="voice-leave-btn" title="Disconnect"><i data-lucide="phone-off"></i></button>
|
||||
</div>
|
||||
|
|
@ -263,4 +264,4 @@
|
|||
<script src="app.js" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
249
desktop/vendor/deepfilternet3-noise-filter.esm.js
vendored
Normal file
249
desktop/vendor/deepfilternet3-noise-filter.esm.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue