From a265f669b05f4c1372554a668e4a3c99e7b69f8b Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sat, 18 Sep 2021 17:59:32 +0200 Subject: [PATCH] [mirotalksfu] - added raise hand & turn-off audio-video --- public/Room.html | 2 + public/css/Room.css | 10 -- public/images/hide.png | Bin 0 -> 1844 bytes public/images/mute.png | Bin 0 -> 3686 bytes public/js/Room.js | 105 +++++++++++++++---- public/js/RoomClient.js | 197 ++++++++++++++++++++++++++++-------- public/sounds/raiseHand.wav | Bin 0 -> 62770 bytes src/Peer.js | 21 ++++ src/Server.js | 33 +++++- 9 files changed, 287 insertions(+), 81 deletions(-) create mode 100644 public/images/hide.png create mode 100644 public/images/mute.png create mode 100644 public/sounds/raiseHand.wav diff --git a/public/Room.html b/public/Room.html index fefa05b..6ab111f 100644 --- a/public/Room.html +++ b/public/Room.html @@ -116,6 +116,8 @@ access to use this app. + + diff --git a/public/css/Room.css b/public/css/Room.css index 48ed050..325c501 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -532,16 +532,6 @@ button:hover { overflow: auto; } -#roomParticipants button { - background: transparent; - color: red; - border-radius: 5px; -} -#roomParticipants button:hover { - color: white; - border-radius: 5px; -} - #roomParticipants table { border-collapse: collapse; width: 100%; diff --git a/public/images/hide.png b/public/images/hide.png new file mode 100644 index 0000000000000000000000000000000000000000..cb221c2798aa509a76e7c6196fd6bc62001d49ee GIT binary patch literal 1844 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 literal 0 HcmV?d00001 diff --git a/public/js/Room.js b/public/js/Room.js index 26e7ef5..9af9558 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -4,6 +4,16 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h const RoomURL = window.location.href; +const _PEER = { + audioOn: '', + audioOff: '', + videoOn: '', + videoOff: '', + raiseHand: '', + lowerHand: '', + ejectPeer: '', +}; + const swalBackground = 'linear-gradient(to left, #1f1e1e, #000000)'; const swalImageUrl = '../images/pricing-illustration.svg'; @@ -174,6 +184,7 @@ function getPeerInfo() { peer_name: peer_name, peer_audio: isAudioOn, peer_video: isVideoOn, + peer_hand: false, }; } @@ -389,6 +400,7 @@ function roomIsReady() { show(fullScreenButton); } show(settingsButton); + show(raiseHandButton); if (isAudioAllowed) show(startAudioButton); if (isVideoAllowed) show(startVideoButton); show(participantsButton); @@ -534,6 +546,12 @@ function handleButtons() { swapCameraButton.onclick = () => { rc.closeThenProduce(RoomClient.mediaType.video, null, true); }; + raiseHandButton.onclick = () => { + rc.updatePeerInfo(peer_name, rc.peer_id, 'hand', true); + }; + lowerHandButton.onclick = () => { + rc.updatePeerInfo(peer_name, rc.peer_id, 'hand', false); + }; startAudioButton.onclick = () => { rc.produce(RoomClient.mediaType.audio, microphoneSelect.value); // rc.resumeProducer(RoomClient.mediaType.audio); @@ -633,6 +651,17 @@ function handleRoomClientEvents() { show(startRecButton); stopRecordingTimer(); }); + rc.on(RoomClient.EVENTS.raiseHand, () => { + console.log('Room Client raise hand'); + hide(raiseHandButton); + show(lowerHandButton); + setColor(lowerHandButton, 'green'); + }); + rc.on(RoomClient.EVENTS.lowerHand, () => { + console.log('Room Client lower hand'); + hide(lowerHandButton); + show(raiseHandButton); + }); rc.on(RoomClient.EVENTS.startAudio, () => { console.log('Room Client start audio'); hide(startAudioButton); @@ -720,13 +749,13 @@ function closeNav() { // SHOW LOG // #################################################### -function userLog(icon, message, position) { +function userLog(icon, message, position, timer = 3000) { const Toast = Swal.mixin({ background: swalBackground, toast: true, position: position, showConfirmButton: false, - timer: 3000, + timer: timer, }); Toast.fire({ icon: icon, @@ -755,26 +784,7 @@ async function sound(name) { async function getRoomParticipants() { let room_info = await rc.getRoomInfo(); let peers = new Map(JSON.parse(room_info.peers)); - let table = `
- - - - `; - for (let peer of Array.from(peers.keys())) { - let peer_info = peers.get(peer).peer_info; - let peer_name = peer_info.peer_name; - let peer_id = peer_info.peer_id; - rc.peer_id === peer_id - ? (table += ` - - - `) - : (table += ` - - - `); - } - table += `
👤 ${peer_name} (me)
👤 ${peer_name}
`; + let table = await getParticipantsTable(peers); sound('open'); @@ -783,15 +793,66 @@ async function getRoomParticipants() { position: 'center', title: `Participants ${peers.size}`, html: table, + showCancelButton: true, + confirmButtonText: `Refresh`, + cancelButtonText: `Close`, showClass: { popup: 'animate__animated animate__fadeInDown', }, hideClass: { popup: 'animate__animated animate__fadeOutUp', }, + }).then((result) => { + if (result.isConfirmed) { + getRoomParticipants(); + } }); } +async function getParticipantsTable(peers) { + let table = `
+ + + + + + + + `; + for (let peer of Array.from(peers.keys())) { + let peer_info = peers.get(peer).peer_info; + let peer_name = '👤 ' + peer_info.peer_name; + let peer_audio = peer_info.peer_audio ? _PEER.audioOn : _PEER.audioOff; + let peer_video = peer_info.peer_video ? _PEER.videoOn : _PEER.videoOff; + let peer_hand = peer_info.peer_hand ? _PEER.raiseHand : _PEER.lowerHand; + let peer_eject = _PEER.ejectPeer; + let peer_id = peer_info.peer_id; + if (rc.peer_id === peer_id) { + table += ` + + + + + + + + `; + } else { + table += ` + + + + + + + + `; + } + } + table += `
${peer_name} (me)
${peer_name}
`; + return table; +} + // #################################################### // ABOUT // #################################################### diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index fa148bc..5f1200f 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -12,6 +12,8 @@ const image = { poster: '../images/loader.gif', delete: '../images/delete.png', locked: '../images/locked.png', + mute: '../images/mute.png', + hide: '../images/hide.png', }; const mediaType = { @@ -28,6 +30,8 @@ const _EVENTS = { pauseRec: 'pauseRec', resumeRec: 'resumeRec', stopRec: 'stopRec', + raiseHand: 'raiseHand', + lowerHand: 'lowerHand', startVideo: 'startVideo', pauseVideo: 'pauseVideo', resumeVideo: 'resumeVideo', @@ -381,6 +385,14 @@ class RoomClient { }.bind(this), ); + 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); + }.bind(this), + ); + this.socket.on( 'disconnect', function () { @@ -606,7 +618,7 @@ class RoomClient { let elem, d, p; d = document.createElement('div'); d.className = 'Camera'; - d.id = id + '_d'; + d.id = id + '__d'; elem = document.createElement('video'); elem.setAttribute('id', id); elem.setAttribute('playsinline', true); @@ -614,7 +626,7 @@ class RoomClient { elem.poster = image.poster; this.isMobileDevice || type === mediaType.screen ? (elem.className = '') : (elem.className = 'mirror'); p = document.createElement('p'); - p.id = id + '_name'; + p.id = id + '__name'; p.innerHTML = '👤 ' + this.peer_name + ' (me)'; d.appendChild(elem); d.appendChild(p); @@ -684,11 +696,16 @@ class RoomClient { } let producer_id = this.producerLabel.get(type); - console.log('Close producer', producer_id); - this.socket.emit('producerClosed', { - producer_id, - }); + let data = { + peer_name: this.peer_name, + producer_id: producer_id, + type: type, + status: false, + }; + console.log('Close producer', data); + + this.socket.emit('producerClosed', data); this.producers.get(producer_id).close(); this.producers.delete(producer_id); @@ -696,7 +713,7 @@ class RoomClient { if (type !== mediaType.audio) { let elem = this.getId(producer_id); - let d = this.getId(producer_id + '_d'); + let d = this.getId(producer_id + '__d'); elem.srcObject.getTracks().forEach(function (track) { track.stop(); }); @@ -786,7 +803,7 @@ class RoomClient { case mediaType.video: d = document.createElement('div'); d.className = 'Camera'; - d.id = id + '_d'; + d.id = id + '__d'; elem = document.createElement('video'); elem.setAttribute('id', id); elem.setAttribute('playsinline', true); @@ -794,7 +811,7 @@ class RoomClient { elem.className = ''; elem.poster = image.poster; p = document.createElement('p'); - p.id = id + '_name'; + p.id = id + '__name'; p.innerHTML = '👤 ' + peer_name; d.appendChild(elem); d.appendChild(p); @@ -819,7 +836,7 @@ class RoomClient { removeConsumer(consumer_id) { let elem = this.getId(consumer_id); - let d = this.getId(consumer_id + '_d'); + let d = this.getId(consumer_id + '__d'); elem.srcObject.getTracks().forEach(function (track) { track.stop(); @@ -980,13 +997,13 @@ class RoomClient { } } - userLog(icon, message, position) { + userLog(icon, message, position, timer = 5000) { const Toast = Swal.mixin({ background: swalBackground, toast: true, position: position, showConfirmButton: false, - timer: 5000, + timer: timer, }); Toast.fire({ icon: icon, @@ -1436,45 +1453,136 @@ class RoomClient { // PEER ACTION // #################################################### - peerAction(from_peer_name, peer_id, action, emit = true) { - switch (action) { - case 'eject': - let peer = this.getId(peer_id); - if (peer) peer.parentNode.removeChild(peer); - if (peer_id === this.peer_id) { - this.sound(action); - let timerInterval; - Swal.fire({ - allowOutsideClick: false, - background: swalBackground, - title: from_peer_name, - html: 'Will eject you from the room after milliseconds.', - timer: 5000, - timerProgressBar: true, - didOpen: () => { - Swal.showLoading(); - const b = Swal.getHtmlContainer().querySelector('b'); - timerInterval = setInterval(() => { - b.textContent = Swal.getTimerLeft(); - }, 100); - }, - willClose: () => { - clearInterval(timerInterval); - }, - }).then(() => { - this.exit(); - }); - } - break; - // ... - } + peerAction(from_peer_name, id, action, emit = true, broadcast = false) { + let peer_id = id; + if (emit) { + const words = peer_id.split('__'); + peer_id = words[0]; + + switch (action) { + case 'eject': + let peer = this.getId(peer_id); + if (peer) peer.parentNode.removeChild(peer); + break; + case 'mute': + let peerAudioButton = this.getId(peer_id + '__audio'); + if (peerAudioButton) peerAudioButton.innerHTML = _PEER.audioOff; + break; + case 'hide': + let peerVideoButton = this.getId(peer_id + '__video'); + if (peerVideoButton) peerVideoButton.innerHTML = _PEER.videoOff; + } + let data = { from_peer_name: this.peer_name, peer_id: peer_id, action: action, + broadcast: broadcast, }; this.socket.emit('peerAction', data); + } else { + switch (action) { + case 'eject': + if (peer_id === this.peer_id) { + this.sound(action); + let timerInterval; + Swal.fire({ + allowOutsideClick: false, + background: swalBackground, + title: from_peer_name, + html: 'Will eject you from the room after milliseconds.', + timer: 5000, + timerProgressBar: true, + didOpen: () => { + Swal.showLoading(); + const b = Swal.getHtmlContainer().querySelector('b'); + timerInterval = setInterval(() => { + b.textContent = Swal.getTimerLeft(); + }, 100); + }, + willClose: () => { + clearInterval(timerInterval); + }, + }).then(() => { + this.exit(); + }); + } + break; + case 'mute': + if (peer_id === this.peer_id) { + this.closeProducer(mediaType.audio); + this.userLog( + 'warning', + from_peer_name + ' ' + _PEER.audioOff + ' has closed yours audio', + 'top-end', + 10000, + ); + } + break; + case 'hide': + if (peer_id === this.peer_id) { + this.closeProducer(mediaType.video); + this.userLog( + 'warning', + from_peer_name + ' ' + _PEER.videoOff + ' has closed yours video', + 'top-end', + 10000, + ); + } + break; + // ... + } + } + } + + // #################################################### + // UPDATE PEER INFO + // #################################################### + + updatePeerInfo(peer_name, peer_id, type, status, emit = true) { + if (emit) { + switch (type) { + case 'audio': + this.peer_info.peer_audio = status; + break; + case 'video': + this.peer_info.peer_video = status; + break; + case 'hand': + this.peer_info.peer_hand = status; + if (status) { + this.event(_EVENTS.raiseHand); + this.sound('raiseHand'); + } else { + this.event(_EVENTS.lowerHand); + } + break; + } + let data = { + peer_name: peer_name, + peer_id: peer_id, + type: type, + status: status, + }; + this.socket.emit('updatePeerInfo', data); + } else { + switch (type) { + case 'audio': + break; + case 'video': + break; + case 'hand': + if (status) + this.userLog( + 'warning', + peer_name + ' ' + _PEER.raiseHand + ' has raised the hand', + 'top-end', + 10000, + ); + this.sound('raiseHand'); + break; + } } } @@ -1488,6 +1596,7 @@ class RoomClient { 'peer_name', 'peer_audio', 'peer_video', + 'peer_hand', 'is_mobile_device', 'os_name', 'os_version', diff --git a/public/sounds/raiseHand.wav b/public/sounds/raiseHand.wav new file mode 100644 index 0000000000000000000000000000000000000000..c2b1352a3585b239cfef68be5f2057572ae424a9 GIT binary patch literal 62770 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 VA>u_^;u^+_`W5BnK97rY{||X#oK64$ literal 0 HcmV?d00001 diff --git a/src/Peer.js b/src/Peer.js index 5e24aeb..8aeea12 100644 --- a/src/Peer.js +++ b/src/Peer.js @@ -10,11 +10,32 @@ module.exports = class Peer { this.peer_name = data.peer_info.peer_name; this.peer_audio = data.peer_info.peer_audio; this.peer_video = data.peer_info.peer_video; + this.peer_hand = data.peer_info.peer_hand; this.transports = new Map(); this.consumers = new Map(); this.producers = new Map(); } + // #################################################### + // UPDATE PEER INFO + // #################################################### + + updatePeerInfo(data) { + switch (data.type) { + case 'audio': + case 'audioType': + this.peer_info.peer_audio = data.status; + break; + case 'video': + case 'videoType': + this.peer_info.peer_video = data.status; + break; + case 'hand': + this.peer_info.peer_hand = data.status; + break; + } + } + // #################################################### // TRANSPORT // #################################################### diff --git a/src/Server.js b/src/Server.js index 3c6b588..1141d12 100644 --- a/src/Server.js +++ b/src/Server.js @@ -219,7 +219,18 @@ io.on('connection', (socket) => { socket.on('peerAction', (data) => { log.debug('Peer action:', data); - roomList.get(socket.room_id).sendTo(data.peer_id, 'peerAction', data); + if (data.broadcast) { + roomList.get(socket.room_id).broadCast(data.peer_id, 'peerAction', data); + } else { + roomList.get(socket.room_id).sendTo(data.peer_id, 'peerAction', data); + } + }); + + socket.on('updatePeerInfo', (data) => { + log.debug('Peer info update:', data); + // peer_info hand raise Or lower + roomList.get(socket.room_id).getPeers().get(socket.id).updatePeerInfo(data); + roomList.get(socket.room_id).broadCast(socket.id, 'updatePeerInfo', data); }); socket.on('join', (data, cb) => { @@ -290,16 +301,26 @@ io.on('connection', (socket) => { return callback({ error: 'Room not found' }); } + let peer_name = getPeerName(false); + let producer_id = await roomList .get(socket.room_id) .produce(socket.id, producerTransportId, rtpParameters, kind); log.debug('Produce', { kind: kind, - peer_name: getPeerName(false), + peer_name: peer_name, producer_id: producer_id, }); + // peer_info audio Or video ON + let data = { + peer_name: peer_name, + type: kind, + status: true, + }; + roomList.get(socket.room_id).getPeers().get(socket.id).updatePeerInfo(data); + callback({ producer_id, }); @@ -323,10 +344,12 @@ io.on('connection', (socket) => { callback(params); }); - socket.on('producerClosed', ({ producer_id }) => { - log.debug('Producer close', getPeerName()); + socket.on('producerClosed', (data) => { + log.debug('Producer close', data); - roomList.get(socket.room_id).closeProducer(socket.id, producer_id); + // peer_info audio Or video OFF + roomList.get(socket.room_id).getPeers().get(socket.id).updatePeerInfo(data); + roomList.get(socket.room_id).closeProducer(socket.id, data.producer_id); }); socket.on('resume', async (_, callback) => {