diff --git a/app/src/Room.js b/app/src/Room.js index 9052170..35c79dd 100644 --- a/app/src/Room.js +++ b/app/src/Room.js @@ -9,6 +9,7 @@ module.exports = class Room { this.id = room_id; this.worker = worker; this.router = null; + this.audioLevelObserver = null; this.io = io; this._isLocked = false; this._roomPassword = null; @@ -29,10 +30,49 @@ module.exports = class Room { .then( function (router) { this.router = router; + this.startAudioLevelObservation(router); }.bind(this), ); } + // #################################################### + // PRODUCER AUDIO LEVEL OBSERVER + // #################################################### + + async startAudioLevelObservation(router) { + log.debug('Start audioLevelObserver for signaling active speaker...'); + + this.audioLevelObserver = await router.createAudioLevelObserver({ + maxEntries: 1, + threshold: -80, + interval: 800, + }); + + this.audioLevelObserver.on('volumes', (volumes) => { + const volume = volumes[0].volume; + let audioVolume = Math.round(Math.pow(10, volume / 85) * 10); // 1-10 + if (audioVolume > 2) { + //console.log('PEERS', this.peers); + this.peers.forEach((peer) => { + peer.producers.forEach((producer) => { + if (producer.kind == 'audio') { + let data = { peer_id: peer.id, audioVolume: audioVolume }; + //log.debug('audioLevelObserver', data); + this.io.emit('audioVolume', data); + } + }); + }); + } + }); + this.audioLevelObserver.on('silence', () => { + log.debug('audioLevelObserver', { volume: 'silence' }); + }); + } + + addProducerToAudioLevelObserver(producer) { + this.audioLevelObserver.addProducer(producer); + } + getRtpCapabilities() { return this.router.rtpCapabilities; } diff --git a/app/src/Server.js b/app/src/Server.js index fa0900a..3e30779 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -479,9 +479,15 @@ io.on('connection', (socket) => { log.debug('Produce', { kind: kind, peer_name: peer_name, + peer_id: socket.id, producer_id: producer_id, }); + // add & monitor producer audio level + if (kind === 'audio') { + roomList.get(socket.room_id).addProducerToAudioLevelObserver({ producerId: producer_id }); + } + // peer_info audio Or video ON let data = { peer_name: peer_name, diff --git a/public/css/Room.css b/public/css/Room.css index 15b8cf3..864fd3a 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -735,6 +735,31 @@ progress { padding: 0; } +/*-------------------------------------------------------------- +# Speech bar +--------------------------------------------------------------*/ + +.speechbar { + position: absolute; + top: 0; + bottom: 0; + right: 2px; + width: 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + pointer-events: none; +} + +.bar { + width: 6px; + border-radius: 6px; + background: rgba(#19bb5c, 0.65); + transition-property: height background-color; + transition-duration: 0.25s; +} + /*-------------------------------------------------------------- # Pulse class effect --------------------------------------------------------------*/ diff --git a/public/css/VideoGrid.css b/public/css/VideoGrid.css index 88d69a7..05ce1a9 100644 --- a/public/css/VideoGrid.css +++ b/public/css/VideoGrid.css @@ -17,8 +17,7 @@ right: 0px; } -#videoMediaContainer div { - /* Camera */ +.Camera { position: relative; vertical-align: middle; align-self: center; diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index c246a5b..1272311 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -133,6 +133,9 @@ class RoomClient { this.myVideoEl = null; this.debug = false; + this.videoProducerId = null; + this.audioProducerId = null; + this.consumers = new Map(); this.producers = new Map(); this.producerLabel = new Map(); @@ -513,6 +516,13 @@ class RoomClient { }.bind(this), ); + this.socket.on( + 'audioVolume', + function (data) { + this.handleAudioVolume(data); + }.bind(this), + ); + this.socket.on( 'disconnect', function () { @@ -599,7 +609,7 @@ class RoomClient { } producer = await this.producerTransport.produce(params); - console.log('Producer id', producer.id); + console.log('PRODUCER', producer); this.producers.set(producer.id, producer); @@ -607,8 +617,10 @@ class RoomClient { if (!audio) { this.localVideoStream = stream; elem = await this.handleProducer(producer.id, type, stream); + this.videoProducerId = producer.id; } else { this.localAudioStream = stream; + this.audioProducerId = producer.id; } producer.on('trackended', () => { @@ -749,7 +761,7 @@ class RoomClient { } async handleProducer(id, type, stream) { - let elem, d, p, i, b, fs; + let elem, d, p, i, b, fs, pm, pb; this.removeVideoOff(this.peer_id); d = document.createElement('div'); d.className = 'Camera'; @@ -773,11 +785,20 @@ class RoomClient { fs = document.createElement('button'); fs.id = id + '__fullScreen'; fs.className = html.fullScreen; + pm = document.createElement('div'); + pb = document.createElement('div'); + pm.setAttribute('id', this.peer_id + '_pitchMeter'); + pb.setAttribute('id', this.peer_id + '_pitchBar'); + pm.className = 'speechbar'; + pb.className = 'bar'; + pb.style.height = '1%'; + pm.appendChild(pb); d.appendChild(elem); d.appendChild(i); d.appendChild(p); d.appendChild(b); d.appendChild(fs); + d.appendChild(pm); this.videoMediaContainer.appendChild(d); this.attachMediaStream(elem, stream, type, 'Producer'); this.myVideoEl = elem; @@ -950,7 +971,7 @@ class RoomClient { } handleConsumer(id, type, stream, peer_name, peer_info) { - let elem, d, p, i, b, fs; + let elem, d, p, i, b, fs, pb, pm; switch (type) { case mediaType.video: this.removeVideoOff(peer_info.peer_id); @@ -976,11 +997,20 @@ class RoomClient { fs = document.createElement('button'); fs.id = id + '__fullScreen'; fs.className = html.fullScreen; + pm = document.createElement('div'); + pb = document.createElement('div'); + pm.setAttribute('id', peer_info.peer_id + '__pitchMeter'); + pb.setAttribute('id', peer_info.peer_id + '__pitchBar'); + pm.className = 'speechbar'; + pb.className = 'bar'; + pb.style.height = '1%'; + pm.appendChild(pb); d.appendChild(elem); d.appendChild(p); d.appendChild(i); d.appendChild(b); d.appendChild(fs); + d.appendChild(pm); this.videoMediaContainer.appendChild(d); this.attachMediaStream(elem, stream, type, 'Consumer'); this.handleFS(elem.id, fs.id); @@ -1025,7 +1055,7 @@ class RoomClient { // #################################################### async setVideoOff(peer_info, remotePeer = false) { - let d, i, h, b, p; + let d, i, h, b, p, pm, pb; let peer_id = peer_info.peer_id; let peer_name = peer_info.peer_name; let peer_audio = peer_info.peer_audio; @@ -1046,10 +1076,19 @@ class RoomClient { h = document.createElement('i'); h.id = peer_info.peer_id + '__hand'; h.className = html.userHand; + pm = document.createElement('div'); + pb = document.createElement('div'); + pm.setAttribute('id', peer_id + '__pitchMeter'); + pb.setAttribute('id', peer_id + '__pitchBar'); + pm.className = 'speechbar'; + pb.className = 'bar'; + pb.style.height = '1%'; + pm.appendChild(pb); d.appendChild(i); d.appendChild(p); d.appendChild(b); d.appendChild(h); + d.appendChild(pm); this.videoMediaContainer.appendChild(d); this.setVideoAvatarImgName(i.id, peer_name); this.getId(i.id).style.display = 'block'; @@ -2272,6 +2311,31 @@ class RoomClient { }); } + // #################################################### + // HANDLE AUDIO VOLUME + // #################################################### + + handleAudioVolume(data) { + let peerId = data.peer_id; + let pbProducer = this.getId(peerId + '_pitchBar'); + let pbConsumer = this.getId(peerId + '__pitchBar'); + let audioVolume = data.audioVolume * 10; //10-100 + // console.log('Active speaker', { peer_id: peerId, audioVolume: audioVolume }); + if (audioVolume > 40) { + if (pbProducer) pbProducer.style.backgroundColor = 'orange'; + if (pbConsumer) pbConsumer.style.backgroundColor = 'orange'; + } else { + if (pbProducer) pbProducer.style.backgroundColor = '#19bb5c'; + if (pbConsumer) pbConsumer.style.backgroundColor = '#19bb5c'; + } + if (pbProducer) pbProducer.style.height = audioVolume + '%'; + if (pbConsumer) pbConsumer.style.height = audioVolume + '%'; + setTimeout(function () { + if (pbProducer) pbProducer.style.height = '0%'; + if (pbConsumer) pbConsumer.style.height = '0%'; + }, 2000); + } + // #################################################### // PEER ACTION // ####################################################