From f5bb382ee3c86c1f6ac2825bb8ca8d8d1d6c9f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 25 Oct 2018 16:09:40 +0200 Subject: [PATCH] Reworked lastN --- app/lib/LastN.js | 123 ++++++++++++++++++++++++++ app/lib/RoomClient.js | 177 +++++++++++++++++++------------------- app/lib/cookiesManager.js | 10 +++ 3 files changed, 220 insertions(+), 90 deletions(-) create mode 100644 app/lib/LastN.js diff --git a/app/lib/LastN.js b/app/lib/LastN.js new file mode 100644 index 0000000..7534d16 --- /dev/null +++ b/app/lib/LastN.js @@ -0,0 +1,123 @@ +import { EventEmitter } from 'events'; +import Logger from './Logger'; + +const logger = new Logger('LastN'); + +export default class LastN extends EventEmitter +{ + constructor(lastNCount, room) + { + super(); + + this._room = room; + this._lastNCount = lastNCount; + this._peerList = []; + this._currentLastN = []; + this._started = false; + } + + start() + { + const peers = this._room.peers; + + for (const peer of peers) + { + this._handlePeer(peer); + } + + this._handleRoom(); + + this._started = true; + this._lastNUpdated(); + } + + _handleRoom() + { + this._room.on('newpeer', (peer) => + { + logger.debug( + 'lastN room "newpeer" event [name:"%s", peer:%o]', peer.name, peer); + this._handlePeer(peer); + }); + } + + addSpeakerList(speakerList) + { + this._peerList = [ ...new Set([ ...speakerList, ...this._peerList ]) ]; + + if (this._started) + this._lastNUpdated(); + } + + _handlePeer(peer) + { + logger.debug('_lastN _handlePeer() [peerName:"%s"]', peer.name); + + if (this._peerList.indexOf(peer.name) === -1) // We don't have this peer in the list + { + peer.on('close', () => + { + const index = this._peerList.indexOf(peer.name); + + if (index > -1) // We have this peer in the list, remove + { + this._peerList.splice(index, 1); + + this._lastNUpdated(); + } + }); + + logger.debug('_handlePeer() | adding peer [peerName:"%s"]', peer.name); + + this._peerList.push(peer.name); + + this._lastNUpdated(); + } + } + + handleActiveSpeaker(peerName) + { + logger.debug('handleActiveSpeaker() [peerName:"%s"]', peerName); + + const index = this._peerList.indexOf(peerName); + + if (index > -1) + { + this._peerList.splice(index, 1); + this._peerList = [ peerName ].concat(this._peerList); + + this._lastNUpdated(); + } + } + + _lastNUpdated() + { + if ( + !this._arraysEqual( + this._currentLastN, this._peerList.slice(0, this._lastNCount) + ) + ) + { + logger.debug('_lastNUpdated() | lastN is updated, emitting'); + + this._currentLastN = this._peerList.slice(0, this._lastNCount); + this.emit('lastn-updated', this._currentLastN); + } + else + logger.debug('_lastNUpdated() | lastN not updated'); + } + + _arraysEqual(arr1, arr2) + { + if (arr1.length !== arr2.length) + return false; + + for (let i = arr1.length; i--;) + { + if (arr1[i] !== arr2[i]) + return false; + } + + return true; + } +} diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index 380d75c..c8b857b 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -3,6 +3,7 @@ import * as mediasoupClient from 'mediasoup-client'; import Logger from './Logger'; import hark from 'hark'; import ScreenShare from './ScreenShare'; +import LastN from './LastN'; import { getSignalingUrl } from './urlFactory'; import * as cookiesManager from './cookiesManager'; import * as requestActions from './redux/requestActions'; @@ -75,7 +76,7 @@ export default class RoomClient this._lastNSpeakers = ROOM_OPTIONS.lastN; // Array of lastN speakers - this._lastN = []; + this._lastN = new LastN(this._lastNSpeakers, this._room); // Transport for sending. this._sendTransport = null; @@ -309,13 +310,12 @@ export default class RoomClient { logger.debug('Got lastN'); + // Remove our self from list const index = lastN.indexOf(this._peerName); lastN.splice(index, 1); - this._lastN = lastN; - - this.updateSpeakers(); + this._lastN.addSpeakerList(lastN); } } catch (error) @@ -340,22 +340,16 @@ export default class RoomClient this._micProducer.resume(); } - // Resumes consumers based on lastN speakers - async updateSpeakers() + // Updated consumers based on lastN + async updateSpeakers(speakers) { logger.debug('updateSpeakers()'); try { - const speakers = this._lastN.slice(0, this._lastNSpeakers); - - this._dispatch(stateActions.setLastN(speakers)); - - speakers.forEach((peerName) => + for (const peer of this._room.peers) { - const peer = this._room.getPeerByName(peerName); - - if (peer) + if (speakers.indexOf(peer.name) > -1) // Resume video for speaker { for (const consumer of peer.consumers) { @@ -364,10 +358,22 @@ export default class RoomClient !consumer.locallyPaused) continue; - consumer.resume(); + await consumer.resume(); } } - }); + else // Pause video for everybody else + { + for (const consumer of peer.consumers) + { + if (consumer.appData.source !== 'webcam' || + !consumer.supported || + consumer.locallyPaused) + continue; + + await consumer.pause(); + } + } + } } catch (error) { @@ -375,41 +381,6 @@ export default class RoomClient } } - handleActiveSpeaker(peerName) - { - logger.debug('handleActiveSpeaker() [peerName:"%s"]', peerName); - - const index = this._lastN.indexOf(peerName); - - if (index > -1) // We have this speaker in the list, move to front - { - if (index >= this._lastNSpeakers) // We need to remove someone - { - const removePeer = this._lastN[this._lastNSpeakers - 1]; - - this.pausePeerVideo(removePeer); - } - - this._lastN.splice(index, 1); - this._lastN = [ peerName ].concat(this._lastN); - - this.updateSpeakers(); - } - else // We don't have this speaker in the list, should not happen - { - if (this._lastN.length >= this._lastNSpeakers) - { - const removePeer = this._lastN[this._lastNSpeakers - 1]; - - this.pausePeerVideo(removePeer); - } - - this._lastN = [ peerName ].concat(this._lastN); - - this.updateSpeakers(); - } - } - installExtension() { logger.debug('installExtension()'); @@ -575,6 +546,8 @@ export default class RoomClient this._dispatch( stateActions.setProducerTrack(this._micProducer.id, newTrack)); + cookiesManager.setAudioDevice({ audioDeviceId: deviceId }); + await this._updateAudioDevices(); } catch (error) @@ -629,6 +602,8 @@ export default class RoomClient this._dispatch( stateActions.setProducerTrack(this._webcamProducer.id, newTrack)); + cookiesManager.setVideoDevice({ videoDeviceId: deviceId }); + await this._updateWebcams(); } catch (error) @@ -1065,7 +1040,7 @@ export default class RoomClient stateActions.setRoomActiveSpeaker(peerName)); if (peerName && peerName !== this._peerName) - this.handleActiveSpeaker(peerName); + this._lastN.handleActiveSpeaker(peerName); }); this._signalingSocket.on('display-name-changed', (data) => @@ -1193,14 +1168,6 @@ export default class RoomClient logger.debug( 'room "newpeer" event [name:"%s", peer:%o]', peer.name, peer); - const index = this._lastN.indexOf(peer.name); - - if (index === -1) // We don't have this peer in the list, add - { - this._lastN.push(peer.name); - this.updateSpeakers(); - } - this._handlePeer(peer); }); @@ -1262,15 +1229,23 @@ export default class RoomClient this._dispatch(stateActions.removeAllNotifications()); this.getServerHistory(); - + this.notify('You are in the room'); + this._lastN.on('lastn-updated', (lastN) => + { + this._dispatch(stateActions.setLastN(lastN)); + this.updateSpeakers(lastN); + }); + const peers = this._room.peers; for (const peer of peers) { this._handlePeer(peer, { notify: false }); } + + this._lastN.start(); } catch (error) { @@ -1298,9 +1273,27 @@ export default class RoomClient await this._updateAudioDevices(); + const devicesCookie = cookiesManager.getDevices(); + + let audioDeviceId; + + if (devicesCookie) + audioDeviceId = devicesCookie.audioDeviceId; + logger.debug('_setMicProducer() | calling getUserMedia()'); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + let stream; + + if (this._audioDevices.has(audioDeviceId)) + stream = await navigator.mediaDevices.getUserMedia( + { + audio : + { + deviceId : { exact: audioDeviceId } + } + }); + else + stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const track = stream.getAudioTracks()[0]; @@ -1519,16 +1512,35 @@ export default class RoomClient if (!device) throw new Error('no webcam devices'); + const devicesCookie = cookiesManager.getDevices(); + + let videoDeviceId; + + if (devicesCookie) + videoDeviceId = devicesCookie.videoDeviceId; + logger.debug('_setWebcamProducer() | calling getUserMedia()'); - const stream = await navigator.mediaDevices.getUserMedia( - { - video : + let stream; + + if (this._webcams.has(videoDeviceId)) + stream = await navigator.mediaDevices.getUserMedia( { - deviceId : { exact: device.deviceId }, - ...VIDEO_CONSTRAINS - } - }); + video : + { + deviceId : { exact: videoDeviceId }, + ...VIDEO_CONSTRAINS + } + }); + else + stream = await navigator.mediaDevices.getUserMedia( + { + video : + { + deviceId : { exact: device.deviceId }, + ...VIDEO_CONSTRAINS + } + }); const track = stream.getVideoTracks()[0]; @@ -1640,9 +1652,6 @@ export default class RoomClient else if (!this._audioDevices.has(currentAudioDeviceId)) this._audioDevice.device = array[0]; - this._dispatch( - stateActions.setCanChangeWebcam(this._webcams.size >= 2)); - this._dispatch( stateActions.setCanChangeAudioDevice(len >= 2)); if (len >= 1) @@ -1690,9 +1699,6 @@ export default class RoomClient else if (!this._webcams.has(currentWebcamId)) this._webcam.device = array[0]; - this._dispatch( - stateActions.setCanChangeWebcam(this._webcams.size >= 2)); - this._dispatch( stateActions.setCanChangeWebcam(len >= 2)); if (len >= 1) @@ -1752,14 +1758,6 @@ export default class RoomClient this._dispatch(stateActions.removePeer(peer.name)); - const index = this._lastN.indexOf(peer.name); - - if (index > -1) // We have this peer in the list, remove - { - this._lastN.splice(index, 1); - this.updateSpeakers(); - } - if (this._room.joined) { this.notify(`${peer.appData.displayName} left the room`); @@ -1871,14 +1869,13 @@ export default class RoomClient // Receive the consumer (if we can). if (consumer.supported) { - // Pause it if video and we are in audio-only mode. - if (consumer.kind === 'video' && this._getState().me.audioOnly) - consumer.pause('audio-only-mode'); - - const index = this._lastN.indexOf(consumer.peer.name); - - if (consumer.kind === 'video' && ((index >= this._lastNSpeakers) || (index === -1))) + if (consumer.kind === 'video' && + Object.keys(this._room.peers).length > this._lastNSpeakers) + { // Start paused + logger.debug( + 'consumer paused by default'); consumer.pause('not-speaker'); + } consumer.receive(this._recvTransport) .then((track) => diff --git a/app/lib/cookiesManager.js b/app/lib/cookiesManager.js index 22e9e21..18f31cd 100644 --- a/app/lib/cookiesManager.js +++ b/app/lib/cookiesManager.js @@ -22,3 +22,13 @@ export function setDevices({ webcamEnabled }) { jsCookie.set(DEVICES_COOKIE, { webcamEnabled }); } + +export function setAudioDevice({ audioDeviceId }) +{ + jsCookie.set(DEVICES_COOKIE, { audioDeviceId }); +} + +export function setVideoDevice({ videoDeviceId }) +{ + jsCookie.set(DEVICES_COOKIE, { videoDeviceId }); +}