[mirotalk] - refactor noise supression
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
# ====================================================
|
||||
# MiroTalk P2P v.1.6.84 - Environment Configuration
|
||||
# MiroTalk P2P v.1.6.85 - Environment Configuration
|
||||
# ====================================================
|
||||
|
||||
# App environment
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* ==============================================
|
||||
* MiroTalk P2P v.1.6.84 - Configuration File
|
||||
* MiroTalk P2P v.1.6.85 - Configuration File
|
||||
* ==============================================
|
||||
*
|
||||
* Branding and customizations require a license:
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ dependencies: {
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.6.84
|
||||
* @version 1.6.85
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.6.84",
|
||||
"version": "1.6.85",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mirotalk",
|
||||
"version": "1.6.84",
|
||||
"version": "1.6.85",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@mattermost/client": "11.2.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.6.84",
|
||||
"version": "1.6.85",
|
||||
"description": "A free WebRTC browser-based video call",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ let brand = {
|
||||
},
|
||||
about: {
|
||||
imageUrl: '../images/mirotalk-logo.gif',
|
||||
title: 'WebRTC P2P v1.6.84',
|
||||
title: 'WebRTC P2P v1.6.85',
|
||||
html: `
|
||||
<button
|
||||
id="support-button"
|
||||
|
||||
+80
-25
@@ -15,7 +15,7 @@
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.6.84
|
||||
* @version 1.6.85
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -2101,8 +2101,14 @@ async function changeLocalCamera(deviceId) {
|
||||
* @param {string} deviceId
|
||||
*/
|
||||
async function changeLocalMicrophone(deviceId) {
|
||||
if (localAudioMediaStream) {
|
||||
await stopAudioTracks(localAudioMediaStream);
|
||||
// If noise suppression is active, localAudioMediaStream may be the processed stream.
|
||||
// Stop the RNNoise pipeline first and stop the *original* microphone tracks.
|
||||
const oldMicStream = noiseProcessor?.originalStream || noiseProcessor?.mediaStream || localAudioMediaStream;
|
||||
|
||||
stopNoiseSuppressionPipeline();
|
||||
|
||||
if (oldMicStream) {
|
||||
await stopAudioTracks(oldMicStream);
|
||||
}
|
||||
|
||||
// Get audio constraints
|
||||
@@ -2116,9 +2122,15 @@ async function changeLocalMicrophone(deviceId) {
|
||||
localAudioMediaStream = micStream;
|
||||
logStreamSettingsInfo('Success attached local microphone stream', micStream);
|
||||
getMicrophoneVolumeIndicator(micStream);
|
||||
lsSettings.mic_noise_suppression
|
||||
? await restartNoiseSuppression()
|
||||
: await refreshMyStreamToPeers(micStream, true);
|
||||
|
||||
if (lsSettings.mic_noise_suppression) {
|
||||
const ok = await enableNoiseSuppression();
|
||||
if (!ok) {
|
||||
await refreshMyStreamToPeers(micStream, true);
|
||||
}
|
||||
} else {
|
||||
await refreshMyStreamToPeers(micStream, true);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[Error] changeLocalMicrophone', err);
|
||||
@@ -2154,38 +2166,68 @@ function checkPeerAudioVideo() {
|
||||
* Enable RNNoise audio processing for noise suppression
|
||||
*/
|
||||
async function enableNoiseSuppression() {
|
||||
if (!localAudioMediaStream) {
|
||||
if (!localAudioMediaStream || localAudioMediaStream.getAudioTracks().length === 0) {
|
||||
userLog('error', 'No local audio stream available for noise suppression.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!noiseProcessor) noiseProcessor = new RNNoiseProcessor();
|
||||
|
||||
// Reset any existing pipeline to avoid keeping stale/ended streams.
|
||||
stopNoiseSuppressionPipeline();
|
||||
|
||||
noiseProcessor = new RNNoiseProcessor();
|
||||
// Keep a reference to the raw microphone stream for safe restore.
|
||||
noiseProcessor.originalStream = localAudioMediaStream;
|
||||
|
||||
const processedStream = await noiseProcessor.startProcessing(localAudioMediaStream);
|
||||
if (!processedStream || processedStream.getAudioTracks().length === 0) {
|
||||
userLog('warning', 'Noise suppression unavailable, using raw microphone.');
|
||||
stopNoiseSuppressionPipeline();
|
||||
await refreshMyStreamToPeers(localAudioMediaStream, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
noiseProcessor.toggleNoiseSuppression();
|
||||
localAudioMediaStream = processedStream;
|
||||
await refreshMyStreamToPeers(localAudioMediaStream, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable RNNoise audio processing for noise suppression
|
||||
*/
|
||||
async function disableNoiseSuppression() {
|
||||
async function disableNoiseSuppression(restoreOriginalStream = true) {
|
||||
if (noiseProcessor) {
|
||||
localAudioMediaStream = noiseProcessor.mediaStream || localAudioMediaStream;
|
||||
const originalStream = noiseProcessor.originalStream || noiseProcessor.mediaStream;
|
||||
if (restoreOriginalStream && originalStream) {
|
||||
localAudioMediaStream = originalStream;
|
||||
}
|
||||
await refreshMyStreamToPeers(localAudioMediaStream, true);
|
||||
noiseProcessor.toggleNoiseSuppression();
|
||||
await noiseProcessor.stopProcessing();
|
||||
noiseProcessor = null;
|
||||
stopNoiseSuppressionPipeline();
|
||||
} else {
|
||||
await refreshMyStreamToPeers(localAudioMediaStream, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop RNNoise audio processing pipeline
|
||||
*/
|
||||
function stopNoiseSuppressionPipeline() {
|
||||
if (!noiseProcessor) return;
|
||||
try {
|
||||
noiseProcessor.stopProcessing();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
noiseProcessor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart noise suppression (e.g. after changing mic)
|
||||
*/
|
||||
async function restartNoiseSuppression() {
|
||||
if (!lsSettings.mic_noise_suppression) return;
|
||||
await disableNoiseSuppression();
|
||||
// Do not restore the old microphone stream when restarting.
|
||||
await disableNoiseSuppression(false);
|
||||
await enableNoiseSuppression();
|
||||
}
|
||||
|
||||
@@ -6362,7 +6404,7 @@ function setMySettingsBtn() {
|
||||
playSound('switch');
|
||||
});
|
||||
|
||||
// WakeLook for mobile/tablet
|
||||
// WakeLock for mobile/tablet
|
||||
if (!isDesktopDevice && isWakeLockSupported()) {
|
||||
switchKeepAwake.addEventListener('change', (e) => {
|
||||
applyKeepAwake(e.currentTarget.checked);
|
||||
@@ -6519,14 +6561,27 @@ function setupMySettings() {
|
||||
// audio options
|
||||
switchNoiseSuppression.onchange = async (e) => {
|
||||
if (!buttons.settings.customNoiseSuppression) return;
|
||||
const noiseSuppressionEnabled = e.currentTarget.checked;
|
||||
lsSettings.mic_noise_suppression = noiseSuppressionEnabled;
|
||||
lS.setSettings(lsSettings);
|
||||
noiseSuppressionEnabled ? await enableNoiseSuppression() : await disableNoiseSuppression();
|
||||
toastMessage(
|
||||
noiseSuppressionEnabled ? 'success' : 'info',
|
||||
`Noise suppression ${noiseSuppressionEnabled ? 'enabled' : 'disabled'}`
|
||||
);
|
||||
const desired = e.currentTarget.checked;
|
||||
|
||||
if (desired) {
|
||||
lsSettings.mic_noise_suppression = true;
|
||||
lS.setSettings(lsSettings);
|
||||
|
||||
const ok = await enableNoiseSuppression();
|
||||
if (!ok) {
|
||||
lsSettings.mic_noise_suppression = false;
|
||||
lS.setSettings(lsSettings);
|
||||
e.currentTarget.checked = false;
|
||||
toastMessage('warning', 'Noise suppression unavailable');
|
||||
} else {
|
||||
toastMessage('success', 'Noise suppression enabled');
|
||||
}
|
||||
} else {
|
||||
lsSettings.mic_noise_suppression = false;
|
||||
lS.setSettings(lsSettings);
|
||||
await disableNoiseSuppression(true);
|
||||
toastMessage('info', 'Noise suppression disabled');
|
||||
}
|
||||
e.target.blur();
|
||||
};
|
||||
// select audio output
|
||||
@@ -13333,7 +13388,7 @@ function showAbout() {
|
||||
Swal.fire({
|
||||
background: swBg,
|
||||
position: 'center',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.6.84',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.6.85',
|
||||
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
|
||||
customClass: { image: 'img-about' },
|
||||
html: `
|
||||
|
||||
@@ -129,10 +129,18 @@ class RNNoiseProcessor {
|
||||
try {
|
||||
this.uiManager.updateStatus('🎤 Starting audio processing...', 'info');
|
||||
|
||||
this.audioContext = new AudioContext();
|
||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const sampleRate = this.audioContext.sampleRate;
|
||||
this.uiManager.updateStatus(`🎵 Audio context created with sample rate: ${sampleRate}Hz`, 'info');
|
||||
|
||||
if (this.audioContext.state === 'suspended') {
|
||||
try {
|
||||
await this.audioContext.resume();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
this.mediaStream = mediaStream;
|
||||
if (!this.mediaStream.getAudioTracks().length) {
|
||||
throw new Error('No audio tracks found in the provided media stream');
|
||||
@@ -162,10 +170,25 @@ class RNNoiseProcessor {
|
||||
return this.destinationNode.stream;
|
||||
} catch (error) {
|
||||
this.uiManager.updateStatus('❌ Error: ' + error.message, 'error');
|
||||
console.error('RNNoise startProcessing error:', error);
|
||||
this.stopProcessing();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
stopProcessing() {
|
||||
this.mediaStream = null;
|
||||
|
||||
try {
|
||||
this.sourceNode?.disconnect();
|
||||
} catch (e) {}
|
||||
try {
|
||||
this.workletNode?.disconnect();
|
||||
} catch (e) {}
|
||||
try {
|
||||
this.destinationNode?.stream?.getTracks?.().forEach((t) => t.stop());
|
||||
} catch (e) {}
|
||||
|
||||
if (this.audioContext && this.audioContext.state !== 'closed') {
|
||||
this.audioContext.close();
|
||||
this.audioContext = null;
|
||||
|
||||
Reference in New Issue
Block a user