From d446b33695678e441a5761f99e482d139fad3305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 22 Mar 2020 22:41:48 +0100 Subject: [PATCH 001/198] Room now scales up to total server capacity --- server/lib/Peer.js | 12 +++++++ server/lib/Room.js | 79 +++++++++++++++++++++++++++++++++++++--------- server/server.js | 4 +-- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/server/lib/Peer.js b/server/lib/Peer.js index cce62aa..6f886a6 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -30,6 +30,8 @@ class Peer extends EventEmitter this._email = null; + this._routerId = null; + this._rtpCapabilities = null; this._raisedHand = false; @@ -227,6 +229,16 @@ class Peer extends EventEmitter this._email = email; } + get routerId() + { + return this._routerId; + } + + set routerId(routerId) + { + this._routerId = routerId; + } + get rtpCapabilities() { return this._rtpCapabilities; diff --git a/server/lib/Room.js b/server/lib/Room.js index f25f31d..516af2a 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -12,32 +12,38 @@ class Room extends EventEmitter * * @async * - * @param {mediasoup.Worker} mediasoupWorker - The mediasoup Worker in which a new + * @param {mediasoup.Worker} mediasoupWorkers - The mediasoup Worker in which a new * mediasoup Router must be created. * @param {String} roomId - Id of the Room instance. */ - static async create({ mediasoupWorker, roomId }) + static async create({ mediasoupWorkers, roomId }) { logger.info('create() [roomId:"%s"]', roomId); // Router media codecs. const mediaCodecs = config.mediasoup.router.mediaCodecs; - // Create a mediasoup Router. - const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs }); + const mediasoupRouters = []; - // Create a mediasoup AudioLevelObserver. - const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver( + for (const worker of mediasoupWorkers) + { + const router = await worker.createRouter({ mediaCodecs }); + + mediasoupRouters.push(router); + } + + // Create a mediasoup AudioLevelObserver on first router + const audioLevelObserver = await mediasoupRouters[0].createAudioLevelObserver( { maxEntries : 1, threshold : -80, interval : 800 }); - return new Room({ roomId, mediasoupRouter, audioLevelObserver }); + return new Room({ roomId, mediasoupRouters, audioLevelObserver }); } - constructor({ roomId, mediasoupRouter, audioLevelObserver }) + constructor({ roomId, mediasoupRouters, audioLevelObserver }) { logger.info('constructor() [roomId:"%s"]', roomId); @@ -70,8 +76,8 @@ class Room extends EventEmitter this._peers = {}; - // mediasoup Router instance. - this._mediasoupRouter = mediasoupRouter; + // Array of mediasoup Router instances. + this._mediasoupRouters = mediasoupRouters; // mediasoup AudioLevelObserver. this._audioLevelObserver = audioLevelObserver; @@ -108,8 +114,11 @@ class Room extends EventEmitter this._peers = null; - // Close the mediasoup Router. - this._mediasoupRouter.close(); + // Close the mediasoup Routers. + for (const router of this._mediasoupRouters) + { + router.close(); + } // Emit 'close' event. this.emit('close'); @@ -332,6 +341,9 @@ class Room extends EventEmitter this._peers[peer.id] = peer; + // Assign least loaded router + peer.routerId = this._getLeastLoadedRouter(); + this._handlePeer(peer); this._notification(peer.socket, 'roomReady'); } @@ -413,11 +425,14 @@ class Room extends EventEmitter async _handleSocketRequest(peer, request, cb) { + const router = + this._mediasoupRouters.find((peerRouter) => peerRouter.id === peer.routerId); + switch (request.method) { case 'getRouterRtpCapabilities': { - cb(null, this._mediasoupRouter.rtpCapabilities); + cb(null, router.rtpCapabilities); break; } @@ -531,7 +546,7 @@ class Room extends EventEmitter webRtcTransportOptions.enableTcp = true; } - const transport = await this._mediasoupRouter.createWebRtcTransport( + const transport = await router.createWebRtcTransport( webRtcTransportOptions ); @@ -615,6 +630,17 @@ class Room extends EventEmitter const producer = await transport.produce({ kind, rtpParameters, appData }); + for (const destinationRouter of this._mediasoupRouters) + { + if (destinationRouter !== router) + { + await router.pipeToRouter({ + producerId : producer.id, + router : destinationRouter + }); + } + } + // Store the Producer into the Peer data Object. peer.addProducer(producer.id, producer); @@ -1089,6 +1115,9 @@ class Room extends EventEmitter producer.id ); + const router = this._mediasoupRouters.find((producerRouter) => + producerRouter.id === producerPeer.routerId); + // Optimization: // - Create the server-side Consumer. If video, do it paused. // - Tell its Peer about it and wait for its response. @@ -1099,7 +1128,7 @@ class Room extends EventEmitter // NOTE: Don't create the Consumer if the remote Peer cannot consume it. if ( !consumerPeer.rtpCapabilities || - !this._mediasoupRouter.canConsume( + !router.canConsume( { producerId : producer.id, rtpCapabilities : consumerPeer.rtpCapabilities @@ -1292,6 +1321,26 @@ class Room extends EventEmitter socket.emit('notification', { method, data }); } } + + _getLeastLoadedRouter() + { + let load = Infinity; + let id; + + for (const router of this._mediasoupRouters) + { + const routerLoad = + Object.values(this._peers).filter((peer) => peer.routerId === router.id).length; + + if (routerLoad < load) + { + id = router.id; + load = routerLoad; + } + } + + return id; + } } module.exports = Room; diff --git a/server/server.js b/server/server.js index 8faa837..e4f868a 100755 --- a/server/server.js +++ b/server/server.js @@ -570,9 +570,9 @@ async function getOrCreateRoom({ roomId }) { logger.info('creating a new Room [roomId:"%s"]', roomId); - const mediasoupWorker = getMediasoupWorker(); + // const mediasoupWorker = getMediasoupWorker(); - room = await Room.create({ mediasoupWorker, roomId }); + room = await Room.create({ mediasoupWorkers, roomId }); rooms.set(roomId, room); From 698a57cb3eacd2d929a067682fda94c75ef20a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 14:59:25 +0100 Subject: [PATCH 002/198] Scaling up to new router after this many users connect --- server/lib/Room.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/lib/Room.js b/server/lib/Room.js index 516af2a..23f6d53 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -5,6 +5,8 @@ const config = require('../config/config'); const logger = new Logger('Room'); +const ROUTER_SCALE_SIZE = 40; + class Room extends EventEmitter { /** From d20f0c161fe46c7db4cb5c3ac478260a7d3a9ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 01:36:11 +0100 Subject: [PATCH 003/198] Handle client reconnects better --- app/src/RoomClient.js | 29 +++++++++++ server/lib/Room.js | 115 ++++++++++++++++++++++++++++-------------- server/package.json | 4 +- server/server.js | 4 +- 4 files changed, 111 insertions(+), 41 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index e133a30..204643d 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1520,6 +1520,28 @@ export default class RoomClient }) })); + if (this._screenSharingProducer) + this._screenSharingProducer.close(); + + if (this._webcamProducer) + this._webcamProducer.close(); + + if (this._micProducer) + this._micProducer.close(); + + // Close mediasoup Transports. + if (this._sendTransport) + { + this._sendTransport.close(); + this._sendTransport = null; + } + + if (this._recvTransport) + { + this._recvTransport.close(); + this._recvTransport = null; + } + store.dispatch(roomActions.setRoomState('connecting')); }); @@ -1721,6 +1743,13 @@ export default class RoomClient break; } + + case 'roomBack': + { + await this._joinRoom({ joinVideo }); + + break; + } case 'lockRoom': { diff --git a/server/lib/Room.js b/server/lib/Room.js index f0ceb66..b6ee85b 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -2,6 +2,8 @@ const EventEmitter = require('events').EventEmitter; const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); +const { v4: uuidv4 } = require('uuid'); +const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -46,6 +48,8 @@ class Room extends EventEmitter super(); this.setMaxListeners(Infinity); + this._uuid = uuidv4(); + // Room ID. this._roomId = roomId; @@ -120,9 +124,26 @@ class Room extends EventEmitter this.emit('close'); } - handlePeer(peer) + handlePeer({ peer, token }) { - logger.info('handlePeer() [peer:"%s", roles:"%s"]', peer.id, peer.roles); + logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); + + let verifiedPeer = false; + + if (token) + { + try + { + const decoded = jwt.verify(token, this._uuid); + + if (decoded.id === peer.id) + verifiedPeer = true; + } + catch (err) + { + logger.warn('handlePeer() | invalid token'); + } + } // Allow reconnections, remove old peer if (this._peers[peer.id]) @@ -134,8 +155,11 @@ class Room extends EventEmitter this._peers[peer.id].close(); } + // Returning user + if (verifiedPeer) + this._peerJoining(peer, true); // Always let ADMIN in, even if locked - if (peer.roles.includes(userRoles.ADMIN)) + else if (peer.roles.includes(userRoles.ADMIN)) this._peerJoining(peer); else if (this._locked) this._parkPeer(peer); @@ -332,7 +356,7 @@ class Room extends EventEmitter } } - async _peerJoining(peer) + async _peerJoining(peer, returning = false) { peer.socket.join(this._roomId); @@ -343,45 +367,58 @@ class Room extends EventEmitter this._handlePeer(peer); - let turnServers; - - if ('turnAPIURI' in config) + if (returning) { - try - { - const { data } = await axios.get( - config.turnAPIURI, - { - params : { - 'uri_schema' : 'turn', - 'transport' : 'tcp', - 'ip_ver' : 'ipv4', - 'servercount' : '2', - 'api_key' : config.turnAPIKey, - 'ip' : peer.socket.request.connection.remoteAddress - } - }); - - turnServers = [ { - urls : data.uris, - username : data.username, - credential : data.password - } ]; - } - catch (error) - { - if ('backupTurnServers' in config) - turnServers = config.backupTurnServers; - - logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); - } + this._notification(peer.socket, 'roomBack'); } - else if ('backupTurnServers' in config) + else { - turnServers = config.backupTurnServers; - } + const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); - this._notification(peer.socket, 'roomReady', { turnServers }); + peer.socket.handshake.session.token = token; + + peer.socket.handshake.session.save(); + + let turnServers; + + if ('turnAPIURI' in config) + { + try + { + const { data } = await axios.get( + config.turnAPIURI, + { + params : { + 'uri_schema' : 'turn', + 'transport' : 'tcp', + 'ip_ver' : 'ipv4', + 'servercount' : '2', + 'api_key' : config.turnAPIKey, + 'ip' : peer.socket.request.connection.remoteAddress + } + }); + + turnServers = [ { + urls : data.uris, + username : data.username, + credential : data.password + } ]; + } + catch (error) + { + if ('backupTurnServers' in config) + turnServers = config.backupTurnServers; + + logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); + } + } + else if ('backupTurnServers' in config) + { + turnServers = config.backupTurnServers; + } + + this._notification(peer.socket, 'roomReady', { turnServers }); + } } _handlePeer(peer) diff --git a/server/package.json b/server/package.json index b05676d..ae1f3b9 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", + "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", @@ -32,6 +33,7 @@ "pidusage": "^2.0.17", "redis": "^2.8.0", "socket.io": "^2.3.0", - "spdy": "^4.0.1" + "spdy": "^4.0.1", + "uuid": "^7.0.2" } } diff --git a/server/server.js b/server/server.js index ebf951e..2e50a8c 100755 --- a/server/server.js +++ b/server/server.js @@ -467,6 +467,8 @@ async function runWebSocketServer() const room = await getOrCreateRoom({ roomId }); const peer = new Peer({ id: peerId, roomId, socket }); + const { token } = socket.handshake.session; + peers.set(peerId, peer); peer.on('close', () => peers.delete(peerId)); @@ -495,7 +497,7 @@ async function runWebSocketServer() } } - room.handlePeer(peer); + room.handlePeer({ peer, token }); }) .catch((error) => { From 3043098f0c42ad727e7e743b80d9893d265fabda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 10:38:51 +0100 Subject: [PATCH 004/198] Remove jwt, not needed --- server/lib/Room.js | 27 ++++----------------------- server/package.json | 1 - server/server.js | 25 ++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index b6ee85b..4950123 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -3,7 +3,6 @@ const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); const { v4: uuidv4 } = require('uuid'); -const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -128,31 +127,15 @@ class Room extends EventEmitter { logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); - let verifiedPeer = false; + // This peer is returning, reconnect + const verifiedPeer = token && token === this._uuid; - if (token) - { - try - { - const decoded = jwt.verify(token, this._uuid); - - if (decoded.id === peer.id) - verifiedPeer = true; - } - catch (err) - { - logger.warn('handlePeer() | invalid token'); - } - } - - // Allow reconnections, remove old peer + // Should not happen if (this._peers[peer.id]) { logger.warn( 'handleConnection() | there is already a peer with same peerId [peer:"%s"]', peer.id); - - this._peers[peer.id].close(); } // Returning user @@ -373,9 +356,7 @@ class Room extends EventEmitter } else { - const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); - - peer.socket.handshake.session.token = token; + peer.socket.handshake.session.token = this._uuid; peer.socket.handshake.session.save(); diff --git a/server/package.json b/server/package.json index ae1f3b9..50a6831 100644 --- a/server/package.json +++ b/server/package.json @@ -25,7 +25,6 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", - "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", diff --git a/server/server.js b/server/server.js index 2e50a8c..5e19248 100755 --- a/server/server.js +++ b/server/server.js @@ -464,11 +464,30 @@ async function runWebSocketServer() queue.push(async () => { - const room = await getOrCreateRoom({ roomId }); - const peer = new Peer({ id: peerId, roomId, socket }); - const { token } = socket.handshake.session; + const room = await getOrCreateRoom({ roomId }); + + let peer = peers.get(peerId); + + if (peer) + { + if (token) + { + peer.close(); + + peer = null; + } + else + { + socket.disconnect(true); + + return; + } + } + + peer = new Peer({ id: peerId, roomId, socket }); + peers.set(peerId, peer); peer.on('close', () => peers.delete(peerId)); From 5b96e34ea9bb8c7676ae6730c76a3f91fd58e5f9 Mon Sep 17 00:00:00 2001 From: Tobias H Date: Fri, 27 Mar 2020 15:56:24 +0100 Subject: [PATCH 005/198] Update de.json --- app/src/translations/de.json | 114 +++++++++++++++++------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/app/src/translations/de.json b/app/src/translations/de.json index d78e5bd..6ffe74b 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -1,46 +1,46 @@ { "socket.disconnected": "Verbindung unterbrochen", "socket.reconnecting": "Verbindung unterbrochen, versuche neu zu verbinden", - "socket.reconnected": "Verbindung wieder herges|tellt", + "socket.reconnected": "Verbindung wiederhergestellt", "socket.requestError": "Fehler bei Serveranfrage", - "room.chooseRoom": null, + "room.chooseRoom": "Wähle den Raum aus, den du betreten möchtest", "room.cookieConsent": "Diese Seite verwendet Cookies, um die Benutzerfreundlichkeit zu erhöhen", - "room.consentUnderstand": "I understand", - "room.joined": "Konferenzraum betreten", + "room.consentUnderstand": "Verstanden", + "room.joined": "Du bist dem Raum beigetreten", "room.cantJoin": "Betreten des Raumes nicht möglich", - "room.youLocked": "Raum wurde abgeschlossen", + "room.youLocked": "Du hast den Raum abgeschlossen", "room.cantLock": "Abschließen des Raumes nicht möglich", - "room.youUnLocked": "Raum geöffnet", + "room.youUnLocked": "Du hast den Raum geöffnet", "room.cantUnLock": "Öffnen des Raumes nicht möglich", "room.locked": "Raum wurde abgeschlossen", "room.unlocked": "Raum wurde geöffnet", - "room.newLobbyPeer": "Neuer Teilnehmer im Empfangsraum", - "room.lobbyPeerLeft": "Teilnehmer hat Empfangsraum verlassen", - "room.lobbyPeerChangedDisplayName": "Teilnehmer im Empfangsraum hat seinen Namen geändert: {displayName}", - "room.lobbyPeerChangedPicture": "Teilnehmer in Empfangsraum hat sein Avatar geändert", - "room.setAccessCode": "Zugangskode für den Raum geändert", - "room.accessCodeOn": "Zugangskode aktiviert", - "room.accessCodeOff": "Zugangskode deaktiviert", + "room.newLobbyPeer": "Neuer Teilnehmer im Warteraum", + "room.lobbyPeerLeft": "Ein Teilnehmer hat den Warteraum verlassen", + "room.lobbyPeerChangedDisplayName": "Ein Teilnehmer im Warteraum hat seinen Namen geändert zu: {displayName}", + "room.lobbyPeerChangedPicture": "Ein Teilnehmer im Warteraum hat seinen Avatar geändert", + "room.setAccessCode": "Zugangscode für den Raum geändert", + "room.accessCodeOn": "Zugangscode aktiviert", + "room.accessCodeOff": "Zugangscode deaktiviert", "room.peerChangedDisplayName": "{oldDisplayName} heißt jetzt {displayName}", "room.newPeer": "{displayName} hat den Raum betreten", "room.newFile": "Neue Datei verfügbar", "room.toggleAdvancedMode": "Erweiterter Modus aktiv", - "room.setDemocraticView": "Raumlayout demokratisch", - "room.setFilmStripView": "Raumlayout Filmstreifen", + "room.setDemocraticView": "Demokratische Ansicht", + "room.setFilmStripView": "Filmstreifen-Ansicht", "room.loggedIn": "Angemeldet", "room.loggedOut": "Abgemeldet", "room.changedDisplayName": "Dein Name ist jetzt {displayName}", - "room.changeDisplayNameError": "Konnte Name nicht ändern", - "room.chatError": "Konnte Meldung nicht senden", - "room.aboutToJoin": "Du bist dabei den Raum zu betreten", + "room.changeDisplayNameError": "Dein Name konnte nicht geändert werden", + "room.chatError": "Die Chat-Nachricht konnte nicht gesendet werden", + "room.aboutToJoin": "Du bist dabei, folgenden Raum zu betreten:", "room.roomId": "Raum ID: {roomName}", - "room.setYourName": "Gib deinen Namen an und wähle wie den Raum betreten willst", + "room.setYourName": "Gib deinen Namen an und wähle aus, wie du den Raum betreten willst:", "room.audioOnly": "Nur Audio", "room.audioVideo": "Audio und Video", - "room.youAreReady": "Ok, Du bist bereit", - "room.emptyRequireLogin": "Der Raum ist leer. Melde dich an um den Raum zu aktivieren, oder warte bis der Raum aktiviert wird", - "room.locketWait": "Der Raum ist abgeschlossen, warte bis Dir jemand öffnet", + "room.youAreReady": "Ok, du bist bereit", + "room.emptyRequireLogin": "Der Raum ist leer. Melde dich an um die Konferenz zu starten oder warte bis der Raum aktiviert wird", + "room.locketWait": "Der Raum ist abgeschlossen - warte, bis dich jemand rein lässt...", "room.lobbyAdministration": "Warteraum", "room.peersInLobby": "Teilnehmer im Warteraum", "room.lobbyEmpty": "Der Warteraum ist leer", @@ -49,34 +49,34 @@ "room.spotlights": "Aktive Teinehmer", "room.passive": "Passive Teilnehmer", "room.videoPaused": "Video gestoppt", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, + "room.muteAll": "Alle stummschalten", + "room.stopAllVideo": "Alle Videos stoppen", + "room.closeMeeting": "Meeting schließen", "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", - "tooltip.admitFromLobby": "Teilnehmer aktivieren", + "tooltip.admitFromLobby": "Teilnehmer reinlassen", "tooltip.lockRoom": "Raum abschließen", - "tooltip.unLockRoom": "Raum öffnen", + "tooltip.unLockRoom": "Raum entsperren", "tooltip.enterFullscreen": "Vollbild", "tooltip.leaveFullscreen": "Vollbild verlassen", "tooltip.lobby": "Warteraum", "tooltip.settings": "Einstellungen", "tooltip.participants": "Teilnehmer", - "tooltip.kickParticipant": null, + "tooltip.kickParticipant": "Teilnehmer rauswerfen", - "label.roomName": null, - "label.chooseRoomButton": null, + "label.roomName": "Name des Raums", + "label.chooseRoomButton": "Weiter", "label.yourName": "Dein Name", - "label.newWindow": "In separatem Fenster öffnen", + "label.newWindow": "Neues Fenster", "label.fullscreen": "Vollbild", "label.openDrawer": "Menü", - "label.leave": "Ausgang", - "label.chatInput": "Schreibe Chat...", + "label.leave": "Verlassen", + "label.chatInput": "Schreibe eine Nachricht...", "label.chat": "Chat", "label.filesharing": "Dateien", "label.participants": "Teilnehmer", - "label.shareFile": "Teile Datai", + "label.shareFile": "Datei hochladen", "label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt", "label.unknown": "Unbekannt", "label.democratic": "Demokratisch", @@ -90,14 +90,14 @@ "settings.settings": "Einstellungen", "settings.camera": "Kamera", - "settings.selectCamera": "Wähle Videogerät", + "settings.selectCamera": "Wähle ein Videogerät", "settings.cantSelectCamera": "Kann Videogerät nicht aktivieren", "settings.audio": "Audiogerät", - "settings.selectAudio": "Wähle Audiogerät", + "settings.selectAudio": "Wähle ein Audiogerät", "settings.cantSelectAudio": "Kann Audiogerät nicht aktivieren", - "settings.resolution": "Wähle Auflösung", + "settings.resolution": "Wähle eine Auflösung", "settings.layout": "Raumlayout", - "settings.selectRoomLayout": "Wähle Raumlayout", + "settings.selectRoomLayout": "Wähle ein Raumlayout", "settings.advancedMode": "Erweiterter Modus", "settings.permanentTopBar": "Permanente obere Leiste", "settings.lastn": "Anzahl der sichtbaren Videos", @@ -105,40 +105,40 @@ "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", "filesharing.successfulFileShare": "Datei wurde geteilt", - "filesharing.unableToShare": "Kann Datei nicht teilen", + "filesharing.unableToShare": "Datei kann nicht geteilt werden", "filesharing.error": "Fehler beim Teilen der Datei", "filesharing.finished": "Datei heruntergeladen", "filesharing.save": "Speichern", "filesharing.sharedFile": "{displayName} hat eine Datei geteilt", "filesharing.download": "Herunterladen", - "filesharing.missingSeeds": "Wenn das Herunterladen nicht pausiert ist wahrscheinlich niemeand mehr im Raum der die Datei teilen kann. Datei muss erneut geteilt werden.", + "filesharing.missingSeeds": "Wenn der Download zu lange dauert, ist wahrscheinlich keiner mehr im Raum, der die Datei teilen kann. Die Datei muss erneut hochgeladen werden.", - "devices.devicesChanged": "Mediengeräte wurden aktualisiert und sind in Einstellungen verfügbar", + "devices.devicesChanged": "Mediengeräte wurden aktualisiert und sind in den Einstellungen verfügbar", "device.audioUnsupported": "Audio nicht unterstützt", "device.activateAudio": "Aktiviere Audio", - "device.muteAudio": "stummschalten", - "device.unMuteAudio": "Aktiviere Audio", + "device.muteAudio": "Stummschalten", + "device.unMuteAudio": "Stummschaltung aufheben", "device.videoUnsupported": "Video nicht unterstützt", "device.startVideo": "Starte Video", "device.stopVideo": "Stoppe Video", - "device.screenSharingUnsupported": "Bildschirmteilen nicht unterstützt", - "device.startScreenSharing": "Bildschirmteilen", - "device.stopScreenSharing": "Beende Bildschirmteilen", + "device.screenSharingUnsupported": "Bildschirmfreigabe nicht unterstützt", + "device.startScreenSharing": "Starte Bildschirmfreigabe", + "device.stopScreenSharing": "Beende Bildschirmfreigabe", - "devices.microphoneDisconnected": "Mikrophon nicht verbunden", - "devices.microphoneError": "Fehler mit Mikrophon", - "devices.microPhoneMute": "Mikrophon stumm geschaltet", - "devices.micophoneUnMute": "Mikrophon aktiviert", - "devices.microphoneEnable": "Mikrofonen aktiviert", - "devices.microphoneMuteError": "Kann Mikrophon nicht stummschalten", - "devices.microphoneUnMuteError": "Kann Mikrophon nicht aktivieren", + "devices.microphoneDisconnected": "Mikrofon nicht verbunden", + "devices.microphoneError": "Fehler beim Zugriff auf dein Mikrofon", + "devices.microPhoneMute": "Mikrofon stummgeschaltet", + "devices.micophoneUnMute": "Mikrofon aktiviert", + "devices.microphoneEnable": "Mikrofon aktiviert", + "devices.microphoneMuteError": "Kann Mikrofon nicht stummschalten", + "devices.microphoneUnMuteError": "Kann Mikrofon nicht aktivieren", - "devices.screenSharingDisconnected" : "Bildschirmteilen unterbrochen", - "devices.screenSharingError": "Fehler beim Bildschirmteilen", + "devices.screenSharingDisconnected" : "Bildschirmfreigabe unterbrochen", + "devices.screenSharingError": "Fehler bei der Bildschirmfreigabe", - "devices.cameraDisconnected": "Video unterbrochen", - "devices.cameraError": "Fehler mit Videogerät" + "devices.cameraDisconnected": "Kamera getrennt", + "devices.cameraError": "Fehler mit deiner Kamera" } From c521bb9ad15f28c5abd5573901a9379e8db77385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 28 Mar 2020 21:17:59 +0100 Subject: [PATCH 006/198] Firefox relay if turnservers configured, fixes #160 --- app/src/RoomClient.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index fac58da..81e4181 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2349,7 +2349,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined, + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined, proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS }); @@ -2413,7 +2413,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined }); this._recvTransport.on( From 87d40375624c1b6559e5734a49aa7909d36ed6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 28 Mar 2020 23:20:37 +0100 Subject: [PATCH 007/198] We need jwt to make sure no one can hijack peerId --- app/src/RoomClient.js | 24 +++++++++++++++++++++++- server/lib/Room.js | 30 ++++++++++++++++++++++++------ server/package.json | 1 + server/server.js | 25 ++++++++++++------------- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 204643d..d301b86 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1521,24 +1521,46 @@ export default class RoomClient })); if (this._screenSharingProducer) + { this._screenSharingProducer.close(); + store.dispatch( + producerActions.removeProducer(this._screenSharingProducer.id)); + + this._screenSharingProducer = null; + } + if (this._webcamProducer) + { this._webcamProducer.close(); + store.dispatch( + producerActions.removeProducer(this._webcamProducer.id)); + + this._webcamProducer = null; + } + if (this._micProducer) + { this._micProducer.close(); - // Close mediasoup Transports. + store.dispatch( + producerActions.removeProducer(this._micProducer.id)); + + this._micProducer = null; + } + if (this._sendTransport) { this._sendTransport.close(); + this._sendTransport = null; } if (this._recvTransport) { this._recvTransport.close(); + this._recvTransport = null; } diff --git a/server/lib/Room.js b/server/lib/Room.js index 4950123..521007d 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -3,6 +3,7 @@ const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); const { v4: uuidv4 } = require('uuid'); +const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -123,12 +124,27 @@ class Room extends EventEmitter this.emit('close'); } - handlePeer({ peer, token }) + verifyPeer({ id, token }) { - logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); + try + { + const decoded = jwt.verify(token, this._uuid); - // This peer is returning, reconnect - const verifiedPeer = token && token === this._uuid; + logger.info('verifyPeer() [decoded:"%o"]', decoded); + + return decoded.id === id; + } + catch (err) + { + logger.warn('verifyPeer() | invalid token'); + } + + return false; + } + + handlePeer({ peer, returning }) + { + logger.info('handlePeer() [peer:"%s", roles:"%s", returning:"%s"]', peer.id, peer.roles, returning); // Should not happen if (this._peers[peer.id]) @@ -139,7 +155,7 @@ class Room extends EventEmitter } // Returning user - if (verifiedPeer) + if (returning) this._peerJoining(peer, true); // Always let ADMIN in, even if locked else if (peer.roles.includes(userRoles.ADMIN)) @@ -356,7 +372,9 @@ class Room extends EventEmitter } else { - peer.socket.handshake.session.token = this._uuid; + const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); + + peer.socket.handshake.session.token = token; peer.socket.handshake.session.save(); diff --git a/server/package.json b/server/package.json index 50a6831..ae1f3b9 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", + "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", diff --git a/server/server.js b/server/server.js index 5e19248..c1c0f61 100755 --- a/server/server.js +++ b/server/server.js @@ -469,21 +469,20 @@ async function runWebSocketServer() const room = await getOrCreateRoom({ roomId }); let peer = peers.get(peerId); + let returning = false; - if (peer) - { - if (token) - { + if (peer && !token) + { // Don't allow hijacking sessions + socket.disconnect(true); + + return; + } + else if (token && room.verifyPeer({ id: peerId, token })) + { // Returning user, remove if old peer exists + if (peer) peer.close(); - peer = null; - } - else - { - socket.disconnect(true); - - return; - } + returning = true; } peer = new Peer({ id: peerId, roomId, socket }); @@ -516,7 +515,7 @@ async function runWebSocketServer() } } - room.handlePeer({ peer, token }); + room.handlePeer({ peer, returning }); }) .catch((error) => { From 9065996abd6f75f60640dacca797cc333b728fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Sat, 28 Mar 2020 21:43:25 +0100 Subject: [PATCH 008/198] Add trustProxy option to server config --- server/config/config.example.js | 6 ++++++ server/server.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/server/config/config.example.js b/server/config/config.example.js index 53b1f05..b33c04b 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -64,6 +64,12 @@ module.exports = // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : false, + // WebServer/Express trust proxy config for httpOnly mode + // You can find more info: + // - https://expressjs.com/en/guide/behind-proxies.html + // - https://www.npmjs.com/package/proxy-addr + // use case: loadbalancer backend + trustProxy : '', // This function will be called on successful login through oidc. // Use this function to map your oidc userinfo to the Peer object. // The roomId is equal to the room name. diff --git a/server/server.js b/server/server.js index c1c0f61..853cd47 100755 --- a/server/server.js +++ b/server/server.js @@ -100,6 +100,10 @@ const session = expressSession({ } }); +if (config.trustProxy) { + app.set('trust proxy', config.trustProxy); +} + app.use(session); passport.serializeUser((user, done) => From 924e78de39929e639683e892a49cd589d0f71b50 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Mon, 30 Mar 2020 12:54:56 +0200 Subject: [PATCH 009/198] lint --- app/src/translations/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/translations/de.json b/app/src/translations/de.json index c87b807..d2fe938 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -54,9 +54,9 @@ "room.closeMeeting": "Meeting schließen", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", - "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", + "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", - "tooltip.login": "Anmelden", + "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", "tooltip.admitFromLobby": "Teilnehmer reinlassen", "tooltip.lockRoom": "Raum abschließen", From a31da9359b7099237e4cc46309f2198fbbfd5793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 01:27:59 +0200 Subject: [PATCH 010/198] Add logging class that receives room and peer events --- server/config/config.example.js | 38 +++++++++++++++++++++++++++++++++ server/server.js | 36 ++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 53b1f05..cfd1496 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -1,5 +1,7 @@ const os = require('os'); const userRoles = require('../userRoles'); +// const AwaitQueue = require('awaitqueue'); +// const axios = require('axios'); module.exports = { @@ -64,6 +66,41 @@ module.exports = // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : false, + // This logger class will have the log function + // called every time there is a room created or destroyed, + // or peer created or destroyed. This would then be able + // to log to a file or external service. + /* StatusLogger : class + { + constructor() + { + this._queue = new AwaitQueue(); + } + + // Array of rooms + // [ + // { roomId : 'example', peers: 5 }, + // { roomId : 'example2', peers: 4 } + // ] + // eslint-disable-next-line no-unused-vars + async log({ rooms, peers }) + { + this._queue.push(async () => + { + // Do your logging in here, use queue to keep correct order + + // eslint-disable-next-line no-console + console.log('Number of rooms: ', rooms); + // eslint-disable-next-line no-console + console.log('Number of peers: ', peers); + }) + .catch((error) => + { + // eslint-disable-next-line no-console + console.log('error in log', error); + }); + } + }, */ // This function will be called on successful login through oidc. // Use this function to map your oidc userinfo to the Peer object. // The roomId is equal to the room name. @@ -124,6 +161,7 @@ module.exports = peer.addRole(userRoles.AUTHENTICATED); }, */ + // eslint-disable-next-line no-unused-vars userMapping : async ({ peer, roomId, userinfo }) => { if (userinfo.picture != null) diff --git a/server/server.js b/server/server.js index c1c0f61..dbc6bb0 100755 --- a/server/server.js +++ b/server/server.js @@ -45,6 +45,11 @@ const logger = new Logger(); const queue = new AwaitQueue(); +let statusLogger = null; + +if ('StatusLogger' in config) + statusLogger = new config.StatusLogger(); + // mediasoup Workers. // @type {Array} const mediasoupWorkers = []; @@ -159,6 +164,17 @@ async function run() }, 10000); } +function statusLog() +{ + if (statusLogger) + { + statusLogger.log({ + rooms : rooms.size, + peers : peers.size + }); + } +} + function setupLTI(ltiConfig) { @@ -361,7 +377,7 @@ async function runHttpsServer() app.all('*', async (req, res, next) => { - if (req.secure || config.httpOnly ) + if (req.secure || config.httpOnly) { const ltiURL = new URL(`${req.protocol }://${ req.get('host') }${req.originalUrl}`); @@ -489,7 +505,12 @@ async function runWebSocketServer() peers.set(peerId, peer); - peer.on('close', () => peers.delete(peerId)); + peer.on('close', () => + { + peers.delete(peerId); + + statusLog(); + }); if ( Boolean(socket.handshake.session.passport) && @@ -516,6 +537,8 @@ async function runWebSocketServer() } room.handlePeer({ peer, returning }); + + statusLog(); }) .catch((error) => { @@ -590,7 +613,14 @@ async function getOrCreateRoom({ roomId }) rooms.set(roomId, room); - room.on('close', () => rooms.delete(roomId)); + statusLog(); + + room.on('close', () => + { + rooms.delete(roomId); + + statusLog(); + }); } return room; From fe532433724b34a55732a1b80f7e9035d4693736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 01:31:53 +0200 Subject: [PATCH 011/198] Lint and fix --- server/config/config.example.js | 7 ++----- server/server.js | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 7d9cff7..4b8cfe7 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -77,11 +77,8 @@ module.exports = this._queue = new AwaitQueue(); } - // Array of rooms - // [ - // { roomId : 'example', peers: 5 }, - // { roomId : 'example2', peers: 4 } - // ] + // rooms: number of rooms + // peers: number of peers // eslint-disable-next-line no-unused-vars async log({ rooms, peers }) { diff --git a/server/server.js b/server/server.js index a492fc1..3243280 100755 --- a/server/server.js +++ b/server/server.js @@ -105,7 +105,8 @@ const session = expressSession({ } }); -if (config.trustProxy) { +if (config.trustProxy) +{ app.set('trust proxy', config.trustProxy); } From 9cbe8b9609d2eac79dd2695184e00fccd8cc47e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 12:34:49 +0200 Subject: [PATCH 012/198] Hotfix for bug in react-scripts --- app/package.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/package.json b/app/package.json index 89a116e..b5138de 100644 --- a/app/package.json +++ b/app/package.json @@ -28,7 +28,7 @@ "react-intl": "^3.4.0", "react-redux": "^7.1.1", "react-router-dom": "^5.1.2", - "react-scripts": "^3.3.0", + "react-scripts": "3.0.1", "redux": "^4.0.4", "redux-logger": "^3.0.6", "redux-persist": "^6.0.0", @@ -56,11 +56,7 @@ ], "devDependencies": { "electron": "^7.1.1", - "eslint": "^6.5.1", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-react": "^7.16.0", "foreman": "^3.0.1", - "jest": "^24.9.0", "redux-mock-store": "^1.5.3" } } From e305e7e1a558481474e474addfbc07a72fa45b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 12:36:08 +0200 Subject: [PATCH 013/198] Update version --- app/package.json | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/package.json b/app/package.json index b5138de..e7958ec 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "multiparty-meeting", - "version": "3.1.0", + "version": "3.2.0", "private": true, "description": "multiparty meeting service", "author": "Håvar Aambø Fosstveit ", diff --git a/server/package.json b/server/package.json index cc4e236..6bde5ee 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "multiparty-meeting-server", - "version": "3.1.0", + "version": "3.2.0", "private": true, "description": "multiparty meeting server", "author": "Håvar Aambø Fosstveit ", From 197156e6f615c05f1dbb43c1372802d58060a2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 20:32:36 +0200 Subject: [PATCH 014/198] Handle it correctly if a user tries to share a file that has allready been shared. Closes #142 --- app/package.json | 5 ++-- app/src/RoomClient.js | 64 +++++++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/app/package.json b/app/package.json index ed4c27c..3364ad2 100644 --- a/app/package.json +++ b/app/package.json @@ -11,9 +11,10 @@ "@material-ui/core": "^4.5.1", "@material-ui/icons": "^4.5.1", "bowser": "^2.7.0", + "create-torrent": "^4.4.1", "dompurify": "^2.0.7", "domready": "^1.0.8", - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "file-saver": "^2.0.2", "hark": "^1.2.3", "is-electron": "^2.2.0", @@ -37,7 +38,7 @@ "riek": "^1.1.0", "socket.io-client": "^2.3.0", "source-map-explorer": "^2.1.0", - "webtorrent": "^0.107.16" + "webtorrent": "^0.107.17" }, "scripts": { "analyze": "source-map-explorer build/static/js/*", diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 200cf85..14194d6 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -14,6 +14,8 @@ import * as consumerActions from './actions/consumerActions'; import * as producerActions from './actions/producerActions'; import * as notificationActions from './actions/notificationActions'; +let createTorrent; + let WebTorrent; let saveAs; @@ -756,26 +758,48 @@ export default class RoomClient }) })); - this._webTorrent.seed( - files, - { announceList: [ [ 'wss://tracker.lab.vvc.niif.hu:443' ] ] }, - (torrent) => + createTorrent(files, (err, torrent) => + { + if (err) { - store.dispatch(requestActions.notify( + return store.dispatch(requestActions.notify( { + type : 'error', text : intl.formatMessage({ - id : 'filesharing.successfulFileShare', - defaultMessage : 'File successfully shared' + id : 'filesharing.unableToShare', + defaultMessage : 'Unable to share file' }) })); + } - store.dispatch(fileActions.addFile( - this._peerId, - torrent.magnetURI - )); + const existingTorrent = this._webTorrent.get(torrent); - this._sendFile(torrent.magnetURI); - }); + if (existingTorrent) + { + return this._sendFile(existingTorrent.magnetURI); + } + + this._webTorrent.seed( + files, + { announceList: [ [ 'wss://tracker.lab.vvc.niif.hu:443' ] ] }, + (newTorrent) => + { + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'filesharing.successfulFileShare', + defaultMessage : 'File successfully shared' + }) + })); + + store.dispatch(fileActions.addFile( + this._peerId, + newTorrent.magnetURI + )); + + this._sendFile(newTorrent.magnetURI); + }); + }); } // { file, name, picture } @@ -1501,6 +1525,13 @@ export default class RoomClient async _loadDynamicImports() { + ({ default: createTorrent } = await import( + + /* webpackPrefetch: true */ + /* webpackChunkName: "createtorrent" */ + 'create-torrent' + )); + ({ default: WebTorrent } = await import( /* webpackPrefetch: true */ @@ -2356,7 +2387,7 @@ export default class RoomClient } } }); - + this._webTorrent.on('error', (error) => { logger.error('Filesharing [error:"%o"]', error); @@ -2364,7 +2395,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : intl.formatMessage({ id: 'filesharing.error', defaultMessage: 'There was a filesharing error' }) + text : intl.formatMessage({ + id : 'filesharing.error', + defaultMessage : 'There was a filesharing error' + }) })); }); From 038aeaff670f0d6ba96dcdbda269f13e5079cc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 1 Apr 2020 14:13:46 +0200 Subject: [PATCH 015/198] Add quality indicator pr peer --- .../components/VideoContainers/VideoView.js | 114 +++++++++++++++--- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 930a04c..3395e97 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -60,24 +60,95 @@ const styles = (theme) => { display : 'flex', transitionProperty : 'opacity', - transitionDuration : '.15s', - '&.hidden' : - { - opacity : 0, - transitionDuration : '0s' - } + transitionDuration : '.15s' }, box : { - padding : theme.spacing(0.5), - borderRadius : 2, - backgroundColor : 'rgba(0, 0, 0, 0.25)', - '& p' : + padding : theme.spacing(0.5), + borderRadius : 2, + '& p' : { userSelect : 'none', margin : 0, color : 'rgba(255, 255, 255, 0.7)', fontSize : '0.8em' + }, + '&.left' : + { + backgroundColor : 'rgba(0, 0, 0, 0.25)' + }, + '&.right' : + { + marginLeft : 'auto', + width : 30 + }, + '&.hidden' : + { + opacity : 0, + transitionDuration : '0s' + } + }, + qualityBar : + { + height : 6, + borderRadius : 2, + background : 'rgba(green, 0.65)', + transitionProperty : 'height background-color', + transitionDuration : '0.25s', + '&.score0' : + { + width : 0, + backgroundColor : 'rgba(246, 58, 15, 0.65)' + }, + '&.score1' : + { + width : '10%', + backgroundColor : 'rgba(246, 58, 15, 0.65)' + }, + '&.score2' : + { + width : '20%', + backgroundColor : 'rgba(246, 58, 15, 0.65)' + }, + '&.score3' : + { + width : '30%', + backgroundColor : 'rgba(246, 58, 15, 0.65)' + }, + '&.score4' : + { + width : '40%', + backgroundColor : 'rgba(246, 58, 15, 0.65)' + }, + '&.score5' : + { + width : '50%', + backgroundColor : 'rgba(242, 176, 30, 0.65)' + }, + '&.score6' : + { + width : '60%', + backgroundColor : 'rgba(242, 176, 30, 0.65)' + }, + '&.score7' : + { + width : '70%', + backgroundColor : 'rgba(242, 211, 27, 0.65)' + }, + '&.score8' : + { + width : '80%', + backgroundColor : 'rgba(242, 211, 27, 0.65)' + }, + '&.score9' : + { + width : '90%', + backgroundColor : 'rgba(134, 224, 30, 0.65)' + }, + '&.score10' : + { + width : '100%', + backgroundColor : 'rgba(134, 224, 30, 0.65)' } }, peer : @@ -138,8 +209,8 @@ class VideoView extends React.PureComponent advancedMode, videoVisible, videoMultiLayer, - // audioScore, - // videoScore, + audioScore, + videoScore, // consumerSpatialLayers, // consumerTemporalLayers, consumerCurrentSpatialLayer, @@ -161,12 +232,8 @@ class VideoView extends React.PureComponent return (
-