From cfbe8a3e5f00edbb25da96f16111f36ef6183379 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Wed, 15 Nov 2023 18:23:20 +0100 Subject: [PATCH] [mirotalksfu] - add moderator --- app/src/Room.js | 22 ++++++++++++ app/src/Server.js | 12 ++++++- package.json | 12 +++---- public/css/Room.css | 2 +- public/js/LocalStorage.js | 2 ++ public/js/Room.js | 31 ++++++++++++---- public/js/RoomClient.js | 68 ++++++++++++++++++++--------------- public/js/Rules.js | 10 +++++- public/js/landing.js | 16 ++++----- public/sfu/MediasoupClient.js | 12 +++---- public/views/Room.html | 39 ++++++++++++++++++++ 11 files changed, 168 insertions(+), 58 deletions(-) diff --git a/app/src/Room.js b/app/src/Room.js index 10e3123a..c2791e6f 100644 --- a/app/src/Room.js +++ b/app/src/Room.js @@ -16,6 +16,10 @@ module.exports = class Room { this._isLobbyEnabled = false; this._roomPassword = null; this._hostOnlyRecording = false; + this._moderator = { + start_audio_muted: false, + start_video_hidden: false, + }; this.survey = config.survey; this.redirect = config.redirect; this.peers = new Map(); @@ -98,6 +102,23 @@ module.exports = class Room { return this.router.rtpCapabilities; } + // #################################################### + // ROOM MODERATOR + // #################################################### + + updateRoomModerator(data) { + log.debug('Update room moderator', data); + switch (data.type) { + case 'audio': + this._moderator.start_audio_muted = data.status; + break; + case 'video': + this._moderator.start_video_hidden = data.status; + default: + break; + } + } + // #################################################### // ROOM INFO // #################################################### @@ -110,6 +131,7 @@ module.exports = class Room { isLobbyEnabled: this._isLobbyEnabled, hostOnlyRecording: this._hostOnlyRecording, }, + moderator: this._moderator, survey: this.survey, redirect: this.redirect, peers: JSON.stringify([...this.peers]), diff --git a/app/src/Server.js b/app/src/Server.js index c664e846..79a9dafd 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.2.1 + * @version 1.2.2 * */ @@ -750,6 +750,16 @@ function startServer() { room.broadCast(socket.id, 'updatePeerInfo', data); }); + socket.on('updateRoomModerator', (dataObject) => { + if (!roomList.has(socket.room_id)) return; + + const data = checkXSS(dataObject); + + const room = roomList.get(socket.room_id); + + room.updateRoomModerator(data); + }); + socket.on('fileInfo', (dataObject) => { if (!roomList.has(socket.room_id)) return; diff --git a/package.json b/package.json index 72f9fd17..b11b665d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.2.1", + "version": "1.2.2", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { @@ -34,9 +34,9 @@ "author": "Miroslav Pejic", "license": "AGPL-3.0", "dependencies": { - "@sentry/integrations": "7.79.0", - "@sentry/node": "7.79.0", - "axios": "^1.6.1", + "@sentry/integrations": "7.80.1", + "@sentry/node": "7.80.1", + "axios": "^1.6.2", "body-parser": "1.20.2", "colors": "1.4.0", "compression": "1.7.4", @@ -47,7 +47,7 @@ "mediasoup": "3.13.1", "mediasoup-client": "3.7.0", "ngrok": "^4.3.3", - "openai": "^4.17.0", + "openai": "^4.18.0", "qs": "6.11.2", "socket.io": "4.7.2", "swagger-ui-express": "5.0.0", @@ -58,6 +58,6 @@ "devDependencies": { "node-fetch": "^3.3.2", "nodemon": "^3.0.1", - "prettier": "3.0.3" + "prettier": "3.1.0" } } diff --git a/public/css/Room.css b/public/css/Room.css index 02e6c600..2602dde6 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -439,7 +439,7 @@ th { @media screen and (max-width: 540px) { .tab { - font-size: 0.5rem; + display: inline; } } diff --git a/public/js/LocalStorage.js b/public/js/LocalStorage.js index 49c61bbc..0ea063f8 100644 --- a/public/js/LocalStorage.js +++ b/public/js/LocalStorage.js @@ -20,6 +20,8 @@ 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 + moderator_audio_muted: false, // Everyone starts muted in the room + moderator_video_hidden: false, // Everyone starts hidden in the room mic_auto_gain_control: false, mic_echo_cancellations: true, mic_noise_suppression: true, diff --git a/public/js/Room.js b/public/js/Room.js index 6c1fc74d..8fbb5d61 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.2.1 + * @version 1.2.2 * */ @@ -1158,6 +1158,9 @@ function handleButtons() { tabAspectBtn.onclick = (e) => { rc.openTab(e, 'tabAspect'); }; + tabModeratorBtn.onclick = (e) => { + rc.openTab(e, 'tabModerator'); + }; tabProfileBtn.onclick = (e) => { rc.openTab(e, 'tabProfile'); }; @@ -1796,6 +1799,21 @@ function handleSelects() { wbIsBgTransparent = !wbIsBgTransparent; wbIsBgTransparent ? wbCanvasBackgroundColor('rgba(0, 0, 0, 0.100)') : setTheme(); }; + // room moderator rules + switchEveryoneMute.onchange = (e) => { + const startMuted = e.currentTarget.checked; + rc.updateRoomModerator({ type: 'audio', status: startMuted }); + lsSettings.moderator_audio_muted = startMuted; + lS.setSettings(lsSettings); + e.target.blur(); + }; + switchEveryoneHidden.onchange = (e) => { + const startHidden = e.currentTarget.checked; + rc.updateRoomModerator({ type: 'video', status: startHidden }); + lsSettings.moderator_video_hidden = startHidden; + lS.setSettings(lsSettings); + e.target.blur(); + }; } // #################################################### @@ -1954,6 +1972,7 @@ function loadSettingsFromLocalStorage() { 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; @@ -2050,7 +2069,7 @@ function handleRoomClientEvents() { show(stopVideoButton); setColor(startVideoButton, 'red'); setVideoButtonsDisabled(false); - if (isParticipantsListOpen) getRoomParticipants(true); + // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.pauseVideo, () => { console.log('Room event: Client pause video'); @@ -2068,13 +2087,13 @@ function handleRoomClientEvents() { show(startVideoButton); setVideoButtonsDisabled(false); isVideoPrivacyActive = false; - if (isParticipantsListOpen) getRoomParticipants(true); + // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.startScreen, () => { console.log('Room event: Client start screen'); hide(startScreenButton); show(stopScreenButton); - if (isParticipantsListOpen) getRoomParticipants(true); + // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.pauseScreen, () => { console.log('Room event: Client pause screen'); @@ -2086,7 +2105,7 @@ function handleRoomClientEvents() { console.log('Room event: Client stop screen'); hide(stopScreenButton); show(startScreenButton); - if (isParticipantsListOpen) getRoomParticipants(true); + // if (isParticipantsListOpen) getRoomParticipants(); }); rc.on(RoomClient.EVENTS.roomLock, () => { console.log('Room event: Client lock room'); @@ -2867,7 +2886,7 @@ async function saveRoomPeers() { saveObjToJsonFile(peersToSave, 'PARTICIPANTS'); } -async function getRoomParticipants(refresh = false) { +async function getRoomParticipants() { const peers = await getRoomPeers(); const lists = await getParticipantsList(peers); participantsCount = peers.size; diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index a38474a1..4fbf83d4 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.2.1 + * @version 1.2.2 * */ @@ -158,6 +158,10 @@ class RoomClient { this.peer_uuid = peer_uuid; this.peer_info = peer_info; + // Moderator + this.start_muted = false; + this.start_hidden = false; + this.isAudioAllowed = isAudioAllowed; this.isVideoAllowed = isVideoAllowed; this.isScreenAllowed = isScreenAllowed; @@ -363,6 +367,13 @@ class RoomClient { ? (console.log('07.1 ----> WARNING Room Host only recording enabled'), this.event(_EVENTS.hostOnlyRecordingOn)) : this.event(_EVENTS.hostOnlyRecordingOff); + + // Handle Room moderator rules + if (!isRulesActive || !isPresenter) { + console.log('07.2 ----> MODERATOR', room.moderator); + this.start_muted = room.moderator && room.moderator.start_audio_muted; + this.start_hidden = room.moderator && room.moderator.start_video_hidden; + } } adaptAspectRatio(participantsCount); for (let peer of Array.from(peers.keys()).filter((id) => id !== this.peer_id)) { @@ -586,7 +597,7 @@ class RoomClient { this.lobbyRemoveMe(data.peer_id); participantsCount = data.peer_counts; adaptAspectRatio(participantsCount); - if (isParticipantsListOpen) getRoomParticipants(true); + if (isParticipantsListOpen) getRoomParticipants(); }.bind(this), ); @@ -807,21 +818,26 @@ class RoomClient { async startLocalMedia() { console.log('08 ----> Start local media'); - if (this.isAudioAllowed) { + if (this.isAudioAllowed && !this.start_muted) { console.log('09 ----> Start audio media'); this.produce(mediaType.audio, microphoneSelect.value); } else { - setColor(startAudioButton, 'red'); console.log('09 ----> Audio is off'); + setColor(startAudioButton, 'red'); + this.setIsAudio(this.peer_id, false); + this.event(_EVENTS.stopAudio); + this.updatePeerInfo(this.peer_name, this.peer_id, 'audio', false); } - if (this.isVideoAllowed) { + if (this.isVideoAllowed && !this.start_hidden) { console.log('10 ----> Start video media'); this.produce(mediaType.video, videoSelect.value); } else { - setColor(startVideoButton, 'red'); console.log('10 ----> Video is off'); + setColor(startVideoButton, 'red'); this.setVideoOff(this.peer_info, false); this.sendVideoOff(); + this.event(_EVENTS.stopVideo); + this.updatePeerInfo(this.peer_name, this.peer_id, 'video', false); } if (this.joinRoomWithScreen) { console.log('08 ----> Start Screen media'); @@ -1504,7 +1520,7 @@ class RoomClient { this.myAudioEl = elem; this.localAudioEl.appendChild(elem); this.attachMediaStream(elem, stream, type, 'Producer'); - if (this.isAudioAllowed && !speakerSelect.disabled) { + if (this.isAudioAllowed && !this.start_muted && !speakerSelect.disabled) { this.attachSinkId(elem, speakerSelect.value); } console.log('[addProducer] audio-element-count', this.localAudioEl.childElementCount); @@ -1634,8 +1650,6 @@ class RoomClient { async produceScreenAudio(stream) { try { - //this.stopMyAudio(); - if (this.producerLabel.has(mediaType.audioTab)) { return console.log('Producer already exists for this type ' + mediaType.audioTab); } @@ -1658,7 +1672,6 @@ class RoomClient { producerSa.on('trackended', () => { this.closeProducer(mediaType.audioTab); - // this.startMyAudio(); }); producerSa.on('transportclose', () => { @@ -1687,20 +1700,6 @@ class RoomClient { } } - startMyAudio() { - startAudioButton.click(); - this.setIsAudio(this.peer_id, true); - this.event(_EVENTS.startAudio); - setAudioButtonsDisabled(false); - } - - stopMyAudio() { - stopAudioButton.click(); - this.setIsAudio(this.peer_id, false); - this.event(_EVENTS.stopAudio); - setAudioButtonsDisabled(true); - } - // #################################################### // CONSUMER // #################################################### @@ -1717,7 +1716,7 @@ class RoomClient { this.consumers.set(consumer.id, consumer); if (kind === 'video') { - if (isParticipantsListOpen) getRoomParticipants(true); + if (isParticipantsListOpen) getRoomParticipants(); } this.handleConsumer(consumer.id, type, stream, peer_name, peer_info); @@ -2056,7 +2055,7 @@ class RoomClient { this.setVideoAvatarImgName(i.id, peer_name); this.getId(i.id).style.display = 'block'; handleAspectRatio(); - if (isParticipantsListOpen) getRoomParticipants(true); + if (isParticipantsListOpen) getRoomParticipants(); if (!this.isMobileDevice && remotePeer) { this.setTippy(sm.id, 'Send message', 'bottom'); this.setTippy(sf.id, 'Send file', 'bottom'); @@ -2927,7 +2926,7 @@ class RoomClient { const chatRoom = this.getId('chatRoom'); chatRoom.classList.toggle('show'); if (!this.isChatOpen) { - await getRoomParticipants(true); + await getRoomParticipants(); hide(chatMinButton); if (!this.isMobileDevice) { show(chatMaxButton); @@ -5118,7 +5117,7 @@ class RoomClient { }).then(() => { switch (action) { case 'refresh': - getRoomParticipants(true); + getRoomParticipants(); break; case 'eject': this.exit(); @@ -5369,11 +5368,22 @@ class RoomClient { elemDisplay('chatPrivateMessages', false); } + // #################################################### + // UPDATE ROOM MODERATOR + // #################################################### + + updateRoomModerator(data) { + if (!isRulesActive || isPresenter) { + this.socket.emit('updateRoomModerator', data); + } + } + // #################################################### // UPDATE PEER INFO // #################################################### updatePeerInfo(peer_name, peer_id, type, status, emit = true) { + alert(type); if (emit) { switch (type) { case 'audio': @@ -5437,7 +5447,7 @@ class RoomClient { break; } } - if (isParticipantsListOpen) getRoomParticipants(true); + if (isParticipantsListOpen) getRoomParticipants(); } checkPeerInfoStatus(peer_info) { diff --git a/public/js/Rules.js b/public/js/Rules.js index 4d349660..597d3812 100644 --- a/public/js/Rules.js +++ b/public/js/Rules.js @@ -30,6 +30,7 @@ const BUTTONS = { unlockRoomButton: true, // presenter lobbyButton: true, // presenter micOptionsButton: true, // presenter + tabModerator: true, // presenter tabRecording: true, pushToTalk: true, host_only_recording: true, // presenter @@ -87,6 +88,7 @@ function handleRules(isPresenter) { BUTTONS.settings.unlockRoomButton = false; BUTTONS.settings.lobbyButton = false; BUTTONS.settings.micOptionsButton = false; + BUTTONS.settings.tabModerator = false; BUTTONS.videoOff.muteAudioButton = false; BUTTONS.videoOff.ejectButton = false; BUTTONS.consumerVideo.ejectButton = false; @@ -100,6 +102,7 @@ function handleRules(isPresenter) { BUTTONS.settings.unlockRoomButton = isRoomLocked; BUTTONS.settings.lobbyButton = true; BUTTONS.settings.micOptionsButton = true; + BUTTONS.settings.tabModerator = true; BUTTONS.videoOff.muteAudioButton = true; BUTTONS.videoOff.ejectButton = true; BUTTONS.consumerVideo.ejectButton = true; @@ -120,13 +123,18 @@ function handleRules(isPresenter) { hostOnlyRecording = lsSettings.host_only_recording; switchHostOnlyRecording.checked = hostOnlyRecording; rc.roomAction(hostOnlyRecording ? 'hostOnlyRecordingOn' : 'hostOnlyRecordingOff', true, false); - //... + // Room moderator + switchEveryoneMute.checked = lsSettings.moderator_audio_muted; + switchEveryoneHidden.checked = lsSettings.moderator_video_hidden; + rc.updateRoomModerator({ type: 'audio', status: switchEveryoneMute.checked }); + rc.updateRoomModerator({ type: 'video', status: switchEveryoneHidden.checked }); } // main. settings... 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.settings.tabModerator && hide(tabModeratorBtn); BUTTONS.participantsList.saveInfoButton ? show(participantsSaveBtn) : hide(participantsSaveBtn); BUTTONS.whiteboard.whiteboardLockButton ? elemDisplay('whiteboardLockButton', true) diff --git a/public/js/landing.js b/public/js/landing.js index 52666d71..72bf1727 100644 --- a/public/js/landing.js +++ b/public/js/landing.js @@ -182,14 +182,14 @@ i ? (i.classList.add('is-active'), l.classList.add('is-active')) : 'next' === t - ? (e.firstElementChild.classList.add('is-active'), - e.parentNode - .getElementsByClassName('carousel-bullets')[0] - .firstElementChild.classList.add('is-active')) - : (e.lastElementChild.classList.add('is-active'), - e.parentNode - .getElementsByClassName('carousel-bullets')[0] - .lastElementChild.classList.add('is-active')); + ? (e.firstElementChild.classList.add('is-active'), + e.parentNode + .getElementsByClassName('carousel-bullets')[0] + .firstElementChild.classList.add('is-active')) + : (e.lastElementChild.classList.add('is-active'), + e.parentNode + .getElementsByClassName('carousel-bullets')[0] + .lastElementChild.classList.add('is-active')); } function i(e, t) { diff --git a/public/sfu/MediasoupClient.js b/public/sfu/MediasoupClient.js index 9e32f2bd..5b85277b 100644 --- a/public/sfu/MediasoupClient.js +++ b/public/sfu/MediasoupClient.js @@ -14084,10 +14084,10 @@ typeof global !== 'undefined' ? global : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' - ? window - : {}, + ? self + : typeof window !== 'undefined' + ? window + : {}, ); }, {}, @@ -14649,8 +14649,8 @@ var keyLocation = obj.push ? {} // blank object that will be pushed : needsBlank - ? location[obj.name] - : location; // otherwise, named location or root + ? location[obj.name] + : location; // otherwise, named location or root attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); diff --git a/public/views/Room.html b/public/views/Room.html index ab7d3b65..630d5216 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -185,6 +185,9 @@ access to use this app. + @@ -588,6 +591,42 @@ access to use this app.
+
+ + + + + + + + + +
+
+ +

Everyone starts muted

+
+
+
+ +
+
+
+ +

Everyone starts hidden

+
+
+
+ +
+
+
+