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 0000000..cb221c2
Binary files /dev/null and b/public/images/hide.png differ
diff --git a/public/images/mute.png b/public/images/mute.png
new file mode 100644
index 0000000..29cb77a
Binary files /dev/null and b/public/images/mute.png differ
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 += `
- | 👤 ${peer_name} (me) |
- |
-
`)
- : (table += `
- | 👤 ${peer_name} |
- |
-
`);
- }
- table += `
`;
+ 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 += `
+
+ | ${peer_name} (me) |
+ |
+ |
+ |
+ |
+
+ `;
+ } else {
+ table += `
+
+ | ${peer_name} |
+ |
+ |
+ |
+ |
+
+ `;
+ }
+ }
+ table += `
`;
+ 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 0000000..c2b1352
Binary files /dev/null and b/public/sounds/raiseHand.wav differ
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) => {