From 3528dac63923272775113a1e0558480cd5d9b569 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sat, 21 Oct 2023 22:56:58 +0200 Subject: [PATCH] [mirotalksfu] - add mic advance options --- app/src/Server.js | 2 +- package.json | 4 +- public/css/Room.css | 32 ++++++ public/css/VideoGrid.css | 2 +- public/js/LocalStorage.js | 8 ++ public/js/Room.js | 62 +++++++++++- public/js/RoomClient.js | 21 +++- public/js/Rules.js | 4 + public/sfu/MediasoupClient.js | 32 +++--- public/views/Room.html | 177 +++++++++++++++++++++++++++++++++- 10 files changed, 319 insertions(+), 25 deletions(-) diff --git a/app/src/Server.js b/app/src/Server.js index 03444eb9..936e11e8 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -40,7 +40,7 @@ dependencies: { * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.1.1 + * @version 1.1.2 * */ diff --git a/package.json b/package.json index 4cced4d7..e4d487eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.1.1", + "version": "1.1.2", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { @@ -45,7 +45,7 @@ "express": "4.18.2", "httpolyglot": "0.1.2", "mediasoup": "3.12.13", - "mediasoup-client": "3.6.101", + "mediasoup-client": "3.6.102", "ngrok": "^4.3.3", "openai": "^4.12.4", "qs": "6.11.2", diff --git a/public/css/Room.css b/public/css/Room.css index 807b508d..d50f40b4 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -378,6 +378,13 @@ th { width: 150px; } +.microphone-table-width { + width: 180px; +} +.dropdown-menu { + background: var(--body-bg); +} + /*-------------------------------------------------------------- # Style the tab --------------------------------------------------------------*/ @@ -399,6 +406,9 @@ th { width: 65%; background-color: var(--body-bg); min-height: 480px; + max-height: 550px; + overflow-x: hidden; + overflow-y: auto; } @media screen and (max-width: 830px) { @@ -1406,6 +1416,28 @@ progress { transition-duration: 1s; } +/*-------------------------------------------------------------- +# Webkit-Scrollbar Styles +--------------------------------------------------------------*/ + +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--body-bg); + border-radius: 5px; + cursor: pointer; +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--body-bg); +} + +::-webkit-scrollbar-track { + background: #1a1b1f; +} + /*-------------------------------------------------------------- # Pulse class effect --------------------------------------------------------------*/ diff --git a/public/css/VideoGrid.css b/public/css/VideoGrid.css index 7688e25a..25a31d18 100644 --- a/public/css/VideoGrid.css +++ b/public/css/VideoGrid.css @@ -213,7 +213,7 @@ video:fullscreen { } input[type='range'] { - display: none; + /* display: none; */ color: #fff; width: 50px; cursor: pointer; diff --git a/public/js/LocalStorage.js b/public/js/LocalStorage.js index 168c4157..49c61bbc 100644 --- a/public/js/LocalStorage.js +++ b/public/js/LocalStorage.js @@ -20,6 +20,14 @@ class LocalStorage { show_chat_on_msg: true, // show chat on new message show_transcript_on_msg: true, // show transcript on new message speech_in_msg: false, // speech incoming message + mic_auto_gain_control: false, + mic_echo_cancellations: true, + mic_noise_suppression: true, + mic_sample_rate: 0, // 0: 48000 Hz 1: 44100 Hz + mic_sample_size: 0, // 0: 16 bits 1: 32 bits + mic_channel_count: 0, // 0: 1(mono) 1: 2 (stereo) + mic_latency: 50, // ms + mic_volume: 100, // % video_fps: 0, // default 1280x768 30fps screen_fps: 0, // max 30fps lobby: false, // default false diff --git a/public/js/Room.js b/public/js/Room.js index 9417f6e7..e47ee458 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.1.1 + * @version 1.1.2 * */ @@ -59,8 +59,8 @@ const wbHeight = 600; const swalImageUrl = '../images/pricing-illustration.svg'; -const lS = new LocalStorage(); // Get Settings from localStorage +const lS = new LocalStorage(); const localStorageSettings = lS.getObjectLocalStorage('SFU_SETTINGS'); const lsSettings = localStorageSettings ? localStorageSettings : lS.SFU_SETTINGS; console.log('LS_SETTINGS', lsSettings); @@ -1166,6 +1166,9 @@ function handleButtons() { tabLanguagesBtn.onclick = (e) => { rc.openTab(e, 'tabLanguages'); }; + applyAudioOptionsButton.onclick = () => { + rc.closeThenProduce(RoomClient.mediaType.audio, microphoneSelect.value); + }; speakerTestBtn.onclick = () => { sound('ring', true); }; @@ -1668,6 +1671,49 @@ function handleSelects() { lS.setSettings(lsSettings); e.target.blur(); }; + // audio options + switchAutoGainControl.onchange = (e) => { + lsSettings.mic_auto_gain_control = e.currentTarget.checked; + lS.setSettings(lsSettings); + e.target.blur(); + }; + switchEchoCancellation.onchange = (e) => { + lsSettings.mic_echo_cancellations = e.currentTarget.checked; + lS.setSettings(lsSettings); + e.target.blur(); + }; + switchNoiseSuppression.onchange = (e) => { + lsSettings.mic_noise_suppression = e.currentTarget.checked; + lS.setSettings(lsSettings); + e.target.blur(); + }; + sampleRateSelect.onchange = (e) => { + lsSettings.mic_sample_rate = e.currentTarget.selectedIndex; + lS.setSettings(lsSettings); + e.target.blur(); + }; + sampleSizeSelect.onchange = (e) => { + lsSettings.mic_sample_size = e.currentTarget.selectedIndex; + lS.setSettings(lsSettings); + e.target.blur(); + }; + channelCountSelect.onchange = (e) => { + lsSettings.mic_channel_count = e.currentTarget.selectedIndex; + lS.setSettings(lsSettings); + e.target.blur(); + }; + micLatencyRange.oninput = (e) => { + lsSettings.mic_latency = e.currentTarget.value; + lS.setSettings(lsSettings); + micLatencyValue.innerText = e.currentTarget.value; + e.target.blur(); + }; + micVolumeRange.oninput = (e) => { + lsSettings.mic_volume = e.currentTarget.value; + lS.setSettings(lsSettings); + micVolumeValue.innerText = e.currentTarget.value; + e.target.blur(); + }; // recording switchHostOnlyRecording.onchange = (e) => { hostOnlyRecording = e.currentTarget.checked; @@ -1892,6 +1938,18 @@ function loadSettingsFromLocalStorage() { switchPitchBar.checked = isPitchBarEnabled; switchSounds.checked = isSoundEnabled; switchShare.checked = notify; + + switchAutoGainControl.checked = lsSettings.mic_auto_gain_control; + switchEchoCancellation.checked = lsSettings.mic_echo_cancellations; + switchNoiseSuppression.checked = lsSettings.mic_noise_suppression; + sampleRateSelect.selectedIndex = lsSettings.mic_sample_rate; + sampleSizeSelect.selectedIndex = lsSettings.mic_sample_size; + channelCountSelect.selectedIndex = lsSettings.mic_channel_count; + micLatencyRange.value = lsSettings.mic_latency || 50; + micLatencyValue.innerText = lsSettings.mic_latency || 50; + micVolumeRange.value = lsSettings.mic_volume || 100; + micVolumeValue.innerText = lsSettings.mic_volume || 100; + videoFps.selectedIndex = lsSettings.video_fps; screenFps.selectedIndex = lsSettings.screen_fps; BtnVideoObjectFit.selectedIndex = lsSettings.video_obj_fit; diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index 979e2f05..1853d7df 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -9,7 +9,7 @@ * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.1.1 + * @version 1.1.2 * */ @@ -1028,7 +1028,7 @@ class RoomClient { // #################################################### getAudioConstraints(deviceId) { - return { + let constraints = { audio: { echoCancellation: true, noiseSuppression: true, @@ -1036,6 +1036,23 @@ class RoomClient { }, video: false, }; + if (isRulesActive && isPresenter) { + constraints = { + audio: { + autoGainControl: switchAutoGainControl.checked, + echoCancellation: switchNoiseSuppression.checked, + noiseSuppression: switchEchoCancellation.checked, + sampleRate: parseInt(sampleRateSelect.value), + sampleSize: parseInt(sampleSizeSelect.value), + channelCount: parseInt(channelCountSelect.value), + latency: parseInt(micLatencyRange.value), + volume: parseInt(micVolumeRange.value / 100), + deviceId: deviceId, + }, + video: false, + }; + } + return constraints; } getCameraConstraints() { diff --git a/public/js/Rules.js b/public/js/Rules.js index 55c3607f..fb5ab348 100644 --- a/public/js/Rules.js +++ b/public/js/Rules.js @@ -29,6 +29,7 @@ const BUTTONS = { lockRoomButton: true, // presenter unlockRoomButton: true, // presenter lobbyButton: true, // presenter + micOptionsButton: true, // presenter tabRecording: true, pushToTalk: true, host_only_recording: true, // presenter @@ -87,6 +88,7 @@ function handleRules(isPresenter) { BUTTONS.settings.lockRoomButton = false; BUTTONS.settings.unlockRoomButton = false; BUTTONS.settings.lobbyButton = false; + BUTTONS.settings.micOptionsButton = false; BUTTONS.videoOff.muteAudioButton = false; BUTTONS.videoOff.ejectButton = false; BUTTONS.consumerVideo.ejectButton = false; @@ -99,6 +101,7 @@ function handleRules(isPresenter) { BUTTONS.settings.lockRoomButton = !isRoomLocked; BUTTONS.settings.unlockRoomButton = isRoomLocked; BUTTONS.settings.lobbyButton = true; + BUTTONS.settings.micOptionsButton = true; BUTTONS.videoOff.muteAudioButton = true; BUTTONS.videoOff.ejectButton = true; BUTTONS.consumerVideo.ejectButton = true; @@ -125,6 +128,7 @@ function handleRules(isPresenter) { BUTTONS.settings.lockRoomButton ? show(lockRoomButton) : hide(lockRoomButton); BUTTONS.settings.unlockRoomButton ? show(unlockRoomButton) : hide(unlockRoomButton); BUTTONS.settings.lobbyButton ? show(lobbyButton) : hide(lobbyButton); + !BUTTONS.settings.micOptionsButton && hide(micOptionsButton); BUTTONS.participantsList.saveInfoButton ? show(participantsSaveBtn) : hide(participantsSaveBtn); BUTTONS.whiteboard.whiteboardLockButton ? elemDisplay('whiteboardLockButton', true) diff --git a/public/sfu/MediasoupClient.js b/public/sfu/MediasoupClient.js index 9aea9a0c..2bb8b2da 100644 --- a/public/sfu/MediasoupClient.js +++ b/public/sfu/MediasoupClient.js @@ -1891,24 +1891,25 @@ const uaParser = new ua_parser_js_1.UAParser(ua); logger.debug('detectDevice() | browser detected [ua:%s, parsed:%o]', ua, uaParser.getResult()); const browser = uaParser.getBrowser(); - const browserName = browser.name?.toLowerCase() ?? ''; + const browserName = browser.name?.toLowerCase(); const browserVersion = parseInt(browser.major ?? '0'); const engine = uaParser.getEngine(); - const engineName = engine.name?.toLowerCase() ?? ''; + const engineName = engine.name?.toLowerCase(); const os = uaParser.getOS(); - const osName = os.name?.toLowerCase() ?? ''; + const osName = os.name?.toLowerCase(); const osVersion = parseFloat(os.version ?? '0'); - const isIOS = osName === 'ios'; - const isChrome = [ - 'chrome', - 'chromium', - 'mobile chrome', - 'chrome webview', - 'chrome headless', - ].includes(browserName); - const isFirefox = ['firefox', 'mobile firefox', 'mobile focus'].includes(browserName); - const isSafari = ['safari', 'mobile safari'].includes(browserName); - const isEdge = ['edge'].includes(browserName); + const device = uaParser.getDevice(); + const deviceModel = device.model?.toLowerCase(); + const isIOS = osName === 'ios' || deviceModel === 'ipad'; + const isChrome = + browserName && + ['chrome', 'chromium', 'mobile chrome', 'chrome webview', 'chrome headless'].includes( + browserName, + ); + const isFirefox = + browserName && ['firefox', 'mobile firefox', 'mobile focus'].includes(browserName); + const isSafari = browserName && ['safari', 'mobile safari'].includes(browserName); + const isEdge = browserName && ['edge'].includes(browserName); // Chrome, Chromium, and Edge. if ((isChrome || isEdge) && !isIOS && browserVersion >= 111) { return 'Chrome111'; @@ -1953,7 +1954,6 @@ else if ( engineName === 'webkit' && isIOS && - osVersion >= 14.3 && typeof RTCRtpTransceiver !== 'undefined' && RTCRtpTransceiver.prototype.hasOwnProperty('currentDirection') ) { @@ -12794,7 +12794,7 @@ /** * Expose mediasoup-client version. */ - exports.version = '3.6.101'; + exports.version = '3.6.102'; /** * Expose parseScalabilityMode() function. */ diff --git a/public/views/Room.html b/public/views/Room.html index f1804f1e..c69df825 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -44,7 +44,7 @@ - + @@ -373,6 +373,181 @@ access to use this app.

+