diff --git a/app/src/Room.js b/app/src/Room.js index 4481bf8f..d8148f6d 100644 --- a/app/src/Room.js +++ b/app/src/Room.js @@ -12,6 +12,9 @@ module.exports = class Room { this.audioLevelObserver = null; this.audioLevelObserverEnabled = true; this.audioLastUpdateTime = 0; + // ########################## + this._isBroadcasting = false; + // ########################## this._isLocked = false; this._isLobbyEnabled = false; this._roomPassword = null; @@ -109,6 +112,11 @@ module.exports = class Room { // ROOM MODERATOR // #################################################### + updateRoomModeratorALL(data) { + this._moderator = data; + log.debug('Update room moderator all data', this._moderator); + } + updateRoomModerator(data) { log.debug('Update room moderator', data); switch (data.type) { @@ -137,6 +145,7 @@ module.exports = class Room { toJson() { return { id: this.id, + broadcasting: this._isBroadcasting, config: { isLocked: this._isLocked, isLobbyEnabled: this._isLobbyEnabled, @@ -312,6 +321,10 @@ module.exports = class Room { // ROOM STATUS // #################################################### + // GET + isBroadcasting() { + return this._isBroadcasting; + } getPassword() { return this._roomPassword; } @@ -324,6 +337,11 @@ module.exports = class Room { isHostOnlyRecording() { return this._hostOnlyRecording; } + + // SET + setIsBroadcasting(status) { + this._isBroadcasting = status; + } setLocked(status, password) { this._isLocked = status; this._roomPassword = password; diff --git a/app/src/Server.js b/app/src/Server.js index 7b17a790..3ef3906e 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.8 + * @version 1.2.9 * */ @@ -630,7 +630,7 @@ function startServer() { const room = roomList.get(socket.room_id); - let peerCounts = room.getPeersCount(); + const peerCounts = room.getPeersCount(); log.debug('Peer counts', { peerCounts: peerCounts }); @@ -668,7 +668,13 @@ function startServer() { const room = roomList.get(socket.room_id); log.debug('Room action:', data); + switch (data.action) { + case 'broadcasting': + if (!isPresenter) return; + room.setIsBroadcasting(data.room_broadcasting); + room.broadCast(socket.id, 'roomAction', data.action); + break; case 'lock': if (!isPresenter) return; if (!room.isLocked()) { @@ -716,6 +722,7 @@ function startServer() { break; } log.debug('Room status', { + broadcasting: room.isBroadcasting(), locked: room.isLocked(), lobby: room.isLobbyEnabled(), hostOnlyRecording: room.isHostOnlyRecording(), @@ -780,31 +787,58 @@ function startServer() { const room = roomList.get(socket.room_id); - // update my peer_info status to all in the room room.getPeers().get(socket.id).updatePeerInfo(data); - room.broadCast(socket.id, 'updatePeerInfo', data); + + if (data.broadcast) { + log.info('updatePeerInfo broadcast data'); + room.broadCast(socket.id, 'updatePeerInfo', data); + } }); - socket.on('updateRoomModerator', (dataObject) => { + socket.on('updateRoomModerator', async (dataObject) => { if (!roomList.has(socket.room_id)) return; const data = checkXSS(dataObject); const room = roomList.get(socket.room_id); - room.updateRoomModerator(data); + const isPresenter = await isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); - switch (data.type) { + if (!isPresenter) return; + + const moderator = data.moderator; + + room.updateRoomModerator(moderator); + + switch (moderator.type) { case 'audio_cant_unmute': case 'video_cant_unhide': case 'screen_cant_share': - room.broadCast(socket.id, 'updateRoomModerator', data); + room.broadCast(socket.id, 'updateRoomModerator', moderator); break; default: break; } }); + socket.on('updateRoomModeratorALL', async (dataObject) => { + if (!roomList.has(socket.room_id)) return; + + const data = checkXSS(dataObject); + + const room = roomList.get(socket.room_id); + + const isPresenter = await isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); + + if (!isPresenter) return; + + const moderator = data.moderator; + + room.updateRoomModeratorALL(moderator); + + room.broadCast(socket.id, 'updateRoomModeratorALL', moderator); + }); + socket.on('fileInfo', (dataObject) => { if (!roomList.has(socket.room_id)) return; @@ -886,9 +920,10 @@ function startServer() { const data = checkXSS(dataObject); + log.debug('Video off data', data.peer_name); + const room = roomList.get(socket.room_id); - log.debug('Video off', getPeerName(room)); room.broadCast(socket.id, 'setVideoOff', data); }); @@ -930,7 +965,7 @@ function startServer() { const isPeerValid = isAuthPeer(peer_username, peer_password); - log.debug('[' + socket.id + '] JOIN ROOM - HOST PROTECTED - USER AUTH check peer', { + log.debug('[Join] - HOST PROTECTED - USER AUTH check peer', { ip: peer_ip, peer_username: peer_username, peer_password: peer_password, @@ -953,8 +988,10 @@ function startServer() { if (!(socket.room_id in presenters)) presenters[socket.room_id] = {}; - const peer_name = room.getPeers()?.get(socket.id)?.peer_info?.peer_name; - const peer_uuid = room.getPeers()?.get(socket.id)?.peer_info?.peer_uuid; + const peer = room.getPeers()?.get(socket.id)?.peer_info; + const peer_id = peer.peer_id; + const peer_name = peer.peer_name; + const peer_uuid = peer.peer_uuid; // Set the presenters const presenter = { @@ -995,8 +1032,8 @@ function startServer() { 'The user is currently waiting to join the room because the lobby is enabled, and they are not a presenter', ); room.broadCast(socket.id, 'roomLobby', { - peer_id: data.peer_info.peer_id, - peer_name: data.peer_info.peer_name, + peer_id: peer_id, + peer_name: peer_name, lobby_status: 'waiting', }); return cb('isLobby'); @@ -1235,8 +1272,10 @@ function startServer() { const room = roomList.get(socket.room_id); - const peerName = room.getPeers()?.get(socket.id)?.peer_info?.peer_name || ''; - const peerUuid = room.getPeers()?.get(socket.id)?.peer_info?.peer_uuid || ''; + const peer = room.getPeers()?.get(socket.id)?.peer_info; + + const peerName = peer.peer_name || ''; + const peerUuid = peer.peer_uuid || ''; const isPresenter = await isPeerPresenter(socket.room_id, socket.id, peerName, peerUuid); @@ -1245,12 +1284,7 @@ function startServer() { room.removePeer(socket.id); if (room.getPeers().size === 0) { - if (room.isLocked()) { - room.setLocked(false); - } - if (room.isLobbyEnabled()) { - room.setLobbyEnabled(false); - } + // roomList.delete(socket.room_id); const activeRooms = getActiveRooms(); @@ -1275,8 +1309,11 @@ function startServer() { const room = roomList.get(socket.room_id); - const peerName = room.getPeers()?.get(socket.id)?.peer_info?.peer_name || ''; - const peerUuid = room.getPeers()?.get(socket.id)?.peer_info?.peer_uuid || ''; + const peer = room.getPeers()?.get(socket.id)?.peer_info; + + const peerName = peer.peer_name || ''; + const peerUuid = peer.peer_uuid || ''; + const isPresenter = await isPeerPresenter(socket.room_id, socket.id, peerName, peerUuid); log.debug('Exit room', peerName); @@ -1287,6 +1324,7 @@ function startServer() { room.broadCast(socket.id, 'removeMe', removeMeData(room, peerName, isPresenter)); if (room.getPeers().size === 0) { + // roomList.delete(socket.room_id); delete presenters[socket.room_id]; diff --git a/app/src/config.template.js b/app/src/config.template.js index a368f6ee..d68cee2e 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -62,7 +62,7 @@ module.exports = { Additional layers can be added to specify valid presenters and co-presenters by setting designated usernames. */ 'Miroslav Pejic', - 'MiroTalk Admin', + 'MiroTalk SFU', ], console: { debug: true, diff --git a/package.json b/package.json index dd471532..22d8560f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.2.8", + "version": "1.2.9", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { @@ -34,8 +34,8 @@ "author": "Miroslav Pejic", "license": "AGPL-3.0", "dependencies": { - "@sentry/integrations": "7.81.1", - "@sentry/node": "7.81.1", + "@sentry/integrations": "7.83.0", + "@sentry/node": "7.83.0", "axios": "^1.6.2", "body-parser": "1.20.2", "colors": "1.4.0", @@ -44,10 +44,10 @@ "crypto-js": "4.2.0", "express": "4.18.2", "httpolyglot": "0.1.2", - "mediasoup": "3.13.5", + "mediasoup": "3.13.6", "mediasoup-client": "3.7.0", "ngrok": "^4.3.3", - "openai": "^4.20.0", + "openai": "^4.20.1", "qs": "6.11.2", "socket.io": "4.7.2", "swagger-ui-express": "5.0.0", diff --git a/public/css/GroupChat.css b/public/css/GroupChat.css index 7358482a..1a966f22 100644 --- a/public/css/GroupChat.css +++ b/public/css/GroupChat.css @@ -252,7 +252,7 @@ .chat .chat-history .other-message { background: var(--left-msg-bg); - text-align: right; + text-align: left; } .chat .chat-history .other-message:after { diff --git a/public/js/LocalStorage.js b/public/js/LocalStorage.js index fa735811..ec027f6d 100644 --- a/public/js/LocalStorage.js +++ b/public/js/LocalStorage.js @@ -35,6 +35,7 @@ class LocalStorage { mic_volume: 100, // % video_fps: 0, // default 1280x768 30fps screen_fps: 0, // max 30fps + broadcasting: false, // default false (one to many a/v streaming) lobby: false, // default false pitch_bar: true, // volume indicator sounds: true, // room notify sounds diff --git a/public/js/Room.js b/public/js/Room.js index fb00797d..5e241885 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.8 + * @version 1.2.9 * */ @@ -34,6 +34,8 @@ let redirect = { }; const _PEER = { + presenter: '', + guest: '', audioOn: '', audioOff: '', videoOn: '', @@ -99,6 +101,7 @@ let isPushToTalkActive = false; let isSpaceDown = false; let isPitchBarEnabled = true; let isSoundEnabled = true; +let isBroadcastingEnabled = false; let isLobbyEnabled = false; let isLobbyOpen = false; let hostOnlyRecording = false; @@ -164,6 +167,11 @@ function initClient() { ); setTippy('lobbyAcceptAllBtn', 'Accept', 'top'); setTippy('lobbyRejectAllBtn', 'Reject', 'top'); + setTippy( + 'switchBroadcasting', + 'Broadcasting is the dissemination of audio or video content to a large audience (one to many)', + 'right', + ); setTippy( 'switchLobby', 'Lobby mode lets you protect your meeting by only allowing people to enter after a formal approval by a moderator', @@ -252,7 +260,7 @@ function refreshMainButtonsToolTipPlacement() { setTippy('stopRecButton', 'Stop recording', placement); setTippy('raiseHandButton', 'Raise your hand', placement); setTippy('lowerHandButton', 'Lower your hand', placement); - setTippy('roomEmojiPicker', 'Toggle emoji reaction', placement); + setTippy('emojiRoomButton', 'Toggle emoji reaction', placement); setTippy('swapCameraButton', 'Swap the camera', placement); setTippy('chatButton', 'Toggle the chat', placement); setTippy('transcriptionButton', 'Toggle transcription', placement); @@ -345,7 +353,7 @@ async function initEnumerateVideoDevices() { } function enumerateVideoDevices(stream) { - console.log('03 ----> Get Video Devices'); + console.log('02 ----> Get Video Devices'); navigator.mediaDevices .enumerateDevices() .then((devices) => @@ -383,7 +391,7 @@ async function initEnumerateAudioDevices() { } function enumerateAudioDevices(stream) { - console.log('02 ----> Get Audio Devices'); + console.log('03 ----> Get Audio Devices'); navigator.mediaDevices .enumerateDevices() .then((devices) => @@ -673,7 +681,7 @@ function getPeerInfo() { // #################################################### function whoAreYou() { - console.log('04 ----> Who are you'); + console.log('04 ----> Who are you?'); hide(loadingDiv); document.body.style.background = 'var(--body-bg)'; @@ -1001,7 +1009,7 @@ function roomIsReady() { } BUTTONS.main.chatButton && show(chatButton); BUTTONS.main.raiseHandButton && show(raiseHandButton); - BUTTONS.main.emojiRoomButton && show(roomEmojiPicker); + BUTTONS.main.emojiRoomButton && show(emojiRoomButton); !BUTTONS.chat.chatSaveButton && hide(chatSaveButton); BUTTONS.chat.chatEmojiButton && show(chatEmojiButton); BUTTONS.chat.chatMarkdownButton && show(chatMarkdownButton); @@ -1058,6 +1066,7 @@ function roomIsReady() { isVideoAllowed ? show(stopVideoButton) : BUTTONS.main.startVideoButton && show(startVideoButton); show(fileShareButton); BUTTONS.settings.lockRoomButton && show(lockRoomButton); + BUTTONS.settings.broadcastingButton && show(broadcastingButton); BUTTONS.settings.lobbyButton && show(lobbyButton); BUTTONS.settings.host_only_recording && show(roomRecording); BUTTONS.main.aboutButton && show(aboutButton); @@ -1513,7 +1522,7 @@ function handleSelectsInit() { function setSelectsInit() { const localStorageDevices = lS.getLocalStorageDevices(); - console.log('04 ----> Get Local Storage Devices before', localStorageDevices); + console.log('04.0 ----> Get Local Storage Devices before', localStorageDevices); if (localStorageDevices) { initMicrophoneSelect.selectedIndex = localStorageDevices.audio.index; initSpeakerSelect.selectedIndex = localStorageDevices.speaker.index; @@ -1703,6 +1712,13 @@ function handleSelects() { } }); // room + switchBroadcasting.onchange = (e) => { + isBroadcastingEnabled = e.currentTarget.checked; + rc.roomAction('broadcasting'); + lsSettings.broadcasting = isBroadcastingEnabled; + lS.setSettings(lsSettings); + e.target.blur(); + }; switchLobby.onchange = (e) => { isLobbyEnabled = e.currentTarget.checked; rc.roomAction(isLobbyEnabled ? 'lobbyOn' : 'lobbyOff'); @@ -1951,10 +1967,16 @@ function handleInputs() { ':N': '🥶', ':J': '🥴', }; - for (let i in chatInputEmoji) { - let regex = new RegExp(i.replace(/([()[{*+.$^\\|?])/g, '\\$1'), 'gim'); - this.value = this.value.replace(regex, chatInputEmoji[i]); - } + // Create a regular expression pattern for all keys in chatInputEmoji + const regexPattern = new RegExp( + Object.keys(chatInputEmoji) + .map((key) => key.replace(/([()[{*+.$^\\|?])/g, '\\$1')) + .join('|'), + 'gim', + ); + // Replace matching patterns with corresponding emojis + this.value = this.value.replace(regexPattern, (match) => chatInputEmoji[match]); + rc.checkLineBreaks(); }; @@ -1992,7 +2014,7 @@ function handleRoomEmojiPicker() { emojiPickerContainer.appendChild(emojiRoomPicker); emojiPickerContainer.style.display = 'none'; - roomEmojiPicker.onclick = () => { + emojiRoomButton.onclick = () => { toggleEmojiPicker(); }; closeEmojiPickerContainer.onclick = () => { @@ -2016,10 +2038,10 @@ function handleRoomEmojiPicker() { function toggleEmojiPicker() { if (emojiPickerContainer.style.display === 'block') { emojiPickerContainer.style.display = 'none'; - setColor(roomEmojiPicker, 'white'); + setColor(emojiRoomButton, 'white'); } else { emojiPickerContainer.style.display = 'block'; - setColor(roomEmojiPicker, 'yellow'); + setColor(emojiRoomButton, 'yellow'); } } } @@ -2968,6 +2990,7 @@ async function getRoomParticipants() { participantsList.innerHTML = lists; refreshParticipantsCount(participantsCount, false); setParticipantsTippy(peers); + console.log('*** Refresh Chat participant lists ***'); } async function getParticipantsList(peers) { @@ -3045,6 +3068,7 @@ async function getParticipantsList(peers) { for (const peer of Array.from(peers.keys())) { const peer_info = peers.get(peer).peer_info; const peer_name = peer_info.peer_name; + //const peer_presenter = peer_info.peer_presenter ? _PEER.presenter : _PEER.guest; const peer_audio = peer_info.peer_audio ? _PEER.audioOn : _PEER.audioOff; const peer_video = peer_info.peer_video ? _PEER.videoOn : _PEER.videoOff; const peer_screen = peer_info.peer_screen ? _PEER.screenOn : _PEER.screenOff; @@ -3100,6 +3124,9 @@ async function getParticipantsList(peers) { `; + // li += ` + // `; + if (peer_info.peer_hand) { li += ` `; @@ -3127,7 +3154,11 @@ async function getParticipantsList(peers) {
${peer_name}
online
+ `; + // NO ROOM BROADCASTING + if (!isBroadcastingEnabled) { + li += `
+ `; + } + li += `
@@ -3151,10 +3185,15 @@ async function getParticipantsList(peers) { `; + + // li += ` + // `; + if (peer_info.peer_hand) { li += ` `; } + li += `
@@ -3175,9 +3214,14 @@ function setParticipantsTippy(peers) { for (let peer of Array.from(peers.keys())) { const peer_info = peers.get(peer).peer_info; const peer_id = peer_info.peer_id; - setTippy(peer_id + '___pAudio', 'Mute', 'top'); - setTippy(peer_id + '___pVideo', 'Hide', 'top'); - setTippy(peer_id + '___pScreen', 'Stop', 'top'); + + const peerAudioBtn = rc.getId(peer_id + '___pAudio'); + const peerVideoBtn = rc.getId(peer_id + '___pVideo'); + const peerScreenBtn = rc.getId(peer_id + '___pScreen'); + + if (peerAudioBtn) setTippy(peerAudioBtn.id, 'Mute', 'top'); + if (peerVideoBtn) setTippy(peerVideoBtn.id, 'Hide', 'top'); + if (peerScreenBtn) setTippy(peerScreenBtn.id, 'Stop', 'top'); } } } diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index 800f1c81..ca706996 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.8 + * @version 1.2.9 * */ @@ -42,6 +42,7 @@ const html = { }; const icons = { + room: '', chat: '', user: '', transcript: '', @@ -358,7 +359,11 @@ class RoomClient { this.device = await this.loadDevice(data); console.log('07.3 ----> Get Router Rtp Capabilities codecs: ', this.device.rtpCapabilities.codecs); await this.initTransports(this.device); - await this.startLocalMedia(); + if (isBroadcastingEnabled) { + isPresenter ? await this.startLocalMedia() : await this.handleRoomBroadcasting(); + } else { + await this.startLocalMedia(); + } this.socket.emit('getProducers'); } @@ -369,13 +374,19 @@ class RoomClient { redirect = room.redirect; let peers = new Map(JSON.parse(room.peers)); participantsCount = peers.size; + // ME for (let peer of Array.from(peers.keys()).filter((id) => id == this.peer_id)) { let my_peer_info = peers.get(peer).peer_info; console.log('07.1 ----> My Peer info', my_peer_info); isPresenter = window.localStorage.isReconnected === 'true' ? isPresenter : my_peer_info.peer_presenter; + this.peer_info.peer_presenter = isPresenter; this.getId('isUserPresenter').innerText = isPresenter; window.localStorage.isReconnected = false; handleRules(isPresenter); + // ################################################################################################### + isBroadcastingEnabled = isPresenter && !room.broadcasting ? isBroadcastingEnabled : room.broadcasting; + console.log('07.1 ----> ROOM BROADCASTING', isBroadcastingEnabled); + // ################################################################################################### room.config.hostOnlyRecording ? (console.log('07.1 ----> WARNING Room Host only recording enabled'), this.event(_EVENTS.hostOnlyRecordingOn)) @@ -404,13 +415,17 @@ class RoomClient { this._moderator.video_cant_unhide ? hide(tabVideoDevicesBtn) : show(tabVideoDevicesBtn); } } - adaptAspectRatio(participantsCount); + // PARTICIPANTS for (let peer of Array.from(peers.keys()).filter((id) => id !== this.peer_id)) { let peer_info = peers.get(peer).peer_info; // console.log('07.1 ----> Remote Peer info', peer_info); - if (!peer_info.peer_video) { + const canSetVideoOff = !isBroadcastingEnabled || (isBroadcastingEnabled && peer_info.peer_presenter); + + if (!peer_info.peer_video && canSetVideoOff) { + console.log('Detected peer video off ' + peer_info.peer_name); await this.setVideoOff(peer_info, true); } + if (peer_info.peer_recording) { this.handleRecordingAction({ peer_id: peer_info.id, @@ -419,8 +434,11 @@ class RoomClient { }); } } + this.refreshParticipantsCount(); + console.log('07.2 Participants Count ---->', participantsCount); + // notify && participantsCount == 1 ? shareRoom() : sound('joined'); if (notify && participantsCount == 1) { shareRoom(); @@ -605,7 +623,7 @@ class RoomClient { this.socket.on( 'consumerClosed', function ({ consumer_id, consumer_kind }) { - console.log('Closing consumer', { consumer_id: consumer_id, consumer_kind: consumer_kind }); + console.log('SocketOn Closing consumer', { consumer_id: consumer_id, consumer_kind: consumer_kind }); this.removeConsumer(consumer_id, consumer_kind); }.bind(this), ); @@ -613,19 +631,24 @@ class RoomClient { this.socket.on( 'setVideoOff', function (data) { - console.log('Video off:', data); - this.setVideoOff(data, true); + if (!isBroadcastingEnabled || (isBroadcastingEnabled && data.peer_presenter)) { + console.log('SocketOn setVideoOff', { + peer_name: data.peer_name, + peer_presenter: data.peer_presenter, + }); + this.setVideoOff(data, true); + } }.bind(this), ); this.socket.on( 'removeMe', function (data) { - console.log('Remove me:', data); + console.log('SocketOn Remove me:', data); this.removeVideoOff(data.peer_id); this.lobbyRemoveMe(data.peer_id); participantsCount = data.peer_counts; - adaptAspectRatio(participantsCount); + if (!isBroadcastingEnabled) adaptAspectRatio(participantsCount); if (isParticipantsListOpen) getRoomParticipants(); }.bind(this), ); @@ -633,9 +656,14 @@ class RoomClient { this.socket.on( 'refreshParticipantsCount', function (data) { - console.log('Participants Count:', data); + console.log('SocketOn Participants Count:', data); participantsCount = data.peer_counts; - adaptAspectRatio(participantsCount); + if (isBroadcastingEnabled) { + if (isParticipantsListOpen) getRoomParticipants(); + wbUpdate(); + } else { + adaptAspectRatio(participantsCount); + } }.bind(this), ); @@ -643,7 +671,7 @@ class RoomClient { 'newProducers', async function (data) { if (data.length > 0) { - console.log('New producers', data); + console.log('SocketOn New producers', data); for (let { producer_id, peer_name, peer_info, type } of data) { await this.consume(producer_id, peer_name, peer_info, type); } @@ -654,7 +682,7 @@ class RoomClient { this.socket.on( 'message', function (data) { - console.log('New message:', data); + console.log('SocketOn New message:', data); this.showMessage(data); }.bind(this), ); @@ -662,7 +690,7 @@ class RoomClient { this.socket.on( 'roomAction', function (data) { - console.log('Room action:', data); + console.log('SocketOn Room action:', data); this.roomAction(data, false); }.bind(this), ); @@ -670,7 +698,7 @@ class RoomClient { this.socket.on( 'roomPassword', function (data) { - console.log('Room password:', data.password); + console.log('SocketOn Room password:', data.password); this.roomPassword(data); }.bind(this), ); @@ -678,7 +706,7 @@ class RoomClient { this.socket.on( 'roomLobby', function (data) { - console.log('Room lobby:', data); + console.log('SocketOn Room lobby:', data); this.roomLobby(data); }.bind(this), ); @@ -686,7 +714,7 @@ class RoomClient { this.socket.on( 'cmd', function (data) { - console.log('Peer cmd:', data); + console.log('SocketOn Peer cmd:', data); this.handleCmd(data); }.bind(this), ); @@ -694,7 +722,7 @@ class RoomClient { this.socket.on( 'peerAction', function (data) { - console.log('Peer action:', data); + console.log('SocketOn Peer action:', data); this.peerAction(data.from_peer_name, data.peer_id, data.action, false, data.broadcast); }.bind(this), ); @@ -702,15 +730,15 @@ class RoomClient { this.socket.on( 'updatePeerInfo', function (data) { - console.log('Peer info update:', data); - this.updatePeerInfo(data.peer_name, data.peer_id, data.type, data.status, false); + console.log('SocketOn Peer info update:', data); + this.updatePeerInfo(data.peer_name, data.peer_id, data.type, data.status, false, data.peer_presenter); }.bind(this), ); this.socket.on( 'fileInfo', function (data) { - console.log('File info:', data); + console.log('SocketOn File info:', data); this.handleFileInfo(data); }.bind(this), ); @@ -739,7 +767,7 @@ class RoomClient { this.socket.on( 'wbCanvasToJson', function (data) { - console.log('Received whiteboard canvas JSON'); + console.log('SocketOn Received whiteboard canvas JSON'); JsonToWbCanvas(data); }.bind(this), ); @@ -762,15 +790,23 @@ class RoomClient { this.socket.on( 'updateRoomModerator', function (data) { - console.log('Update room moderator', data); + console.log('SocketOn Update room moderator', data); this.handleUpdateRoomModerator(data); }.bind(this), ); + this.socket.on( + 'updateRoomModeratorALL', + function (data) { + console.log('SocketOn Update room moderator ALL', data); + this.handleUpdateRoomModeratorALL(data); + }.bind(this), + ); + this.socket.on( 'recordingAction', function (data) { - console.log('Recording action:', data); + console.log('SocketOn Recording action:', data); this.handleRecordingAction(data); }.bind(this), ); @@ -778,7 +814,7 @@ class RoomClient { this.socket.on( 'connect', function () { - console.log('Connected to signaling server!'); + console.log('SocketOn Connected to signaling server!'); this._isConnected = true; // location.reload(); getPeerName() ? location.reload() : openURL(this.getReconnectDirectJoinURL()); @@ -849,6 +885,34 @@ class RoomClient { }); } + // #################################################### + // HANDLE ROOM BROADCASTING + // #################################################### + + async handleRoomBroadcasting() { + console.log('07.4 ----> Room Broadcasting is currently active, and you are not the designated presenter'); + + this.peer_info.peer_audio = false; + this.peer_info.peer_video = false; + this.peer_info.peer_screen = false; + + const mediaTypes = ['audio', 'video', 'screen']; + + mediaTypes.forEach((type) => { + const data = { + peer_name: this.peer_name, + peer_id: this.peer_id, + peer_presenter: isPresenter, + type: type, + status: false, + broadcast: true, + }; + this.socket.emit('updatePeerInfo', data); + }); + + handleRulesBroadcasting(); + } + // #################################################### // START LOCAL AUDIO VIDEO MEDIA // #################################################### @@ -1077,7 +1141,7 @@ class RoomClient { this.event(_EVENTS.startScreen); break; default: - return; + break; } this.sound('joined'); } catch (err) { @@ -1682,7 +1746,7 @@ class RoomClient { this.event(_EVENTS.stopScreen); break; default: - return; + break; } this.sound('left'); @@ -1749,7 +1813,7 @@ class RoomClient { wbUpdate(); this.getConsumeStream(producer_id, peer_info.peer_id, type).then( - function ({ consumer, stream, kind }) { + async function ({ consumer, stream, kind }) { console.log('CONSUMER MEDIA TYPE ----> ' + type); console.log('CONSUMER', consumer); @@ -1814,12 +1878,12 @@ class RoomClient { let remotePeerId = peer_info.peer_id; let remoteIsScreen = type == mediaType.screen; + let remotePeerAudio = peer_info.peer_audio; let remotePrivacyOn = peer_info.peer_video_privacy; switch (type) { case mediaType.video: case mediaType.screen: - let remotePeerAudio = peer_info.peer_audio; this.removeVideoOff(remotePeerId); d = document.createElement('div'); d.className = 'Camera'; @@ -1939,6 +2003,7 @@ class RoomClient { this.setTippy(pv.id, '🔊 Volume', 'bottom'); this.setTippy(ko.id, 'Eject', 'bottom'); } + this.setPeerAudio(remotePeerId, remotePeerAudio); break; case mediaType.audio: elem = document.createElement('audio'); @@ -1952,6 +2017,7 @@ class RoomClient { let inputPv = this.getId(audioConsumerId); if (inputPv) { this.handlePV(id + '___' + audioConsumerId); + this.setPeerAudio(remotePeerId, remotePeerAudio); } console.log('[Add audioConsumers]', this.audioConsumers); break; @@ -2016,6 +2082,7 @@ class RoomClient { // #################################################### async setVideoOff(peer_info, remotePeer = false) { + //console.log('setVideoOff', peer_info); let d, vb, i, h, au, sf, sm, sv, ko, p, pm, pb, pv; let peer_id = peer_info.peer_id; let peer_name = peer_info.peer_name; @@ -2104,6 +2171,8 @@ class RoomClient { this.setTippy(pv.id, '🔊 Volume', 'bottom'); this.setTippy(ko.id, 'Eject', 'bottom'); } + remotePeer ? this.setPeerAudio(peer_id, peer_audio) : this.setIsAudio(peer_id, peer_audio); + console.log('[setVideoOff] Video-element-count', this.videoMediaContainer.childElementCount); // wbUpdate(); @@ -2315,25 +2384,44 @@ class RoomClient { return 'data:image/svg+xml,' + svg.replace(/#/g, '%23').replace(/"/g, "'").replace(/&/g, '&'); } + setPeerAudio(peer_id, status) { + if (!isBroadcastingEnabled || (isBroadcastingEnabled && isPresenter)) { + console.log('Set peer audio enabled: ' + status); + const audioStatus = this.getPeerAudioBtn(peer_id); // producer, consumers + const audioVolume = this.getPeerAudioVolumeBtn(peer_id); // consumers + if (audioStatus) audioStatus.className = status ? html.audioOn : html.audioOff; + if (audioVolume) status ? show(audioVolume) : hide(audioVolume); + } + } + setIsAudio(peer_id, status) { - this.peer_info.peer_audio = status; - let b = this.getPeerAudioBtn(peer_id); - if (b) b.className = this.peer_info.peer_audio ? html.audioOn : html.audioOff; + if (!isBroadcastingEnabled || (isBroadcastingEnabled && isPresenter)) { + console.log('Set audio enabled: ' + status); + this.peer_info.peer_audio = status; + const audioStatus = this.getPeerAudioBtn(peer_id); // producer, consumers + if (audioStatus) audioStatus.className = status ? html.audioOn : html.audioOff; + } } setIsVideo(status) { - this.peer_info.peer_video = status; - if (!this.peer_info.peer_video) { - this.setVideoOff(this.peer_info, false); - this.sendVideoOff(); + if (!isBroadcastingEnabled || (isBroadcastingEnabled && isPresenter)) { + this.peer_info.peer_video = status; + if (!this.peer_info.peer_video) { + console.log('Set video enabled: ' + status); + this.setVideoOff(this.peer_info, false); + this.sendVideoOff(); + } } } setIsScreen(status) { - this.peer_info.peer_screen = status; - if (!this.peer_info.peer_screen && !this.peer_info.peer_video) { - this.setVideoOff(this.peer_info, false); - this.sendVideoOff(); + if (!isBroadcastingEnabled || (isBroadcastingEnabled && isPresenter)) { + this.peer_info.peer_screen = status; + if (!this.peer_info.peer_screen && !this.peer_info.peer_video) { + console.log('Set screen enabled: ' + status); + this.setVideoOff(this.peer_info, false); + this.sendVideoOff(); + } } } @@ -2394,6 +2482,10 @@ class RoomClient { return this.getId(peer_id + '__audio'); } + getPeerAudioVolumeBtn(peer_id) { + return this.getId(peer_id + '___pVolume'); + } + getPeerHandBtn(peer_id) { return this.getId(peer_id + '__hand'); } @@ -4462,7 +4554,8 @@ class RoomClient { // #################################################### roomAction(action, emit = true, popup = true) { - let data = { + const data = { + room_broadcasting: isBroadcastingEnabled, room_id: this.room_id, peer_id: this.peer_id, peer_name: this.peer_name, @@ -4472,6 +4565,10 @@ class RoomClient { }; if (emit) { switch (action) { + case 'broadcasting': + this.socket.emit('roomAction', data); + if (popup) this.roomStatus(action); + break; case 'lock': if (room_password) { this.socket @@ -4481,10 +4578,11 @@ class RoomClient { // Only the presenter can lock the room if (isPresenter || res.peerCounts == 1) { isPresenter = true; + this.peer_info.peer_presenter = isPresenter; this.getId('isUserPresenter').innerText = isPresenter; data.password = room_password; this.socket.emit('roomAction', data); - this.roomStatus(action); + if (popup) this.roomStatus(action); } }.bind(this), ) @@ -4519,7 +4617,7 @@ class RoomClient { break; case 'unlock': this.socket.emit('roomAction', data); - this.roomStatus(action); + if (popup) this.roomStatus(action); break; case 'lobbyOn': this.socket.emit('roomAction', data); @@ -4547,6 +4645,9 @@ class RoomClient { roomStatus(action) { switch (action) { + case 'broadcasting': + this.userLog('info', `${icons.room} BROADCASTING ${isBroadcastingEnabled ? 'On' : 'Off'}`, 'top-end'); + break; case 'lock': this.sound('locked'); this.event(_EVENTS.roomLock); @@ -4666,7 +4767,7 @@ class RoomClient { // ROOM LOBBY // #################################################### - roomLobby(data) { + async roomLobby(data) { console.log('LOBBY--->', data); switch (data.lobby_status) { case 'waiting': @@ -4702,7 +4803,7 @@ class RoomClient { } break; case 'accept': - this.joinAllowed(data.room); + await this.joinAllowed(data.room); control.style.display = 'flex'; this.msgPopup('info', 'Your join meeting was be accepted by moderator'); break; @@ -5464,10 +5565,26 @@ class RoomClient { updateRoomModerator(data) { if (!isRulesActive || isPresenter) { - this.socket.emit('updateRoomModerator', data); + const moderator = this.getModeratorData(data); + this.socket.emit('updateRoomModerator', moderator); } } + updateRoomModeratorALL(data) { + if (!isRulesActive || isPresenter) { + const moderator = this.getModeratorData(data); + this.socket.emit('updateRoomModeratorALL', moderator); + } + } + + getModeratorData(data) { + return { + peer_name: this.peer_name, + peer_uuid: this.peer_uuid, + moderator: data, + }; + } + handleUpdateRoomModerator(data) { switch (data.type) { case 'audio_cant_unmute': @@ -5488,6 +5605,11 @@ class RoomClient { } } + handleUpdateRoomModeratorALL(data) { + this._moderator = data; + console.log('Update Room Moderator data all', this._moderator); + } + getModerator() { console.log('Get Moderator', this._moderator); return this._moderator; @@ -5497,7 +5619,7 @@ class RoomClient { // UPDATE PEER INFO // #################################################### - updatePeerInfo(peer_name, peer_id, type, status, emit = true) { + updatePeerInfo(peer_name, peer_id, type, status, emit = true, presenter = false) { if (emit) { switch (type) { case 'audio': @@ -5524,23 +5646,23 @@ class RoomClient { default: break; } - let data = { + const data = { peer_name: peer_name, peer_id: peer_id, type: type, status: status, + broadcast: true, }; this.socket.emit('updatePeerInfo', data); } else { + const canUpdateMediaStatus = !isBroadcastingEnabled || (isBroadcastingEnabled && presenter); switch (type) { case 'audio': - this.setIsAudio(peer_id, status); + if (canUpdateMediaStatus) this.setPeerAudio(peer_id, status); break; case 'video': - this.setIsVideo(status); break; case 'screen': - this.setIsScreen(status); break; case 'hand': let peer_hand = this.getPeerHandBtn(peer_id); diff --git a/public/js/Rules.js b/public/js/Rules.js index 8ab8ee96..aed474c0 100644 --- a/public/js/Rules.js +++ b/public/js/Rules.js @@ -28,6 +28,7 @@ const BUTTONS = { settings: { lockRoomButton: true, // presenter unlockRoomButton: true, // presenter + broadcastingButton: true, // presenter lobbyButton: true, // presenter micOptionsButton: true, // presenter tabModerator: true, // presenter @@ -80,7 +81,7 @@ const BUTTONS = { }; function handleRules(isPresenter) { - console.log('06.1 ----> IsPresenter: ' + isPresenter); + console.log('07.1 ----> IsPresenter: ' + isPresenter); if (!isRulesActive) return; if (!isPresenter) { // ################################## @@ -89,6 +90,7 @@ function handleRules(isPresenter) { BUTTONS.participantsList.saveInfoButton = false; BUTTONS.settings.lockRoomButton = false; BUTTONS.settings.unlockRoomButton = false; + BUTTONS.settings.broadcastingButton = false; BUTTONS.settings.lobbyButton = false; BUTTONS.settings.micOptionsButton = false; BUTTONS.settings.tabModerator = false; @@ -106,6 +108,7 @@ function handleRules(isPresenter) { BUTTONS.participantsList.saveInfoButton = true; BUTTONS.settings.lockRoomButton = !isRoomLocked; BUTTONS.settings.unlockRoomButton = isRoomLocked; + BUTTONS.settings.broadcastingButton = true; BUTTONS.settings.lobbyButton = true; BUTTONS.settings.micOptionsButton = true; BUTTONS.settings.tabModerator = true; @@ -121,6 +124,10 @@ function handleRules(isPresenter) { // Auto detected rules for presenter // ################################## + // Room broadcasting + isBroadcastingEnabled = lsSettings.broadcasting; + switchBroadcasting.checked = isBroadcastingEnabled; + rc.roomAction('broadcasting', true, false); // Room lobby isLobbyEnabled = lsSettings.lobby; switchLobby.checked = isLobbyEnabled; @@ -135,15 +142,20 @@ function handleRules(isPresenter) { switchEveryoneCantUnmute.checked = lsSettings.moderator_audio_cant_unmute; switchEveryoneCantUnhide.checked = lsSettings.moderator_video_cant_unhide; switchEveryoneCantShareScreen.checked = lsSettings.moderator_screen_cant_share; - rc.updateRoomModerator({ type: 'audio_start_muted', status: switchEveryoneMute.checked }); - rc.updateRoomModerator({ type: 'video_start_hidden', status: switchEveryoneHidden.checked }); - rc.updateRoomModerator({ type: 'audio_cant_unmute', status: switchEveryoneCantUnmute.checked }); - rc.updateRoomModerator({ type: 'video_cant_unhide', status: switchEveryoneCantUnhide.checked }); - rc.updateRoomModerator({ type: 'screen_cant_share', status: switchEveryoneCantShareScreen.checked }); + // Update moderator settings... + const moderatorData = { + audio_start_muted: switchEveryoneMute.checked, + video_start_hidden: switchEveryoneHidden.checked, + audio_cant_unmute: switchEveryoneCantUnmute.checked, + video_cant_unhide: switchEveryoneCantUnhide.checked, + screen_cant_share: switchEveryoneCantShareScreen.checked, + }; + rc.updateRoomModeratorALL(moderatorData); } // main. settings... BUTTONS.settings.lockRoomButton ? show(lockRoomButton) : hide(lockRoomButton); BUTTONS.settings.unlockRoomButton ? show(unlockRoomButton) : hide(unlockRoomButton); + BUTTONS.settings.broadcastingButton ? show(broadcastingButton) : hide(broadcastingButton); BUTTONS.settings.lobbyButton ? show(lobbyButton) : hide(lobbyButton); !BUTTONS.settings.micOptionsButton && hide(micOptionsButton); !BUTTONS.settings.tabModerator && hide(tabModeratorBtn); @@ -153,3 +165,50 @@ function handleRules(isPresenter) { : elemDisplay('whiteboardLockButton', false, 'flex'); //... } + +function handleRulesBroadcasting() { + console.log('07.2 ----> handleRulesBroadcasting'); + BUTTONS.main.shareButton = false; + BUTTONS.main.hideMeButton = false; + BUTTONS.main.startAudioButton = false; + BUTTONS.main.startVideoButton = false; + BUTTONS.main.startScreenButton = false; + BUTTONS.main.swapCameraButton = false; + BUTTONS.main.raiseHandButton = false; + BUTTONS.main.whiteboardButton = false; + //BUTTONS.main.emojiRoomButton = false, + BUTTONS.main.transcriptionButton = false; + BUTTONS.main.settingsButton = false; + BUTTONS.participantsList.saveInfoButton = false; + BUTTONS.settings.lockRoomButton = false; + BUTTONS.settings.unlockRoomButton = false; + BUTTONS.settings.lobbyButton = false; + BUTTONS.videoOff.muteAudioButton = false; + BUTTONS.videoOff.ejectButton = false; + BUTTONS.consumerVideo.sendMessageButton = false; + BUTTONS.consumerVideo.sendFileButton = false; + BUTTONS.consumerVideo.sendVideoButton = false; + BUTTONS.consumerVideo.ejectButton = false; + BUTTONS.consumerVideo.muteAudioButton = false; + BUTTONS.consumerVideo.muteVideoButton = false; + BUTTONS.whiteboard.whiteboardLockButton = false; + //... + elemDisplay('shareButton', false); + elemDisplay('hideMeButton', false); + elemDisplay('startAudioButton', false); + elemDisplay('stopAudioButton', false); + elemDisplay('startVideoButton', false); + elemDisplay('stopVideoButton', false); + elemDisplay('startScreenButton', false); + elemDisplay('stopScreenButton', false); + elemDisplay('swapCameraButton', false); + elemDisplay('raiseHandButton', false); + elemDisplay('whiteboardButton', false); + //elemDisplay('emojiRoomButton', false); + elemDisplay('transcriptionButton', false); + elemDisplay('lockRoomButton', false); + elemDisplay('unlockRoomButton', false); + elemDisplay('lobbyButton', false); + elemDisplay('settingsButton', false); + //... +} diff --git a/public/views/Room.html b/public/views/Room.html index 2bf2829e..936eb22b 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -154,7 +154,7 @@ access to use this app. - + @@ -230,6 +230,19 @@ access to use this app.
+ + + +