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/158] 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/158] 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 11b3f65ec970ea826d0977089f83d99b46ab903f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 7 Apr 2020 09:10:44 +0200 Subject: [PATCH 003/158] Add listeningHost fix #179 --- server/config/config.example.js | 3 +++ server/server.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 3787b33..ada1553 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -58,6 +58,9 @@ module.exports = cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`, key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` }, + // listening Host or IP + // If ommitted listens on every IP. ("0.0.0.0" and "::") + //listeningHost: 'localhost', // Listening port for https server. listeningPort : 443, // Any http request is redirected to https. diff --git a/server/server.js b/server/server.js index 119f8e6..6e817b0 100755 --- a/server/server.js +++ b/server/server.js @@ -427,11 +427,17 @@ async function runHttpsServer() // http const redirectListener = http.createServer(app); - redirectListener.listen(config.listeningRedirectPort); + if(config.listeningHost) + redirectListener.listen(config.listeningRedirectPort, config.listeningHost); + else + redirectListener.listen(config.listeningRedirectPort); } // https or http - mainListener.listen(config.listeningPort); + if(config.listeningHost) + mainListener.listen(config.listeningPort, config.listeningHost); + else + mainListener.listen(config.listeningPort); } function isPathAlreadyTaken(url) From 87d7312a3776fab162efee395f6fe6460e432bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 31 Mar 2020 00:23:19 +0200 Subject: [PATCH 004/158] Add LTI doc --- LTI/LTI.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ LTI/lti1.png | Bin 0 -> 87852 bytes LTI/lti2.png | Bin 0 -> 59763 bytes LTI/lti3.png | Bin 0 -> 118045 bytes LTI/lti4.png | Bin 0 -> 136597 bytes 5 files changed, 61 insertions(+) create mode 100644 LTI/LTI.md create mode 100644 LTI/lti1.png create mode 100644 LTI/lti2.png create mode 100644 LTI/lti3.png create mode 100644 LTI/lti4.png diff --git a/LTI/LTI.md b/LTI/LTI.md new file mode 100644 index 0000000..ff6b9ec --- /dev/null +++ b/LTI/LTI.md @@ -0,0 +1,61 @@ +# Learning Tools Interoperability (LTI) + +## LTI + +Read more about IMS Global defined interface for tools like our VideoConference system integration with Learning Managment Systems(LMS) (e.g. moodle). +See: [IMS Global Learning Tool Interoperability](https://www.imsglobal.org/activity/learning-tools-interoperability) + +We implemented LTI interface version 1.0/1.1 + +### Server config auth section LTI settings + +Set in server configuration a random key and secret + +``` json +auth : + { + lti : + { + consumerKey : 'key', + consumerSecret : 'secret' + }, + } +``` + +### Configure your LMS system with secret and key settings above + +#### Auth tool URL + +Set tool URL to your server with path /auth/lti + +``` url +https://mm.example.com/auth/lti +``` + +#### In moodle find external tool plugin setting and external tool action + +See: [moodle external tool settings](https://docs.moodle.org/38/en/External_tool_settings) + +#### Add and activity + +![Add external tool](lti1.png) + +#### Setup Activity + +##### Activity setup basic form + +Open fully the settings **Click on show more!!** +![Add external tool config](lti2.png) + +##### Empty full form + +![Opened external tool config](lti3.png) + +##### Filled out form + +![Filled out external tool config](lti4.png) + +## moodle plugin + +Alternativly you can use multipartymeeting moodle plugin: +[https://github.com/misi/moodle-mod_multipartymeeting](https://github.com/misi/moodle-mod_multipartymeeting) diff --git a/LTI/lti1.png b/LTI/lti1.png new file mode 100644 index 0000000000000000000000000000000000000000..cc420c49bb09244b2d53eda790262ebe77176237 GIT binary patch literal 87852 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

!#|bgz{Y$vcmJ`op;t|fg-=D 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 OU$Qbv(s_~(|NK9;wfI{A literal 0 HcmV?d00001 diff --git a/LTI/lti3.png b/LTI/lti3.png new file mode 100644 index 0000000000000000000000000000000000000000..3b16d35189c4254d56ec8cc4cd2b09a9ec2f67c3 GIT binary patch literal 118045 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ℑyXnAz;t^{<8t)oo4;M#IGX9$<)Zt0-mzddYxi5q z#(oXk^GDz620FH1{(PxioX!EeOFa52=j#$CY!Olw>U-q%Y42;_w_r(8ZZe%ga!$3_ z

Pr*&ERhtY>Y@fHp%!>W>5JBoSTafKUp%=`< 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 z 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 Date: Tue, 31 Mar 2020 21:09:58 +0200 Subject: [PATCH 005/158] Import and use Italian translation --- app/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/index.js b/app/src/index.js index c7f0d90..6bb2cc3 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -35,6 +35,7 @@ import messagesChinese from './translations/cn'; import messagesSpanish from './translations/es'; import messagesCroatian from './translations/hr'; import messagesCzech from './translations/cz'; +import messagesItalian from './translations/it'; import './index.css'; @@ -57,7 +58,8 @@ const messages = 'zh' : messagesChinese, 'es' : messagesSpanish, 'hr' : messagesCroatian, - 'cz' : messagesCzech + 'cz' : messagesCzech, + 'it' : messagesItalian }; const locale = navigator.language.split(/[-_]/)[0]; // language without region code From a5b6c9a36759bc6a321334bd4c72d731d27d1d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 1 Apr 2020 11:54:05 +0200 Subject: [PATCH 006/158] Fix cz=>cs iso lang code for Czech translation --- app/src/index.js | 4 ++-- app/src/translations/{cz.json => cs.json} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename app/src/translations/{cz.json => cs.json} (100%) diff --git a/app/src/index.js b/app/src/index.js index 6bb2cc3..9261344 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -34,7 +34,7 @@ import messagesPortuguese from './translations/pt'; import messagesChinese from './translations/cn'; import messagesSpanish from './translations/es'; import messagesCroatian from './translations/hr'; -import messagesCzech from './translations/cz'; +import messagesCzech from './translations/cs'; import messagesItalian from './translations/it'; import './index.css'; @@ -58,7 +58,7 @@ const messages = 'zh' : messagesChinese, 'es' : messagesSpanish, 'hr' : messagesCroatian, - 'cz' : messagesCzech, + 'cs' : messagesCzech, 'it' : messagesItalian }; diff --git a/app/src/translations/cz.json b/app/src/translations/cs.json similarity index 100% rename from app/src/translations/cz.json rename to app/src/translations/cs.json From ef6e52127277d487735e6ca038ff5980a8da9e02 Mon Sep 17 00:00:00 2001 From: Melkin1968 Date: Wed, 1 Apr 2020 18:24:35 +0300 Subject: [PATCH 007/158] Add uk(Ukraine) localisation file --- app/src/translations/uk.json | 140 +++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 app/src/translations/uk.json diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json new file mode 100644 index 0000000..4c051ed --- /dev/null +++ b/app/src/translations/uk.json @@ -0,0 +1,140 @@ +{ +"socket.disconnected": "Ви відключені", +"socket.reconnecting": "Ви від'єдналися, намагаєтесь знову підключитися", +"socket.reconnected": "Ви знову підключилися", +"socket.requestError": "Помилка при запиті сервера", + +"room.chooseRoom": "Виберіть назву кімнати, до якої хочете приєднатися", +"room.cookieConsent": "Цей веб-сайт використовує файли cookie для поліпшення роботи користувачів", +"room.consentUnderstand": "Я розумію", +"room.joined": "Ви приєдналися до кімнати", +"room.cantJoin": "Неможливо приєднатися до кімнати", +"room.youLocked": "Ви заблокували кімнату", +"room.cantLock": "Не вдається заблокувати кімнату", +"room.youUnLocked": "Ви розблокували кімнату", +"room.cantUnLock": "Не вдається розблокувати кімнату", +"room.locked": "Кімната зараз заблокована", +"room.unlocked": "Кімната зараз розблокована", +"room.newLobbyPeer": "Новий учасник увійшов у зал очікування", +"room.lobbyPeerLeft": "Учасник вийшов із зала очікування", +"room.lobbyPeerChangedDisplayName": "Учасник у залі очікування змінив ім'я на {displayName}", +"room.lobbyPeerChangedPicture": "Учасник залу очікування змінив зображення", +"room.setAccessCode": "Код доступу до кімнати оновлений", +"room.accessCodeOn": "Код доступу до кімнати зараз активований", +"room.accessCodeOff": "Код доступу до кімнати зараз відключений", +"room.peerChangedDisplayName": "{oldDisplayName} змінив ім'я на {displayName}", +"room.newPeer": "{displayName} приєднався до кімнати", +"room.newFile": "Новий файл є у доступі", +"room.toggleAdvancedMode": "Увімкнено розширений режим", +"room.setDemocratView": "Змінено макет на демократичний вигляд", +"room.setFilmStripView": "Змінено макет на вид фільму", +"room.loggedIn": "Ви ввійшли в систему", +"room.loggedOut": "Ви вийшли з системи", +"room.changedDisplayName": "Відображуване ім’я змінено на {displayName}", +"room.changeDisplayNameError": "Сталася помилка під час зміни вашого відображуваного імені", +"room.chatError": "Не вдається надіслати повідомлення в чаті", +"room.aboutToJoin": "Ви збираєтесь приєднатися до зустрічі", +"room.roomId": "Ідентифікатор кімнати: {roomName}", +"room.setYourName": "Встановіть своє ім'я для участі та виберіть, як ви хочете приєднатися:", +"room.audioOnly": "Тільки аудіо", +"room.audioVideo": "Аудіо та відео", +"room.youAreReady": "Добре, ви готові", +"room.emptyRequireLogin": "Кімната порожня! Ви можете увійти, щоб розпочати зустріч або чекати, поки хост приєднається", +"room.locketWait": "Кімната заблокована - дочекайтеся, поки хтось не впустить вас у ...", +"room.lobbyAdministration": "Адміністрація залу очікування", +"room.peersInLobby": "Учасники залу очікувань", +"room.lobbyEmpty": "Наразі у залі очікувань немає нікого", +"room.hiddenPeers": "{hiddenPeersCount, множина, один {учасник} інший {учасників}}", +"room.me": "Я", +"room.spotlights": "Учасники у центрі уваги", +"room.passive": "Пасивні учасники", +"room.videoPaused": "Це відео призупинено", + +"tooltip.login": "Увійти", +"tooltip.logout": "Вихід", +"tooltip.admitFromLobby": "Вхід із залу очікувань", +"tooltip.lockRoom": "Заблокувати кімнату", +"tooltip.unLockRoom": "Розблокувати кімнату", +"tooltip.enterFullscreen": "Вивести повний екран", +"tooltip.leaveFullscreen": "Залишити повноекранний екран", +"tooltip.lobby": "Показати зал очікувань", +"tooltip.settings": "Показати налаштування", +"tooltip.participants": "Показати учасників", + +"label.roomName": "Назва кімнати", +"label.chooseRoomButton": "Продовжити", +"label.yourName": "Ваше ім'я", +"label.newWindow": "Нове вікно", +"label.fullscreen": "Повний екран", +"label.openDrawer": "Відкрити ящик", +"label.leave": "Залишити", +"label.chatInput": "Введіть повідомлення чату ...", +"label.chat": "Чат", +"label.filesharing": "Обмін файлами", +"label.participants": "Учасники", +"label.shareFile": "Надіслати файл", +"label.fileSharingUnsupported": "Обмін файлами не підтримується", +"label.unknown": "Невідомо", +"label.democrat": "Демократичний вигляд", +"label.filmstrip": "У вигляді кінострічки", +"label.low": "Низький", +"label.medium": "Середній", +"label.high": "Високий (HD)", +"label.veryHigh": "Дуже високий (FHD)", +"label.ultra": "Ультра (UHD)", +"label.close": "Закрити", + +"settings.settings": "Налаштування", +"settings.camera": "Камера", +"settings.selectCamera": "Вибрати відеопристрій", +"settings.cantSelectCamera": "Неможливо вибрати відеопристрій", +"settings.audio": "Аудіопристрій", +"settings.selectAudio": "Вибрати аудіопристрій", +"settings.cantSelectAudio": "Неможливо вибрати аудіопристрій", +"settings.resolution": "Виберіть роздільну здатність відео", +"settings.layout": "Розміщення кімнати", +"settings.selectRoomLayout": "Вибір розташування кімнати", +"settings.advancedMode": "Розширений режим", +"settings.permanentTopBar": "Постійний верхній рядок", +"settings.lastn": "Кількість видимих ​​відео", + +"filesharing.saveFileError": "Неможливо зберегти файл", +"filesharing.startingFileShare": "Спроба поділитися файлом", +"filesharing.successfulFileShare": "Файл готовий для обміну", +"filesharing.unableToShare": "Неможливо поділитися файлом", +"filesharing.error": "Виникла помилка обміну файлами", +"filesharing.finished": "Завантаження файлу закінчено", +"filesharing.save": "Зберегти", +"filesharing.sharedFile": "{displayName} поділився файлом", +"filesharing.download": "Завантажити", +"filesharing.missingSeeds": "Якщо цей процес триває тривалий час, може не з’явиться хтось, хто роздає цей торрент. Спробуйте попросити когось перезавантажити потрібний файл.", + +"devices.devicesChanged": "Ваші пристрої змінилися, налаштуйте ваші пристрої в діалоговому вікні налаштувань", + +"device.audioUnsupported": "Аудіо не підтримується", +"device.activateAudio": "Активувати звук", +"device.muteAudio": "Вимкнути звук", +"device.unMuteAudio": "Увімкнути звук", + +"device.videoUnsupported": "Відео не підтримується", +"device.startVideo": "Запустити відео", +"device.stopVideo": "Зупинити відео", + +"device.screenSharingUnsupported": "Обмін екраном не підтримується", +"device.startScreenSharing": "Початок спільного використання екрана", +"device.stopScreenSharing": "Зупинити спільний доступ до екрана", + +"devices.microphoneDisconnected": "Мікрофон відключений", +"devices.microphoneError": "Сталася помилка під час доступу до мікрофона", +"devices.microPhoneMute": "Вимкнено ваш мікрофон", +"devices.micophoneUnMute": "Не ввімкнено ваш мікрофон", +"devices.microphoneEnable": "Увімкнено мікрофон", +"devices.microphoneMuteError": "Не вдається вимкнути мікрофон", +"devices.microphoneUnMuteError": "Неможливо ввімкнути мікрофон", + +"devices.screenSharingDisconnected": "Спільний доступ до екрана відключений", +"devices.screenSharingError": "Сталася помилка під час доступу до екрану", + +"devices.cameraDisconnected": "Камера відключена", +"devices.cameraError": "Під час доступу до камери сталася помилка" +} \ No newline at end of file From 8f4a5aff4e09250fb4425719ff7b265239547092 Mon Sep 17 00:00:00 2001 From: Melkin1968 Date: Wed, 1 Apr 2020 18:29:19 +0300 Subject: [PATCH 008/158] Update index.js to include Ukrainian localisation file --- app/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/index.js b/app/src/index.js index 9261344..dd052fa 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -36,6 +36,7 @@ import messagesSpanish from './translations/es'; import messagesCroatian from './translations/hr'; import messagesCzech from './translations/cs'; import messagesItalian from './translations/it'; +import messagesUkrainian from './translations/uk'; import './index.css'; @@ -59,7 +60,8 @@ const messages = 'es' : messagesSpanish, 'hr' : messagesCroatian, 'cs' : messagesCzech, - 'it' : messagesItalian + 'it' : messagesItalian, + 'uk' : messagesUkrainian }; const locale = navigator.language.split(/[-_]/)[0]; // language without region code From c5bfcf38104fd47cca9c14c6cf1da94f34000177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 22:24:42 +0200 Subject: [PATCH 009/158] Update README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc2c8cf..0a47de2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ If you want the automatic approach, you can find a docker image [here](https://h If you want the ansible approach, you can find ansible role [here](https://github.com/misi/mm-ansible/). [![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) - ## Manual installation * Prerequisites: Currently multiparty-meeting will only run on nodejs v10.* @@ -106,6 +105,12 @@ $ systemctl enable multiparty-meeting * 4443/tcp (default `npm start` port for developing with live browser reload, not needed in production enviroments - adjustable in app/package.json) * 40000-49999/udp/tcp (media ports - adjustable in `server/config.js`) +## Load balanced installation +To deploy this as a load balanced cluster, have a look at [HAproxy](HAproxy.md). + +## Learning management integration +To integrate with an LMS (e.g. Moodle), have a look at [LTI](LTI/LTI.md). + ## TURN configuration * You need an addtional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js` From 2a2d61f860f313a5f7b8ee54383ca344c139397d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 31 Mar 2020 00:35:15 +0200 Subject: [PATCH 010/158] Logout destroy session --- server/server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 6e817b0..4f21b43 100755 --- a/server/server.js +++ b/server/server.js @@ -333,7 +333,9 @@ async function setupAuth() } req.logout(); - res.send(logoutHelper()); + req.session.destroy(function (err) { + res.send(logoutHelper()); + }); }); // callback From f2f548112dedff0a16a224387815815ad87faf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 22:14:15 +0200 Subject: [PATCH 011/158] Change to arrow functions --- server/server.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server/server.js b/server/server.js index 4f21b43..70cb5bc 100755 --- a/server/server.js +++ b/server/server.js @@ -189,7 +189,7 @@ function setupLTI(ltiConfig) const ltiStrategy = new LTIStrategy( ltiConfig, - function(req, lti, done) + (req, lti, done) => { // LTI launch parameters if (lti) @@ -310,7 +310,7 @@ async function setupAuth() // lti launch app.post('/auth/lti', passport.authenticate('lti', { failureRedirect: '/' }), - function(req, res) + (req, res) => { res.redirect(`/${req.user.room}`); } @@ -333,9 +333,7 @@ async function setupAuth() } req.logout(); - req.session.destroy(function (err) { - res.send(logoutHelper()); - }); + req.session.destroy(() => res.send(logoutHelper())); }); // callback From 35c2bc7dd84943eb966c065dfb80fec9d913439e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 31 Mar 2020 00:22:48 +0200 Subject: [PATCH 012/158] Add HAproxy doc --- HAproxy.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 HAproxy.md diff --git a/HAproxy.md b/HAproxy.md new file mode 100644 index 0000000..acc88e4 --- /dev/null +++ b/HAproxy.md @@ -0,0 +1,109 @@ +# Howto deploy a (room based) loadbalanced cluster + +We use in this example an HA proxy loadbalancer to loadbalance between 3 backend server +The static web will be loadbalanced rounrdobin, and the websocket(signaling) and the media will be loadbalnced based on roomId URL parameter. + +## IP and dns + +In this basic example we use the following names and ips: + +### Backend + +* `mm1.example.com` <=> `192.0.2.1` +* `mm2.example.com` <=> `192.0.2.2` +* `mm3.example.com` <=> `192.0.2.3` + +### Redis + +* `redis.example.com` <=> `192.0.2.4` + +### LoadBalncer HAproxy + +* `meet.example.com` <=> `192.0.2.5` + +## Deploy multiple backend/worker + +For example with ansible +Read more here: [mm-ansible](https://github.com/misi/mm-ansible) +[![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) + +## Setup redis for central session store + +### Use one redis for all multiparty meeting + +* Deploy a redis cluster so use one redis HA cluster for all instances. + * We will use in our actual example `192.0.2.4` as redis HA cluster ip. + It is out of scope howto deploy it. + +OR + +* Just for testing you can use one of the redis from the worker's. +e.g. If you plan only for testing on your first worker + * Configure redis configs/redis/redis.conf to not only bind to your loopback but also to your global ip address too: + + ``` plaintext + bind 192.0.2.1 + ``` + + And use `192.0.2.1` where we use in this example `192.0.2.4` + + * modify /etc/ferm/ferm.cfg or where ever your firewall config is to allow incoming redis + + ``` plaintext + chain INPUT { + policy DROP; + + saddr mm2.example.com proto tcp dport 6379 ACCEPT; + saddr mm3.example.com proto tcp dport 6379 ACCEPT; + } + ``` + +* **Use password or if you don't (like in this basic example) take care and use strict firewall rules** + +## Setup backends/workers + +### Setup App config + +mm/configs/app/config.js + +``` js +multipartyServer : 'meet.example.com', +``` + +### Setup Server config + +mm/configs/server/config.js + +``` js +redisOptions : { host: '10.0.2.4'}, +listeningPort: 80, +httpOnly: true, +trustProxy : ['10.0.2.5'], +``` + +## Deploy host with HA proxy + +* configure cerificate / letsencrypt for your meet.example.com + * in this example we put concat the privkey and full cert chain to /root/fullchain.pem. +* Install and setup haproxy + + `apt install haproxy` + +* Add to /etc/haproxy/haproxy.cfg config + +``` plaintext +backend multipartymeeting + balance url_param roomId + hash-type consistent + + server mm1 192.0.2.1:80 check maxconn 20 verify none + server mm2 192.0.2.2:80 check maxconn 20 verify none + server mm3 192.0.2.3:80 check maxconn 20 verify none + +frontend meet.example.com + bind 192.0.2.5:80 + bind 192.0.2.5:443 ssl crt /root/fullchain.pem + http-request redirect scheme https unless { ssl_fc } + reqadd X-Forwarded-Proto:\ https + default_backend multipartymeeting +``` From bac00a87e12711cdd8ba5f07e753b56c38d4b854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 21:27:21 +0200 Subject: [PATCH 013/158] Cleaning --- HAproxy.md | 82 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/HAproxy.md b/HAproxy.md index acc88e4..095a6bf 100644 --- a/HAproxy.md +++ b/HAproxy.md @@ -1,9 +1,9 @@ -# Howto deploy a (room based) loadbalanced cluster +# Howto deploy a (room based) load balanced cluster -We use in this example an HA proxy loadbalancer to loadbalance between 3 backend server -The static web will be loadbalanced rounrdobin, and the websocket(signaling) and the media will be loadbalnced based on roomId URL parameter. +This example will show how to setup an HA proxy to provide load balancing between several +multiparty-meeting servers. -## IP and dns +## IP and DNS In this basic example we use the following names and ips: @@ -17,40 +17,40 @@ In this basic example we use the following names and ips: * `redis.example.com` <=> `192.0.2.4` -### LoadBalncer HAproxy +### Load balancer HAproxy * `meet.example.com` <=> `192.0.2.5` -## Deploy multiple backend/worker +## Deploy multiple multiparty-meeting servers + +This is most easily done using Ansible (see below), but can be done +in any way you choose (manual, Docker, Ansible). -For example with ansible Read more here: [mm-ansible](https://github.com/misi/mm-ansible) [![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) -## Setup redis for central session store +## Setup Redis for central HTTP session store -### Use one redis for all multiparty meeting +### Use one Redis for all multiparty-meeting servers -* Deploy a redis cluster so use one redis HA cluster for all instances. - * We will use in our actual example `192.0.2.4` as redis HA cluster ip. - It is out of scope howto deploy it. +* Deploy a Redis cluster for all instances. + * We will use in our actual example `192.0.2.4` as redis HA cluster ip. It is out of scope howto deploy it. OR -* Just for testing you can use one of the redis from the worker's. -e.g. If you plan only for testing on your first worker - * Configure redis configs/redis/redis.conf to not only bind to your loopback but also to your global ip address too: +* For testing you can use Redis from one the multiparty-meeting servers. e.g. If you plan only for testing on your first multiparty-meeting server. + * Configure Redis `redis.conf` to not only bind to your loopback but also to your global ip address too: ``` plaintext bind 192.0.2.1 ``` - And use `192.0.2.1` where we use in this example `192.0.2.4` + This example sets this to `192.0.2.1`, change this according to your local installation. - * modify /etc/ferm/ferm.cfg or where ever your firewall config is to allow incoming redis + * Change your firewall config to allow incoming Redis. Example (depends on the type of firewall): ``` plaintext - chain INPUT { + chain INPUT { policy DROP; saddr mm2.example.com proto tcp dport 6379 ACCEPT; @@ -58,11 +58,11 @@ e.g. If you plan only for testing on your first worker } ``` -* **Use password or if you don't (like in this basic example) take care and use strict firewall rules** + * **Set a password, or if you don't (like in this basic example) take care to set strict firewall rules** -## Setup backends/workers +## Configure multiparty-meeting servers -### Setup App config +### App config mm/configs/app/config.js @@ -70,40 +70,40 @@ mm/configs/app/config.js multipartyServer : 'meet.example.com', ``` -### Setup Server config +### Server config mm/configs/server/config.js ``` js -redisOptions : { host: '10.0.2.4'}, +redisOptions : { host: '192.0.2.4'}, listeningPort: 80, httpOnly: true, -trustProxy : ['10.0.2.5'], +trustProxy : ['192.0.2.5'], ``` -## Deploy host with HA proxy +## Deploy HA proxy -* configure cerificate / letsencrypt for your meet.example.com - * in this example we put concat the privkey and full cert chain to /root/fullchain.pem. +* Configure cerificate / letsencrypt for `meet.example.com` + * In this example we put a complete chain and private key in /root/certificate.pem. * Install and setup haproxy `apt install haproxy` * Add to /etc/haproxy/haproxy.cfg config -``` plaintext -backend multipartymeeting - balance url_param roomId - hash-type consistent + ``` plaintext + backend multipartymeeting + balance url_param roomId + hash-type consistent - server mm1 192.0.2.1:80 check maxconn 20 verify none - server mm2 192.0.2.2:80 check maxconn 20 verify none - server mm3 192.0.2.3:80 check maxconn 20 verify none + server mm1 192.0.2.1:80 check maxconn 20 verify none + server mm2 192.0.2.2:80 check maxconn 20 verify none + server mm3 192.0.2.3:80 check maxconn 20 verify none -frontend meet.example.com - bind 192.0.2.5:80 - bind 192.0.2.5:443 ssl crt /root/fullchain.pem - http-request redirect scheme https unless { ssl_fc } - reqadd X-Forwarded-Proto:\ https - default_backend multipartymeeting -``` + frontend meet.example.com + bind 192.0.2.5:80 + bind 192.0.2.5:443 ssl crt /root/certificate.pem + http-request redirect scheme https unless { ssl_fc } + reqadd X-Forwarded-Proto:\ https + default_backend multipartymeeting + ``` From 7e7f93d484b551f3cc1314930357991d6e3d1966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 31 Mar 2020 00:26:35 +0200 Subject: [PATCH 014/158] Update changelog --- CHANGELOG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bda55dd..520346e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 3.2.1 + +* Fix: permananent top bar by default +* Fix: httpOnly mode https redirecet +* Add some extra checks for video stream and track +* Add Italian translation +* Add Chech translation +* Add new server option `trustPorxy` for loadbalancing http only use case. +* Add HAproxy loadbalnce example +* Add LTI LMS integration documentation +* Fix spacing of close button + ## 3.2 * Add munin plugin @@ -33,10 +45,10 @@ * Updated to mediasoup v3 * Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client) - - OpenID Connect discovery - - Auth code flow + * OpenID Connect discovery + * Auth code flow * Add spdy http2 support. - - Notice it does not supports node 11.x + * Notice it does not supports node 11.x * Updated to Material UI v4 ## 2.0 From 6f36b4f468044e9817835afde6022a4c2cd51075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 31 Mar 2020 22:33:03 +0200 Subject: [PATCH 015/158] Update CHANGELOG --- CHANGELOG.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 520346e..471b71f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,30 +3,30 @@ ## 3.2.1 * Fix: permananent top bar by default -* Fix: httpOnly mode https redirecet +* Fix: `httpOnly` mode https redirect * Add some extra checks for video stream and track * Add Italian translation -* Add Chech translation -* Add new server option `trustPorxy` for loadbalancing http only use case. -* Add HAproxy loadbalnce example +* Add Czech translation +* Add new server option `trustProxy` for load balancing http only use case +* Add HAproxy load balance example * Add LTI LMS integration documentation -* Fix spacing of close button +* Fix spacing of leave button +* Fix for sharing same file multiple times ## 3.2 * Add munin plugin -* Add muted=true search param to disble audio by deffault +* Add `muted=true` search param to disable audio by default * Modify webtorrent tracker * Add key shortcut `space` for audio mute * Add key shortcut `v` for video mute * Add user configurable LastN -* Add option to sticky top bar (sticky by default) -* update mediasoup server -* Add simulcast options to app config (disabled by default) -* Add stats option to get counts of rooms and peers -* Add httpOnly option for loadbalancer backend setups +* Add option to permananent top bar (permanent by default) +* Update mediasoup server +* Add `simulcast` options to app config (disabled by default) +* Add `stats` option to get counts of rooms and peers +* Add `httpOnly` option for loadbalancer backend setups * LTI integration for LMS systems like moodle -* Add muted=false search parameter * Add translations (12+1 languages) * Add support IPv6 * Many other fixes and refactorings From be8414212bd3c8d1ee0b8ee331e9daddee1b8f00 Mon Sep 17 00:00:00 2001 From: Roman Drozd Date: Mon, 30 Mar 2020 15:11:00 +0200 Subject: [PATCH 016/158] Fix spacing of close button Displayed incorrectly for some languages --- app/src/components/Controls/TopBar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 094af23..2cc380a 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -78,8 +78,8 @@ const styles = (theme) => }, actionButton : { - margin : theme.spacing(1), - padding : 0 + margin : theme.spacing(1, 0), + padding : theme.spacing(0, 1) } }); @@ -446,4 +446,4 @@ export default withRoomContext(connect( ); } } -)(withStyles(styles, { withTheme: true })(TopBar))); \ No newline at end of file +)(withStyles(styles, { withTheme: true })(TopBar))); From bbc8ec379683b27b5a5b59c69c977c251a9ecd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Davidovi=C4=87?= <62179680+sd4v1d@users.noreply.github.com> Date: Tue, 7 Apr 2020 15:34:26 +0200 Subject: [PATCH 017/158] Update Croatian translation development branch --- app/src/translations/hr.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 5f0d6a6..f2b00f2 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -6,7 +6,7 @@ "room.chooseRoom": "Izaberite ime sobe u koju se želite prijaviti", "room.cookieConsent": "Ova stranica koristi kolačiće radi poboljšanja korisničkog iskustva", - "room.consentUnderstand": "I understand", + "room.consentUnderstand": "Razumijem", "room.joined": "Prijavljeni ste u sobu", "room.cantJoin": "Prijava u sobu nije moguća", "room.youLocked": "Zaključali ste sobu", @@ -16,9 +16,9 @@ "room.locked": "Soba je sada zaključana", "room.unlocked": "Soba je sada otključana", "room.newLobbyPeer": "U predvorju je novi učesnik", - "room.lobbyPeerLeft": "Učesnik je napustio predvorje", - "room.lobbyPeerChangedDisplayName": "Učesnik u predvorju je promijenio ime u {displayName}", - "room.lobbyPeerChangedPicture": "Učesnik u predvorju je promijenio sliku", + "room.lobbyPeerLeft": "Sudionik je napustio predvorje", + "room.lobbyPeerChangedDisplayName": "Sudionik u predvorju je promijenio ime u {displayName}", + "room.lobbyPeerChangedPicture": "Sudionik u predvorju je promijenio sliku", "room.setAccessCode": "Obnovljena pristupna šifra za sobu", "room.accessCodeOn": "Pristupna šifra sobe je aktivna", "room.accessCodeOff":"Pristupna šifra sobe je neaktivna", @@ -42,19 +42,19 @@ "room.emptyRequireLogin": "Soba je trenutno prazna! Prijavite se za pokretanje sastanka, ili sačekajte organizatora" , "room.locketWait": "Soba je zaključana - pričekajte odobrenje ...", "room.lobbyAdministration":"Upravljanje predvorjem", - "room.peersInLobby":"Učesnici u predvorju", + "room.peersInLobby":"Sudionici u predvorju", "room.lobbyEmpty": "Trenutno nema nikoga u predvorju", "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", "room.me": "Ja", - "room.spotlights": "Učesnici u fokusu", + "room.spotlights": "Sudionici u fokusu", "room.passive": "Pasivni učesnici", "room.videoPaused": "Video pauziran", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, - "room.speechUnsupported": null, + "room.muteAll": "Utišaj sve", + "room.stopAllVideo": "Ugasi sve kamere", + "room.closeMeeting": "Završi sastanak", + "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", - "me.mutedPTT": null, + "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor ", "tooltip.login": "Prijava", "tooltip.logout": "Odjava", @@ -66,7 +66,7 @@ "tooltip.lobby": "Prikaži predvorje", "tooltip.settings": "Prikaži postavke", "tooltip.participants": "Pokažite sudionike", - "tooltip.kickParticipant": null, + "tooltip.kickParticipant": "Izbaci sudionika", "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", @@ -78,7 +78,7 @@ "label.chatInput":"Uđi u razgovor porukama", "label.chat": "Razgovor", "label.filesharing": "Dijeljenje datoteka", - "label.participants": "Učesnici", + "label.participants": "Sudionici", "label.shareFile": "Dijeli datoteku", "label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano", "label.unknown": "Nepoznato", From 226b0f629315cb3f022b65d0b4accd673cf60ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 7 Apr 2020 19:47:36 +0200 Subject: [PATCH 018/158] Change quality indicator for peers --- .../components/VideoContainers/VideoView.js | 136 +++++++++--------- 1 file changed, 66 insertions(+), 70 deletions(-) diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 3395e97..85506bf 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -3,6 +3,13 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import EditableInput from '../Controls/EditableInput'; +import { green, yellow, orange, red } from '@material-ui/core/colors'; +import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; +import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; +import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar'; +import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; +import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; +import SignalCellular4BarIcon from '@material-ui/icons/SignalCellular4Bar'; const styles = (theme) => ({ @@ -88,69 +95,6 @@ const styles = (theme) => 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 : { display : 'flex' @@ -229,6 +173,62 @@ class VideoView extends React.PureComponent videoHeight } = this.state; + let quality = ; + + if (videoScore || audioScore) + { + const score = videoScore ? videoScore : audioScore; + + switch (score.producerScore) + { + case 0: + case 1: + { + quality = ; + + break; + } + + case 2: + case 3: + { + quality = ; + + break; + } + + case 4: + case 5: + case 6: + { + quality = ; + + break; + } + + case 7: + case 8: + { + quality = ; + + break; + } + + case 9: + case 10: + { + quality = ; + + break; + } + + default: + { + break; + } + } + } + return (

@@ -254,15 +254,11 @@ class VideoView extends React.PureComponent

{videoWidth}x{videoHeight}

}
- { (audioScore || videoScore) && + { !isMe &&
-
}
From ee16bf809e31c035421d0b6e1536fd2196b97e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 7 Apr 2020 21:38:35 +0200 Subject: [PATCH 019/158] Quality indicator on screen sharing missing --- app/src/components/Containers/Peer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js index 758723d..910d67f 100644 --- a/app/src/components/Containers/Peer.js +++ b/app/src/components/Containers/Peer.js @@ -490,6 +490,7 @@ const Peer = (props) => videoTrack={screenConsumer && screenConsumer.track} videoVisible={screenVisible} videoCodec={screenConsumer && screenConsumer.codec} + videoScore={screenConsumer ? screenConsumer.score : null} />
From 1067823ede2955fe047fe00a228c656d1b3669b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 8 Apr 2020 09:31:44 +0200 Subject: [PATCH 020/158] Fixes: #188 handle if _webcamProducer is null --- app/src/RoomClient.js | 60 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 6797c28..747401b 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1135,14 +1135,41 @@ export default class RoomClient ...VIDEO_CONSTRAINS[resolution] } }); + + if(stream){ + const track = stream.getVideoTracks()[0]; - const track = stream.getVideoTracks()[0]; - - await this._webcamProducer.replaceTrack({ track }); - - store.dispatch( - producerActions.setProducerTrack(this._webcamProducer.id, track)); - + if (track) + { + if (this._webcamProducer) + { + await this._webcamProducer.replaceTrack({ track }); + } + else + { + this._webcamProducer = await this._sendTransport.produce({ + track, + appData : + { + source : 'webcam' + } + }); + } + + + store.dispatch( + producerActions.setProducerTrack(this._webcamProducer.id, track)); + } + else + { + logger.warn('getVideoTracks Error: First Video Track is null'); + } + + } + else + { + logger.warn('getUserMedia Error: Stream is null!'); + } store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId)); store.dispatch(settingsActions.setVideoResolution(resolution)); @@ -1195,11 +1222,24 @@ export default class RoomClient if (track) { - await this._webcamProducer.replaceTrack({ track }); - + if (this._webcamProducer) + { + await this._webcamProducer.replaceTrack({ track }); + } + else + { + this._webcamProducer = await this._sendTransport.produce({ + track, + appData : + { + source : 'webcam' + } + }); + } + store.dispatch( producerActions.setProducerTrack(this._webcamProducer.id, track)); - + } else { From 724e5b7e7517c59ca54efa81c8bf04a0bf51c6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 8 Apr 2020 16:20:47 +0200 Subject: [PATCH 021/158] Add wakelock for mobile --- app/package.json | 1 + app/src/components/Room.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/package.json b/app/package.json index 704419f..d68f4f3 100644 --- a/app/package.json +++ b/app/package.json @@ -30,6 +30,7 @@ "react-redux": "^7.1.1", "react-router-dom": "^5.1.2", "react-scripts": "3.0.1", + "react-wakelock-react16": "0.0.7", "redux": "^4.0.4", "redux-logger": "^3.0.6", "redux-persist": "^6.0.0", diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 3e0e1b6..733b0ad 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -23,6 +23,7 @@ import VideoWindow from './VideoWindow/VideoWindow'; import LockDialog from './AccessControl/LockDialog/LockDialog'; import Settings from './Settings/Settings'; import TopBar from './Controls/TopBar'; +import WakeLock from 'react-wakelock-react16'; const TIMEOUT = 5 * 1000; @@ -201,6 +202,7 @@ class Room extends React.PureComponent + From 403c52339a70e57597e37bbcf0e11f006a8c1ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 8 Apr 2020 23:41:51 +0200 Subject: [PATCH 022/158] Enable wakelock only on mobile --- app/src/components/Room.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 733b0ad..4cc40c2 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -139,6 +139,7 @@ class Room extends React.PureComponent { const { room, + isMobile, advancedMode, toolAreaOpen, toggleToolArea, @@ -202,7 +203,10 @@ class Room extends React.PureComponent - + + { isMobile && + + } @@ -221,6 +225,7 @@ class Room extends React.PureComponent Room.propTypes = { room : appPropTypes.Room.isRequired, + isMobile : PropTypes.bool.isRequired, advancedMode : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, @@ -232,6 +237,7 @@ Room.propTypes = const mapStateToProps = (state) => ({ room : state.room, + isMobile : state.me.isMobile, advancedMode : state.settings.advancedMode, toolAreaOpen : state.toolarea.toolAreaOpen }); @@ -257,6 +263,7 @@ export default connect( { return ( prev.room === next.room && + prev.me.isMobile === next.me.isMobile && prev.settings.advancedMode === next.settings.advancedMode && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); From 0077c459e2df66e499eb3320525fbed4a1e5f3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 9 Apr 2020 18:58:44 +0200 Subject: [PATCH 023/158] Fix if displayName is null --- app/src/components/Selectors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index 34bb647..a87ce16 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -82,7 +82,7 @@ export const spotlightSortedPeersSelector = createSelector( spotlightsSelector, peersValueSelector, (spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id)) - .sort((a, b) => a.displayName.localeCompare(b.displayName)) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) ); export const peersLengthSelector = createSelector( @@ -94,7 +94,7 @@ export const passivePeersSelector = createSelector( peersValueSelector, spotlightsSelector, (peers, spotlights) => peers.filter((peer) => !spotlights.includes(peer.id)) - .sort((a, b) => a.displayName.localeCompare(b.displayName)) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) ); export const videoBoxesSelector = createSelector( From 6267f808db176d41e2574f094c08026f659e66ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 9 Apr 2020 19:09:14 +0200 Subject: [PATCH 024/158] Lint --- app/src/RoomClient.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 747401b..1f47fd3 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1136,7 +1136,8 @@ export default class RoomClient } }); - if(stream){ + if (stream) + { const track = stream.getVideoTracks()[0]; if (track) @@ -1155,8 +1156,7 @@ export default class RoomClient } }); } - - + store.dispatch( producerActions.setProducerTrack(this._webcamProducer.id, track)); } From 11441ca8ca6f9a048c39f85c6c68c54558325eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Davidovi=C4=87?= <62179680+sd4v1d@users.noreply.github.com> Date: Thu, 9 Apr 2020 21:59:44 +0200 Subject: [PATCH 025/158] Croatian translation update Terminology and syntax update in development branch --- app/src/translations/hr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index f2b00f2..120b1a5 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -15,7 +15,7 @@ "room.cantUnLock": "Otključavanje sobe nije moguće", "room.locked": "Soba je sada zaključana", "room.unlocked": "Soba je sada otključana", - "room.newLobbyPeer": "U predvorju je novi učesnik", + "room.newLobbyPeer": "Novi sudionik čeka u predvorju", "room.lobbyPeerLeft": "Sudionik je napustio predvorje", "room.lobbyPeerChangedDisplayName": "Sudionik u predvorju je promijenio ime u {displayName}", "room.lobbyPeerChangedPicture": "Sudionik u predvorju je promijenio sliku", @@ -40,21 +40,21 @@ "room.audioVideo": "Zvuk i slika", "room.youAreReady": "Spremni ste", "room.emptyRequireLogin": "Soba je trenutno prazna! Prijavite se za pokretanje sastanka, ili sačekajte organizatora" , - "room.locketWait": "Soba je zaključana - pričekajte odobrenje ...", + "room.locketWait": "Soba je zaključana - pričekajte odobrenje...", "room.lobbyAdministration":"Upravljanje predvorjem", "room.peersInLobby":"Sudionici u predvorju", "room.lobbyEmpty": "Trenutno nema nikoga u predvorju", "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", "room.me": "Ja", "room.spotlights": "Sudionici u fokusu", - "room.passive": "Pasivni učesnici", + "room.passive": "Pasivni sudionici", "room.videoPaused": "Video pauziran", "room.muteAll": "Utišaj sve", "room.stopAllVideo": "Ugasi sve kamere", "room.closeMeeting": "Završi sastanak", "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", - "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor ", + "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", "tooltip.login": "Prijava", "tooltip.logout": "Odjava", From 3cf0f9d3e7138aec2c6a199903ceb428186cc885 Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Thu, 9 Apr 2020 17:33:47 +0200 Subject: [PATCH 026/158] Fix typos --- HAproxy.md | 2 +- LTI/LTI.md | 4 ++-- README.md | 6 +++--- app/src/RoomClient.js | 22 +++++++++++----------- app/src/reducers/lobbyPeers.js | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/HAproxy.md b/HAproxy.md index 095a6bf..b738097 100644 --- a/HAproxy.md +++ b/HAproxy.md @@ -83,7 +83,7 @@ trustProxy : ['192.0.2.5'], ## Deploy HA proxy -* Configure cerificate / letsencrypt for `meet.example.com` +* Configure certificate / letsencrypt for `meet.example.com` * In this example we put a complete chain and private key in /root/certificate.pem. * Install and setup haproxy diff --git a/LTI/LTI.md b/LTI/LTI.md index ff6b9ec..15b6e6e 100644 --- a/LTI/LTI.md +++ b/LTI/LTI.md @@ -2,7 +2,7 @@ ## LTI -Read more about IMS Global defined interface for tools like our VideoConference system integration with Learning Managment Systems(LMS) (e.g. moodle). +Read more about IMS Global defined interface for tools like our VideoConference system integration with Learning Management Systems(LMS) (e.g. moodle). See: [IMS Global Learning Tool Interoperability](https://www.imsglobal.org/activity/learning-tools-interoperability) We implemented LTI interface version 1.0/1.1 @@ -57,5 +57,5 @@ Open fully the settings **Click on show more!!** ## moodle plugin -Alternativly you can use multipartymeeting moodle plugin: +Alternatively you can use multipartymeeting moodle plugin: [https://github.com/misi/moodle-mod_multipartymeeting](https://github.com/misi/moodle-mod_multipartymeeting) diff --git a/README.md b/README.md index 0a47de2..9f9aa0a 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ $ npm install $ cd server $ npm start ``` -* Note: Do not run the server as root. If you need to use port 80/443 make a iptables-mapping for that or use systemd configuration for that (see futher down this doc). +* Note: Do not run the server as root. If you need to use port 80/443 make a iptables-mapping for that or use systemd configuration for that (see further down this doc). * Test your service in a webRTC enabled browser: `https://yourDomainOrIPAdress:3443/roomname` ## Deploy it in a server @@ -102,7 +102,7 @@ $ systemctl enable multiparty-meeting ## Ports and firewall * 3443/tcp (default https webserver and signaling - adjustable in `server/config.js`) -* 4443/tcp (default `npm start` port for developing with live browser reload, not needed in production enviroments - adjustable in app/package.json) +* 4443/tcp (default `npm start` port for developing with live browser reload, not needed in production environments - adjustable in app/package.json) * 40000-49999/udp/tcp (media ports - adjustable in `server/config.js`) ## Load balanced installation @@ -113,7 +113,7 @@ To integrate with an LMS (e.g. Moodle), have a look at [LTI](LTI/LTI.md). ## TURN configuration -* You need an addtional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js` +* You need an additional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js` ## Authors diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 1f47fd3..ad3bc5f 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -151,7 +151,7 @@ export default class RoomClient // Whether we should produce. this._produce = produce; - // Wheter we force TCP + // Whether we force TCP this._forceTcp = forceTcp; // Use displayName @@ -282,7 +282,7 @@ export default class RoomClient _startKeyListener() { - // Add keypress event listner on document + // Add keypress event listener on document document.addEventListener('keydown', (event) => { if (event.repeat) return; @@ -626,7 +626,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'room.changeDisplayNameError', - defaultMessage : 'An error occured while changing your display name' + defaultMessage : 'An error occurred while changing your display name' }) })); } @@ -1065,7 +1065,7 @@ export default class RoomClient { // The exact formula to convert from dBs (-100..0) to linear (0..1) is: // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate + // However it does not produce a visually useful output, so let exaggerate // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to // minimize component renderings. let volume = Math.round(Math.pow(10, dBs / 85) * 10); @@ -1840,7 +1840,7 @@ export default class RoomClient { // The exact formula to convert from dBs (-100..0) to linear (0..1) is: // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate + // However it does not produce a visually useful output, so let exaggerate // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to // minimize component renderings. let volume = Math.round(Math.pow(10, dBs / 85) * 10); @@ -2628,7 +2628,7 @@ export default class RoomClient this.updateSpotlights(spotlights); }); - // Don't produce if explicitely requested to not to do it. + // Don't produce if explicitly requested to not to do it. if (this._produce) { if (this._mediasoupDevice.canProduce('audio')) @@ -2645,7 +2645,7 @@ export default class RoomClient store.dispatch(roomActions.setRoomState('connected')); - // Clean all the existing notifcations. + // Clean all the existing notifications. store.dispatch(notificationActions.removeAllNotifications()); this.getServerHistory(); @@ -2912,7 +2912,7 @@ export default class RoomClient { // The exact formula to convert from dBs (-100..0) to linear (0..1) is: // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate + // However it does not produce a visually useful output, so let exaggerate // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to // minimize component renderings. let volume = Math.round(Math.pow(10, dBs / 85) * 10); @@ -2947,7 +2947,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.microphoneError', - defaultMessage : 'An error occured while accessing your microphone' + defaultMessage : 'An error occurred while accessing your microphone' }) })); @@ -3114,7 +3114,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.screenSharingError', - defaultMessage : 'An error occured while accessing your screen' + defaultMessage : 'An error occurred while accessing your screen' }) })); @@ -3287,7 +3287,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.cameraError', - defaultMessage : 'An error occured while accessing your camera' + defaultMessage : 'An error occurred while accessing your camera' }) })); diff --git a/app/src/reducers/lobbyPeers.js b/app/src/reducers/lobbyPeers.js index fef9190..4cf9cc1 100644 --- a/app/src/reducers/lobbyPeers.js +++ b/app/src/reducers/lobbyPeers.js @@ -44,7 +44,7 @@ const lobbyPeers = (state = {}, action) => if (!oldLobbyPeer) { - // Tried to update non-existant lobbyPeer. Has probably been promoted, or left. + // Tried to update non-existent lobbyPeer. Has probably been promoted, or left. return state; } From 36fab6c57237eb2cf9f4e7b3663967f075207ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Mon, 13 Apr 2020 22:47:09 +0200 Subject: [PATCH 027/158] set in enable AV device constraint to ideal instead of exact --- 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 ad3bc5f..a05a5c7 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2834,7 +2834,7 @@ export default class RoomClient const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { exact: deviceId } + deviceId : { ideal: deviceId } } } ); @@ -3192,7 +3192,7 @@ export default class RoomClient { video : { - deviceId : { exact: deviceId }, + deviceId : { ideal: deviceId }, ...VIDEO_CONSTRAINS[resolution] } }); From 4a085945322259bf40d3bd2794e9fa8237a11486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 14 Apr 2020 13:44:28 +0200 Subject: [PATCH 028/158] move tokenset_claims to userinfo --- server/server.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/server.js b/server/server.js index 70cb5bc..e174c86 100755 --- a/server/server.js +++ b/server/server.js @@ -255,12 +255,15 @@ function setupOIDC(oidcIssuer) { client: oidcClient, params, passReqToCallback, usePKCE }, (tokenset, userinfo, done) => { + if (userinfo && tokenset) { + userinfo._tokenset_claims = tokenset.claims(); + } + const user = { id : tokenset.claims.sub, provider : tokenset.claims.iss, - _userinfo : userinfo, - _claims : tokenset.claims + _userinfo : userinfo }; return done(null, user); From 14ed627ca31d9d3e1f984b3aacfe2834e53dc3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 14 Apr 2020 13:45:24 +0200 Subject: [PATCH 029/158] move lti claims to userinfo --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index e174c86..9b1b0a3 100755 --- a/server/server.js +++ b/server/server.js @@ -199,7 +199,7 @@ function setupLTI(ltiConfig) if (lti.user_id && lti.custom_room) { user.id = lti.user_id; - user._lti = lti; + user._userinfo = { "lti" : lti }; } if (lti.custom_room) From f9e3f9b62295179ba6dbcfcf7c6106659e554cea Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Tue, 14 Apr 2020 20:57:09 +0200 Subject: [PATCH 030/158] Fix typos --- app/src/RoomClient.js | 4 ++++ app/src/__tests__/RoomClient.spec.js | 2 +- munin/mm-plugin | 2 +- server/config/config.example.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index a05a5c7..5d816c6 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -283,7 +283,11 @@ export default class RoomClient _startKeyListener() { // Add keypress event listener on document +<<<<<<< HEAD document.addEventListener('keydown', (event) => +======= + document.addEventListener('keypress', (event) => +>>>>>>> 250679c... Fix typos { if (event.repeat) return; const key = String.fromCharCode(event.which); diff --git a/app/src/__tests__/RoomClient.spec.js b/app/src/__tests__/RoomClient.spec.js index 086e6a5..5e1191c 100644 --- a/app/src/__tests__/RoomClient.spec.js +++ b/app/src/__tests__/RoomClient.spec.js @@ -1,6 +1,6 @@ import RoomClient from '../RoomClient'; -describe('new RoomClient() without paramaters throws Error', () => +describe('new RoomClient() without parameters throws Error', () => { test('Matches the snapshot', () => { diff --git a/munin/mm-plugin b/munin/mm-plugin index d319ad1..978995b 100755 --- a/munin/mm-plugin +++ b/munin/mm-plugin @@ -42,7 +42,7 @@ fi if [ "$1" = "config" ]; then echo 'graph_title MM stats' #echo 'graph_args --base 1000 -l 0' - echo 'graph_vlabel Actual Seesion Count' + echo 'graph_vlabel Actual Session Count' echo 'graph_category other' echo 'graph_info This graph shows the mm stats.' echo 'rooms.label rooms' diff --git a/server/config/config.example.js b/server/config/config.example.js index ada1553..72403d7 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -59,7 +59,7 @@ module.exports = key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` }, // listening Host or IP - // If ommitted listens on every IP. ("0.0.0.0" and "::") + // If omitted listens on every IP. ("0.0.0.0" and "::") //listeningHost: 'localhost', // Listening port for https server. listeningPort : 443, From 8ac9bf1d9cd13a57660efbf92dc6c857e50468fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 15 Apr 2020 11:02:41 +0200 Subject: [PATCH 031/158] Fix comment --- app/src/RoomClient.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 5d816c6..8d0e01f 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -282,12 +282,8 @@ export default class RoomClient _startKeyListener() { - // Add keypress event listener on document -<<<<<<< HEAD + // Add keydown event listener on document document.addEventListener('keydown', (event) => -======= - document.addEventListener('keypress', (event) => ->>>>>>> 250679c... Fix typos { if (event.repeat) return; const key = String.fromCharCode(event.which); From 5a9fc063bf70c00d88b60cb39b5f70faac7fa495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 15 Apr 2020 08:18:03 +0200 Subject: [PATCH 032/158] Move params to config --- server/config/config.example.js | 7 +++++++ server/lib/Room.js | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 72403d7..32ca836 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -38,6 +38,13 @@ module.exports = // URI and key for requesting geoip-based TURN server closest to the client turnAPIKey : 'examplekey', turnAPIURI : 'https://example.com/api/turn', + turnAPIparams : { + 'uri_schema' : 'turn', + 'transport' : 'tcp', + 'ip_ver' : 'ipv4', + 'servercount' : '2' + }, + // Backup turnservers if REST fails or is not configured backupTurnServers : [ { diff --git a/server/lib/Room.js b/server/lib/Room.js index f75ec29..b436671 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -383,10 +383,7 @@ class Room extends EventEmitter config.turnAPIURI, { params : { - 'uri_schema' : 'turn', - 'transport' : 'tcp', - 'ip_ver' : 'ipv4', - 'servercount' : '2', + ...config.turnAPIparams, 'api_key' : config.turnAPIKey, 'ip' : peer.socket.request.connection.remoteAddress } From da429cf1b50732b9d227e9e9d15f41987ca2dd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Thu, 16 Apr 2020 22:32:34 +0200 Subject: [PATCH 033/158] Lowercase room name in url path --- app/src/components/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/components/App.js b/app/src/components/App.js index 0b6b473..6d98847 100644 --- a/app/src/components/App.js +++ b/app/src/components/App.js @@ -14,7 +14,7 @@ const App = (props) => room } = props; - const { id } = useParams(); + const id = useParams().id.toLowerCase(); useEffect(() => { From f254440600151631fa7ab91bd61f524c46cdaeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 17 Apr 2020 10:12:01 +0200 Subject: [PATCH 034/158] Update node version to 13.x --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f9aa0a..de7fb96 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ If you want the ansible approach, you can find ansible role [here](https://githu ## Manual installation * Prerequisites: -Currently multiparty-meeting will only run on nodejs v10.* +Currently multiparty-meeting will only run on nodejs v13.x To install see here [here](https://github.com/nodesource/distributions/blob/master/README.md#debinstall). ```bash From bb9c07de3bad1f220e93b0e1548c34b6d24af0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 17 Apr 2020 13:34:17 +0200 Subject: [PATCH 035/158] Update 3.4.1 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index d68f4f3..fc6c575 100644 --- a/app/package.json +++ b/app/package.json @@ -29,7 +29,7 @@ "react-intl": "^3.4.0", "react-redux": "^7.1.1", "react-router-dom": "^5.1.2", - "react-scripts": "3.0.1", + "react-scripts": "3.4.1", "react-wakelock-react16": "0.0.7", "redux": "^4.0.4", "redux-logger": "^3.0.6", From e98d80ed57ec3e71f3789cd4ed3bfd0a109e4c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Sat, 18 Apr 2020 23:20:58 +0200 Subject: [PATCH 036/158] Add a not yet complete audio out selection. --- app/src/RoomClient.js | 79 +++++++++++++++++++++++++ app/src/__tests__/Room.spec.js | 13 ++-- app/src/actions/meActions.js | 12 ++++ app/src/actions/settingsActions.js | 6 ++ app/src/components/Settings/Settings.js | 47 +++++++++++++++ app/src/reducers/me.js | 7 +++ app/src/reducers/settings.js | 5 ++ 7 files changed, 164 insertions(+), 5 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8d0e01f..219cb10 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -244,6 +244,8 @@ export default class RoomClient this._audioDevices = {}; + this._audioOutputDevices = {}; + // mediasoup Consumers. // @type {Map} this._consumers = new Map(); @@ -1107,6 +1109,50 @@ export default class RoomClient meActions.setAudioInProgress(false)); } + async changeAudioOutputDevice(deviceId) + { + logger.debug('changeAudioOutputDevice() [deviceId: %s]', deviceId); + + store.dispatch( + meActions.setAudioOutputInProgress(true)); + + try + { + const device = this._audioOutputDevices[deviceId]; + + if (!device) + throw new Error('Selected audio output device no longer avaibale'); + + logger.debug( + 'changeAudioOutputDevice() | new selected [audio output device:%o]', + device); + + + + const audioElements = document.getElementById("audio"); + for( let i=0; i me : { audioDevices : null, audioInProgress : false, + audioOutputDevices : null, + audioOutputInProgress : false, canSendMic : false, canSendWebcam : false, canShareFiles : false, @@ -72,11 +74,12 @@ beforeEach(() => windowConsumer : null }, settings : { - advancedMode : true, - displayName : 'Jest Tester', - resolution : 'ultra', - selectedAudioDevice : 'default', - selectedWebcam : 'soifjsiajosjfoi' + advancedMode : true, + displayName : 'Jest Tester', + resolution : 'ultra', + selectedAudioDevice : 'default', + selectedAudioOutputDevice : 'default', + selectedWebcam : 'soifjsiajosjfoi' }, toolarea : { currentToolTab : 'chat', diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index fc72592..9c350da 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -50,6 +50,12 @@ export const setAudioDevices = (devices) => payload : { devices } }); +export const setAudioOutputDevices = (devices) => +({ + type : 'SET_AUDIO_OUTPUT_DEVICES', + payload : { devices } +}); + export const setWebcamDevices = (devices) => ({ type : 'SET_WEBCAM_DEVICES', @@ -67,6 +73,12 @@ export const setAudioInProgress = (flag) => type : 'SET_AUDIO_IN_PROGRESS', payload : { flag } }); + +export const setAudioOutputInProgress = (flag) => +({ + type : 'SET_AUDIO_OUTPUT_IN_PROGRESS', + payload : { flag } +}); export const setWebcamInProgress = (flag) => ({ diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 79b5ef2..e66d437 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -4,6 +4,12 @@ export const setSelectedAudioDevice = (deviceId) => payload : { deviceId } }); +export const setSelectedAudioOutputDevice = (deviceId) => +({ + type : 'CHANGE_AUDIO_OUTPUT_DEVICE', + payload : { deviceId } +}); + export const setSelectedWebcamDevice = (deviceId) => ({ type : 'CHANGE_WEBCAM', diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 91ba0db..4ad8c0c 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -130,6 +130,13 @@ const Settings = ({ audioDevices = Object.values(me.audioDevices); else audioDevices = []; + + let audioOutputDevices; + + if (me.audioOutputDevices) + audioOutputDevices = Object.values(me.audioOutputDevices); + else + audioOutputDevices = []; return ( +
+ + + + { audioOutputDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudio.output', + defaultMessage : 'Select audio output device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudio.output', + defaultMessage : 'Unable to select audio output device' + }) + } + + +
+ ({ + root : + { + padding : theme.spacing(1), + width : '100%', + overflow : 'hidden', + cursor : 'auto', + display : 'flex', + listStyleType : 'none', + boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)', + backgroundColor : 'rgba(255, 255, 255, 1)' + }, + listheader : + { + padding : theme.spacing(1), + fontWeight : 'bolder' + }, + actionButton : + { + marginLeft : 'auto' + } + }); + +const FileSharingModerator = (props) => +{ + const intl = useIntl(); + + const { + roomClient, + isFileSharingModerator, + room, + classes + } = props; + + if (!isFileSharingModerator) + return; + + return ( +
    +
  • + +
  • + +
+ ); +}; + +FileSharingModerator.propTypes = +{ + roomClient : PropTypes.any.isRequired, + isFileSharingModerator : PropTypes.bool, + room : PropTypes.object, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + isFileSharingModerator : + state.me.roles.some((role) => + state.room.permissionsFromRoles.MODERATE_FILES.includes(role)), + room : state.room + }); + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room && + prev.me === next.me + ); + } + } +)(withStyles(styles)(FileSharingModerator))); \ No newline at end of file diff --git a/app/src/reducers/files.js b/app/src/reducers/files.js index 2475cff..7caf2f0 100644 --- a/app/src/reducers/files.js +++ b/app/src/reducers/files.js @@ -85,6 +85,9 @@ const files = (state = {}, action) => return { ...state, [magnetUri]: newFile }; } + case 'CLEAR_FILES': + return {}; + default: return state; } diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index cd702f2..6340b40 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -1,36 +1,38 @@ const initialState = { - name : '', - state : 'new', // new/connecting/connected/disconnected/closed, - locked : false, - inLobby : false, - signInRequired : false, - accessCode : '', // access code to the room if locked and joinByAccessCode == true - joinByAccessCode : true, // if true: accessCode is a possibility to open the room - activeSpeakerId : null, - torrentSupport : false, - showSettings : false, - fullScreenConsumer : null, // ConsumerID - windowConsumer : null, // ConsumerID - toolbarsVisible : true, - mode : 'democratic', - selectedPeerId : null, - spotlights : [], - settingsOpen : false, - lockDialogOpen : false, - joined : false, - muteAllInProgress : false, - stopAllVideoInProgress : false, - closeMeetingInProgress : false, - clearChatInProgress : false, - userRoles : { NORMAL: 'normal' }, // Default role - permissionsFromRoles : { + name : '', + state : 'new', // new/connecting/connected/disconnected/closed, + locked : false, + inLobby : false, + signInRequired : false, + accessCode : '', // access code to the room if locked and joinByAccessCode == true + joinByAccessCode : true, // if true: accessCode is a possibility to open the room + activeSpeakerId : null, + torrentSupport : false, + showSettings : false, + fullScreenConsumer : null, // ConsumerID + windowConsumer : null, // ConsumerID + toolbarsVisible : true, + mode : 'democratic', + selectedPeerId : null, + spotlights : [], + settingsOpen : false, + lockDialogOpen : false, + joined : false, + muteAllInProgress : false, + stopAllVideoInProgress : false, + closeMeetingInProgress : false, + clearChatInProgress : false, + clearFileSharingInProgress : false, + userRoles : { NORMAL: 'normal' }, // Default role + permissionsFromRoles : { CHANGE_ROOM_LOCK : [], PROMOTE_PEER : [], SEND_CHAT : [], MODERATE_CHAT : [], SHARE_SCREEN : [], SHARE_FILE : [], + MODERATE_FILES : [], MODERATE_ROOM : [] } }; @@ -189,6 +191,9 @@ const room = (state = initialState, action) => case 'CLEAR_CHAT_IN_PROGRESS': return { ...state, clearChatInProgress: action.payload.flag }; + case 'CLEAR_FILE_SHARING_IN_PROGRESS': + return { ...state, clearFileSharingInProgress: action.payload.flag }; + case 'SET_USER_ROLES': { const { userRoles } = action.payload; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 6f24a6a..81794f2 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "访问相机时发生错误", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } \ No newline at end of file diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 401eb2d..f1ddf62 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -52,6 +52,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -142,6 +143,7 @@ "devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/de.json b/app/src/translations/de.json index ca61408..945f942 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -53,6 +53,7 @@ "room.stopAllVideo": "Alle Videos stoppen", "room.closeMeeting": "Meeting schließen", "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", @@ -147,6 +148,7 @@ "devices.cameraError": "Fehler mit deiner Kamera", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 839c94c..d3cd47e 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 054853c..1957ac6 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } \ No newline at end of file diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 463a1d0..4e1d1d5 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -53,6 +53,7 @@ "room.stopAllVideo": "Stop all video", "room.closeMeeting": "Close meeting", "room.clearChat": "Clear chat", + "room.clearFileSharing": "Clear files", "room.speechUnsupported": "Your browser does not support speech recognition", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", @@ -147,6 +148,7 @@ "devices.cameraError": "An error occured while accessing your camera", "moderator.clearChat": "Moderator cleared the chat", + "moderator.clearFiles": "Moderator cleared the files", "moderator.muteAudio": "Moderator muted your audio", "moderator.muteVideo": "Moderator muted your video" } \ No newline at end of file diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 3170d3b..3391600 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "Hubo un error al acceder a su cámara", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 10e9566..bf93610 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -146,6 +147,7 @@ "devices.cameraError": "Une erreur est apparue lors de l'accès à votre caméra", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index efc47f6..e3c1713 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -53,6 +53,7 @@ "room.stopAllVideo": "Ugasi sve kamere", "room.closeMeeting": "Završi sastanak", "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", @@ -147,6 +148,7 @@ "devices.cameraError": "Greška prilikom pristupa kameri", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 7f7f334..f6625be 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "Hiba történt a kamera elérése során", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 7cb7d99..b253ef9 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -146,6 +147,7 @@ "devices.cameraError": "Errore con l'accesso alla videocamera", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 49398aa..20b9668 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -53,6 +53,7 @@ "room.stopAllVideo": "Stopp all video", "room.closeMeeting": "Avslutt møte", "room.clearChat": "Tøm chat", + "room.clearFileSharing": "Fjern filer", "room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", @@ -147,6 +148,7 @@ "devices.cameraError": "Det skjedde noe feil med kameraet ditt", "moderator.clearChat": "Moderator tømte chatten", + "moderator.clearFiles": "Moderator fjernet filer", "moderator.muteAudio": "Moderator mutet lyden din", "moderator.muteVideo": "Moderator mutet videoen din" } \ No newline at end of file diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 7cb17f2..cd13f2d 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } \ No newline at end of file diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 8d3848d..a5edc8a 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "Ocorreu um erro no acesso à sua câmara", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 0439cdf..337d7fb 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "me.mutedPTT": null, @@ -147,6 +148,7 @@ "devices.cameraError": "A apărut o eroare la accesarea camerei video", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index 961aef5..67d6495 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -53,6 +53,7 @@ "room.stopAllVideo": null, "room.closeMeeting": null, "room.clearChat": null, + "room.clearFileSharing": null, "room.speechUnsupported": null, "tooltip.login": "Увійти", @@ -144,6 +145,7 @@ "devices.cameraError": "Під час доступу до камери сталася помилка", "moderator.clearChat": null, + "moderator.clearFiles": null, "moderator.muteAudio": null, "moderator.muteVideo": null } \ No newline at end of file diff --git a/server/config/config.example.js b/server/config/config.example.js index 6b030e1..22f3dcb 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -235,6 +235,8 @@ module.exports = SHARE_SCREEN : [ userRoles.NORMAL ], // The role(s) have permission to share files SHARE_FILE : [ userRoles.NORMAL ], + // The role(s) have permission to moderate files + MODERATE_FILES : [ userRoles.MODERATOR ], // The role(s) have permission to moderate room (e.g. kick user) MODERATE_ROOM : [ userRoles.MODERATOR ] }, diff --git a/server/lib/Room.js b/server/lib/Room.js index 8bbedf1..acb2a10 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1178,6 +1178,24 @@ class Room extends EventEmitter break; } + case 'moderator:clearFileSharing': + { + if ( + !peer.roles.some((role) => config.permissionsFromRoles.MODERATE_FILES.includes(role)) + ) + throw new Error('peer not authorized'); + + this._fileHistory = []; + + // Spread to others + this._notification(peer.socket, 'moderator:clearFileSharing', null, true); + + // Return no error + cb(); + + break; + } + case 'raiseHand': { const { raisedHand } = request.data; From 1cef52a3a146410bd41fed1cfa1f41e529be14e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 22 Apr 2020 10:01:58 +0200 Subject: [PATCH 053/158] Properly handle wakelock, fixes #237 --- app/src/RoomClient.js | 8 ++++---- app/src/actions/meActions.js | 5 +++-- app/src/components/Containers/Me.js | 2 +- app/src/components/Containers/Peer.js | 12 ++++++------ app/src/components/Room.js | 10 +++++----- app/src/deviceInfo.js | 8 +++++--- app/src/reducers/me.js | 8 +++++--- 7 files changed, 29 insertions(+), 24 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index d138131..dcebc75 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -199,6 +199,9 @@ export default class RoomClient // @type {mediasoupClient.Device} this._mediasoupDevice = null; + // Put the browser info into state + store.dispatch(meActions.setBrowser(device)); + // Our WebTorrent client this._webTorrent = null; @@ -206,13 +209,10 @@ export default class RoomClient store.dispatch(settingsActions.setVideoResolution(defaultResolution)); // Max spotlights - if (device.bowser.getPlatformType() === 'desktop') + if (device.platform === 'desktop') this._maxSpotlights = lastN; else - { this._maxSpotlights = mobileLastN; - store.dispatch(meActions.setIsMobile()); - } store.dispatch( settingsActions.setLastN(this._maxSpotlights)); diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index fc72592..ec9f00d 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -4,9 +4,10 @@ export const setMe = ({ peerId, loginEnabled }) => payload : { peerId, loginEnabled } }); -export const setIsMobile = () => +export const setBrowser = (browser) => ({ - type : 'SET_IS_MOBILE' + type : 'SET_BROWSER', + payload : { browser } }); export const loggedIn = (flag) => diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 6dc368f..b79c5d0 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -385,7 +385,7 @@ const Me = (props) => - { !me.isMobile && + { me.browser.platform !== 'mobile' &&
advancedMode, peer, activeSpeaker, - isMobile, + browser, micConsumer, webcamConsumer, screenConsumer, @@ -260,7 +260,7 @@ const Peer = (props) =>
- { !isMobile && + { browser.platform !== 'mobile' && }, 2000); }} > - { !isMobile && + { browser.platform !== 'mobile' && ...getPeerConsumers(state, id), windowConsumer : state.room.windowConsumer, activeSpeaker : id === state.room.activeSpeakerId, - isMobile : state.me.isMobile + browser : state.me.browser }; }; @@ -565,7 +565,7 @@ export default withRoomContext(connect( prev.consumers === next.consumers && prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.windowConsumer === next.room.windowConsumer && - prev.me.isMobile === next.me.isMobile + prev.me.browser === next.me.browser ); } } diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 4cc40c2..fbd4cc5 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -139,7 +139,7 @@ class Room extends React.PureComponent { const { room, - isMobile, + browser, advancedMode, toolAreaOpen, toggleToolArea, @@ -204,7 +204,7 @@ class Room extends React.PureComponent - { isMobile && + { browser.platform === 'mobile' && browser.os !== 'ios' && } @@ -225,7 +225,7 @@ class Room extends React.PureComponent Room.propTypes = { room : appPropTypes.Room.isRequired, - isMobile : PropTypes.bool.isRequired, + browser : PropTypes.object.isRequired, advancedMode : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, @@ -237,7 +237,7 @@ Room.propTypes = const mapStateToProps = (state) => ({ room : state.room, - isMobile : state.me.isMobile, + browser : state.me.browser, advancedMode : state.settings.advancedMode, toolAreaOpen : state.toolarea.toolAreaOpen }); @@ -263,7 +263,7 @@ export default connect( { return ( prev.room === next.room && - prev.me.isMobile === next.me.isMobile && + prev.me.browser === next.me.browser && prev.settings.advancedMode === next.settings.advancedMode && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); diff --git a/app/src/deviceInfo.js b/app/src/deviceInfo.js index 3e5d361..deef6f5 100644 --- a/app/src/deviceInfo.js +++ b/app/src/deviceInfo.js @@ -24,8 +24,10 @@ export default function() return { flag, - name : browser.getBrowserName(), - version : browser.getBrowserVersion(), - bowser : browser + os : browser.getOSName(true), // ios, android, linux... + platform : browser.getPlatformType(true), // mobile, desktop, tablet + name : browser.getBrowserName(), + version : browser.getBrowserVersion(), + bowser : browser }; } diff --git a/app/src/reducers/me.js b/app/src/reducers/me.js index 9ed18cd..7d60b4d 100644 --- a/app/src/reducers/me.js +++ b/app/src/reducers/me.js @@ -2,7 +2,7 @@ const initialState = { id : null, picture : null, - isMobile : false, + browser : null, roles : [ 'normal' ], // Default role canSendMic : false, canSendWebcam : false, @@ -39,9 +39,11 @@ const me = (state = initialState, action) => }; } - case 'SET_IS_MOBILE': + case 'SET_BROWSER': { - return { ...state, isMobile: true }; + const { browser } = action.payload; + + return { ...state, browser }; } case 'LOGGED_IN': From 66b513cf1936736948849af554e20ea39aa1b100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 22 Apr 2020 10:21:49 +0200 Subject: [PATCH 054/158] Cleanup --- app/src/RoomClient.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 7d1d20d..3712009 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1126,19 +1126,16 @@ export default class RoomClient logger.debug( 'changeAudioOutputDevice() | new selected [audio output device:%o]', device); - - - const audioElements = document.getElementsByTagName("audio"); - for( let i=0; i Date: Wed, 22 Apr 2020 10:26:58 +0200 Subject: [PATCH 055/158] Various lint and fixes --- app/src/__tests__/Room.spec.js | 2 +- app/src/actions/meActions.js | 16 ++++++++-------- app/src/actions/settingsActions.js | 8 ++++---- app/src/components/PeerAudio/AudioPeers.js | 5 +++-- app/src/components/PeerAudio/PeerAudio.js | 10 +++++----- app/src/components/Settings/Settings.js | 7 ++++++- app/src/reducers/settings.js | 6 +++--- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/__tests__/Room.spec.js b/app/src/__tests__/Room.spec.js index f62e62d..3c802d3 100644 --- a/app/src/__tests__/Room.spec.js +++ b/app/src/__tests__/Room.spec.js @@ -32,7 +32,7 @@ beforeEach(() => audioDevices : null, audioInProgress : false, audioOutputDevices : null, - audioOutputInProgress : false, + audioOutputInProgress : false, canSendMic : false, canSendWebcam : false, canShareFiles : false, diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index 9c350da..32c429d 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -51,10 +51,10 @@ export const setAudioDevices = (devices) => }); export const setAudioOutputDevices = (devices) => -({ - type : 'SET_AUDIO_OUTPUT_DEVICES', - payload : { devices } -}); + ({ + type : 'SET_AUDIO_OUTPUT_DEVICES', + payload : { devices } + }); export const setWebcamDevices = (devices) => ({ @@ -75,10 +75,10 @@ export const setAudioInProgress = (flag) => }); export const setAudioOutputInProgress = (flag) => -({ - type : 'SET_AUDIO_OUTPUT_IN_PROGRESS', - payload : { flag } -}); + ({ + type : 'SET_AUDIO_OUTPUT_IN_PROGRESS', + payload : { flag } + }); export const setWebcamInProgress = (flag) => ({ diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index e66d437..9603eaf 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -5,10 +5,10 @@ export const setSelectedAudioDevice = (deviceId) => }); export const setSelectedAudioOutputDevice = (deviceId) => -({ - type : 'CHANGE_AUDIO_OUTPUT_DEVICE', - payload : { deviceId } -}); + ({ + type : 'CHANGE_AUDIO_OUTPUT_DEVICE', + payload : { deviceId } + }); export const setSelectedWebcamDevice = (deviceId) => ({ diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index 56d2a88..8109ac4 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -32,12 +32,13 @@ const AudioPeers = (props) => AudioPeers.propTypes = { - micConsumers : PropTypes.array + micConsumers : PropTypes.array, + audioOutputDevice : PropTypes.string }; const mapStateToProps = (state) => ({ - micConsumers : micConsumerSelector(state), + micConsumers : micConsumerSelector(state), audioOutputDevice : settings.selectedAudioOutputDevice }); diff --git a/app/src/components/PeerAudio/PeerAudio.js b/app/src/components/PeerAudio/PeerAudio.js index accf1fe..d5ef982 100644 --- a/app/src/components/PeerAudio/PeerAudio.js +++ b/app/src/components/PeerAudio/PeerAudio.js @@ -63,8 +63,9 @@ export default class PeerAudio extends React.PureComponent } } - _setOutputDevice(audioOutputDevice){ - if(this._audioOutputDevice === audioOutputDevice) + _setOutputDevice(audioOutputDevice) + { + if (this._audioOutputDevice === audioOutputDevice) return; this._audioOutputDevice = audioOutputDevice; @@ -72,13 +73,12 @@ export default class PeerAudio extends React.PureComponent const { audio } = this.refs; if (audioOutputDevice && typeof audio.setSinkId === 'function') - { audio.setSinkId(audioOutputDevice); - } } } PeerAudio.propTypes = { - audioTrack : PropTypes.any + audioTrack : PropTypes.any, + audioOutputDevice : PropTypes.string }; diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 2a9719d..c5fa353 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -254,7 +254,12 @@ const Settings = ({ { audioOutputDevices.map((audioOutput, index) => { return ( - {audioOutput.label} + + {audioOutput.label} + ); })} diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 590ac8f..0be91a8 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -24,9 +24,9 @@ const settings = (state = initialState, action) => } case 'CHANGE_AUDIO_OUTPUT_DEVICE': - { - return { ...state, selectedAudioOutputDevice: action.payload.deviceId }; - } + { + return { ...state, selectedAudioOutputDevice: action.payload.deviceId }; + } case 'SET_DISPLAY_NAME': { From 37d5fdeefc547ccf10d29565e4c9a7d61357a200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 22 Apr 2020 10:30:29 +0200 Subject: [PATCH 056/158] Proper state handling --- app/src/components/PeerAudio/AudioPeers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index 8109ac4..2b0a951 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { micConsumerSelector } from '../Selectors'; import PropTypes from 'prop-types'; import PeerAudio from './PeerAudio'; -import settings from '../../reducers/settings'; const AudioPeers = (props) => { @@ -21,7 +20,7 @@ const AudioPeers = (props) => ); }) @@ -39,7 +38,7 @@ AudioPeers.propTypes = const mapStateToProps = (state) => ({ micConsumers : micConsumerSelector(state), - audioOutputDevice : settings.selectedAudioOutputDevice + audioOutputDevice : state.settings.selectedAudioOutputDevice }); const AudioPeersContainer = connect( @@ -50,7 +49,8 @@ const AudioPeersContainer = connect( areStatesEqual : (next, prev) => { return ( - prev.consumers === next.consumers + prev.consumers === next.consumers && + prev.settings === next.settings ); } } From cdf899a948358f7b31d6623fb4074564f2622400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 22 Apr 2020 10:41:12 +0200 Subject: [PATCH 057/158] We don't need to change sink manualy. State, and React handles that for us. --- app/src/RoomClient.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 3712009..bc075b0 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1127,16 +1127,6 @@ export default class RoomClient 'changeAudioOutputDevice() | new selected [audio output device:%o]', device); - const audioElements = document.getElementsByTagName('audio'); - - if (typeof audioElements[0].setSinkId === 'function') - { - for (let i = 0; i < audioElements.length; i++) - await audioElements[i].setSinkId(deviceId); - } - else - logger.debug('changeAudioOutputDevice() | setSinkId not implemented'); - store.dispatch(settingsActions.setSelectedAudioOutputDevice(deviceId)); await this._updateAudioOutputDevices(); From 1ea330059ebe02c55555818c5e2293359d2b6ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 22 Apr 2020 12:26:35 +0200 Subject: [PATCH 058/158] Change browser name to lowercase --- app/src/deviceInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/deviceInfo.js b/app/src/deviceInfo.js index deef6f5..6acdedf 100644 --- a/app/src/deviceInfo.js +++ b/app/src/deviceInfo.js @@ -26,7 +26,7 @@ export default function() flag, os : browser.getOSName(true), // ios, android, linux... platform : browser.getPlatformType(true), // mobile, desktop, tablet - name : browser.getBrowserName(), + name : browser.getBrowserName(true), version : browser.getBrowserVersion(), bowser : browser }; From 58b4aaf6c1d31814b9970c76bf2b92d1cb6a1f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 22 Apr 2020 12:29:29 +0200 Subject: [PATCH 059/158] Compare audioOutputDevice states --- app/src/components/PeerAudio/AudioPeers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index 2b0a951..386990f 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -50,6 +50,7 @@ const AudioPeersContainer = connect( { return ( prev.consumers === next.consumers && + prev.audioOutputDevice === next.audioOutputDevice && prev.settings === next.settings ); } From 3236a18a0bdc8c42deb60bd341433358c86f33b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Wed, 22 Apr 2020 12:33:37 +0200 Subject: [PATCH 060/158] Limit audio output selection to chrome - whitelist --- app/src/components/Settings/Settings.js | 98 +++++++++++++------------ 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index c5fa353..ad1d245 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -58,6 +58,7 @@ const Settings = ({ room, me, settings, + browser, onToggleAdvancedMode, onTogglePermanentTopBar, handleCloseSettings, @@ -233,51 +234,53 @@ const Settings = ({
-
- - - - { audioOutputDevices.length > 0 ? - intl.formatMessage({ - id : 'settings.selectAudioOutput', - defaultMessage : 'Select audio output device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectAudioOutput', - defaultMessage : 'Unable to select audio output device' - }) - } - - -
+ { browser.name === 'chrome' && +
+ + + + { audioOutputDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudioOutput', + defaultMessage : 'Select audio output device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudioOutput', + defaultMessage : 'Unable to select audio output device' + }) + } + + +
+ }
me : state.me, room : state.room, settings : state.settings, - browser : state.me.browser, + browser : state.me.browser }; }; From 3caa505d08b64e4cfb0f3692d7354611fd83d6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Thu, 23 Apr 2020 21:52:33 +0200 Subject: [PATCH 064/158] Fix state mistake --- app/src/components/PeerAudio/AudioPeers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index 386990f..d02f26d 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -50,8 +50,7 @@ const AudioPeersContainer = connect( { return ( prev.consumers === next.consumers && - prev.audioOutputDevice === next.audioOutputDevice && - prev.settings === next.settings + prev.settings.selectedAudioOutputDevice === next.settings.selectedAudioOutputDevice ); } } From d06f44d1d48883354e8da7eefc2a316c75cff9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Thu, 23 Apr 2020 21:56:27 +0200 Subject: [PATCH 065/158] Remove redundant browser state, and tidy --- app/src/components/Settings/Settings.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 1945ef2..5a4a09a 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -58,7 +58,6 @@ const Settings = ({ room, me, settings, - browser, onToggleAdvancedMode, onTogglePermanentTopBar, handleCloseSettings, @@ -235,8 +234,8 @@ const Settings = ({
{ - window.config.audioOutputSupportedBrowsers && - window.config.audioOutputSupportedBrowsers.includes(browser.name) && + 'audioOutputSupportedBrowsers' in window.config && + window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
- { - if (event.target.value) - roomClient.changeMaxSpotlights(event.target.value); - }} - name='Last N' - autoWidth - className={classes.selectEmpty} - > - { [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].map((lastN) => - { - return ( - - {lastN} - - ); - })} - - - - - -
+ { window.config && !window.config.lockLastN && +
+ + + + + + +
+ } } From 2ece8e9975933c7f4a8db2a7291b01018c4c9b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 30 Apr 2020 12:48:36 +0200 Subject: [PATCH 076/158] Make layout configurable in client, fixes #227 --- app/public/config/config.example.js | 15 ++++++++------- app/src/reducers/room.js | 11 +++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 371d399..cf2703d 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -42,17 +42,18 @@ var config = { tcp : true }, - lastN : 4, - mobileLastN : 1, + defaultLayout : 'democratic', // democratic, filmstrip + lastN : 4, + mobileLastN : 1, // Highest number of speakers user can select - maxLastN : 5, + maxLastN : 5, // If truthy, users can NOT change number of speakers visible - lockLastN : false, - background : 'images/background.jpg', + lockLastN : false, + background : 'images/background.jpg', // Add file and uncomment for adding logo to appbar // logo : 'images/logo.svg', - title : 'Multiparty meeting', - theme : + title : 'Multiparty meeting', + theme : { palette : { diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 6340b40..54c4011 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -1,19 +1,22 @@ const initialState = { name : '', - state : 'new', // new/connecting/connected/disconnected/closed, + // new/connecting/connected/disconnected/closed, + state : 'new', locked : false, inLobby : false, signInRequired : false, - accessCode : '', // access code to the room if locked and joinByAccessCode == true - joinByAccessCode : true, // if true: accessCode is a possibility to open the room + // access code to the room if locked and joinByAccessCode == true + accessCode : '', + // if true: accessCode is a possibility to open the room + joinByAccessCode : true, activeSpeakerId : null, torrentSupport : false, showSettings : false, fullScreenConsumer : null, // ConsumerID windowConsumer : null, // ConsumerID toolbarsVisible : true, - mode : 'democratic', + mode : window.config.defaultLayout || 'democratic', selectedPeerId : null, spotlights : [], settingsOpen : false, From 1ffa4fdc9a38b6ea235f02ae8b4a0a257bddeea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 30 Apr 2020 12:50:23 +0200 Subject: [PATCH 077/158] Better handling of default resolution. --- app/src/RoomClient.js | 14 ++------------ app/src/reducers/settings.js | 3 ++- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8e5c365..f499871 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -31,8 +31,7 @@ let Spotlights; let requestTimeout, transportOptions, lastN, - mobileLastN, - defaultResolution; + mobileLastN; if (process.env.NODE_ENV !== 'test') { @@ -40,8 +39,7 @@ if (process.env.NODE_ENV !== 'test') requestTimeout, transportOptions, lastN, - mobileLastN, - defaultResolution + mobileLastN } = window.config); } @@ -205,9 +203,6 @@ export default class RoomClient // Our WebTorrent client this._webTorrent = null; - if (defaultResolution) - store.dispatch(settingsActions.setVideoResolution(defaultResolution)); - // Max spotlights if (device.platform === 'desktop') this._maxSpotlights = lastN; @@ -534,11 +529,6 @@ export default class RoomClient } } - notify(text) - { - store.dispatch(requestActions.notify({ text: text })); - } - timeoutCallback(callback) { let called = false; diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 0be91a8..2b2325b 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -4,7 +4,8 @@ const initialState = selectedWebcam : null, selectedAudioDevice : null, advancedMode : false, - resolution : 'medium', // low, medium, high, veryhigh, ultra + // low, medium, high, veryhigh, ultra + resolution : window.config.defaultResolution || 'medium', lastN : 4, permanentTopBar : true }; From 2fced42c425abfa1394b5e008f23943194e0d6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 30 Apr 2020 12:50:43 +0200 Subject: [PATCH 078/158] Cleanups. --- app/src/components/ChooseRoom.js | 4 ++-- app/src/components/Controls/TopBar.js | 4 ++-- app/src/components/JoinDialog.js | 6 +++--- app/src/components/Settings/Settings.js | 5 ++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/components/ChooseRoom.js b/app/src/components/ChooseRoom.js index b81ff47..3d549b3 100644 --- a/app/src/components/ChooseRoom.js +++ b/app/src/components/ChooseRoom.js @@ -86,7 +86,7 @@ const DialogTitle = withStyles(styles)((props) => return ( - { window.config && window.config.logo && Logo } + { window.config.logo && Logo } {children} ); @@ -125,7 +125,7 @@ const ChooseRoom = ({ }} > - { window.config && window.config.title ? window.config.title : 'Multiparty meeting' } + { window.config.title ? window.config.title : 'Multiparty meeting' }
diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 2cc380a..23cddec 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -194,14 +194,14 @@ const TopBar = (props) => - { window.config && window.config.logo && Logo } + { window.config.logo && Logo } - { window.config && window.config.title ? window.config.title : 'Multiparty meeting' } + { window.config.title ? window.config.title : 'Multiparty meeting' }
diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 85262b1..6b1d7ad 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -128,9 +128,9 @@ const DialogTitle = withStyles(styles)((props) => return ( - { window.config && window.config.logo && Logo } + { window.config.logo && Logo } {children} - { window.config && window.config.loginEnabled && + { window.config.loginEnabled && - { window.config && window.config.title ? window.config.title : 'Multiparty meeting' } + { window.config.title ? window.config.title : 'Multiparty meeting' }
diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index af46eb2..064aa2f 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -233,8 +233,7 @@ const Settings = ({ - { - 'audioOutputSupportedBrowsers' in window.config && + { 'audioOutputSupportedBrowsers' in window.config && window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
@@ -355,7 +354,7 @@ const Settings = ({ /> { settings.advancedMode && - { window.config && !window.config.lockLastN && + { !window.config.lockLastN && + { + if (event.target.value) + roomClient.changeMaxSpotlights(event.target.value); + }} + name='Last N' + autoWidth + className={classes.selectEmpty} + > + { Array.from( + { length: window.config.maxLastN || 10 }, + (_, i) => i + 1 + ).map((lastN) => + { + return ( + + {lastN} + + ); + })} + + + + + + + } + + ); +}; + +AdvancedSettings.propTypes = +{ + roomClient : PropTypes.any.isRequired, + settings : PropTypes.object.isRequired, + onToggleAdvancedMode : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + settings : state.settings + }); + +const mapDispatchToProps = { + onToggleAdvancedMode : settingsActions.toggleAdvancedMode +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(AdvancedSettings))); \ No newline at end of file diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js new file mode 100644 index 0000000..71f250a --- /dev/null +++ b/app/src/components/Settings/AppearenceSettings.js @@ -0,0 +1,132 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import * as appPropTypes from '../appPropTypes'; +import { withStyles } from '@material-ui/core/styles'; +import * as roomActions from '../../actions/roomActions'; +import * as settingsActions from '../../actions/settingsActions'; +import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import FormControl from '@material-ui/core/FormControl'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; + +const styles = (theme) => + ({ + setting : + { + padding : theme.spacing(2) + }, + formControl : + { + display : 'flex' + } + }); + +const AppearenceSettings = ({ + room, + settings, + onTogglePermanentTopBar, + handleChangeMode, + classes +}) => +{ + const intl = useIntl(); + + const modes = [ { + value : 'democratic', + label : intl.formatMessage({ + id : 'label.democratic', + defaultMessage : 'Democratic view' + }) + }, { + value : 'filmstrip', + label : intl.formatMessage({ + id : 'label.filmstrip', + defaultMessage : 'Filmstrip view' + }) + } ]; + + return ( + +
+ + + + + + +
+ } + label={intl.formatMessage({ + id : 'settings.permanentTopBar', + defaultMessage : 'Permanent top bar' + })} + /> +
+ ); +}; + +AppearenceSettings.propTypes = +{ + room : appPropTypes.Room.isRequired, + settings : PropTypes.object.isRequired, + onTogglePermanentTopBar : PropTypes.func.isRequired, + handleChangeMode : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + room : state.room, + settings : state.settings + }); + +const mapDispatchToProps = { + onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, + handleChangeMode : roomActions.setDisplayMode +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room && + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(AppearenceSettings)); \ No newline at end of file diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js new file mode 100644 index 0000000..fa9728b --- /dev/null +++ b/app/src/components/Settings/MediaSettings.js @@ -0,0 +1,284 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import * as appPropTypes from '../appPropTypes'; +import { withStyles } from '@material-ui/core/styles'; +import { withRoomContext } from '../../RoomContext'; +import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; + +const styles = (theme) => + ({ + setting : + { + padding : theme.spacing(2) + }, + formControl : + { + display : 'flex' + } + }); + +const MediaSettings = ({ + roomClient, + me, + settings, + classes +}) => +{ + const intl = useIntl(); + + const resolutions = [ { + value : 'low', + label : intl.formatMessage({ + id : 'label.low', + defaultMessage : 'Low' + }) + }, + { + value : 'medium', + label : intl.formatMessage({ + id : 'label.medium', + defaultMessage : 'Medium' + }) + }, + { + value : 'high', + label : intl.formatMessage({ + id : 'label.high', + defaultMessage : 'High (HD)' + }) + }, + { + value : 'veryhigh', + label : intl.formatMessage({ + id : 'label.veryHigh', + defaultMessage : 'Very high (FHD)' + }) + }, + { + value : 'ultra', + label : intl.formatMessage({ + id : 'label.ultra', + defaultMessage : 'Ultra (UHD)' + }) + } ]; + + let webcams; + + if (me.webcamDevices) + webcams = Object.values(me.webcamDevices); + else + webcams = []; + + let audioDevices; + + if (me.audioDevices) + audioDevices = Object.values(me.audioDevices); + else + audioDevices = []; + + let audioOutputDevices; + + if (me.audioOutputDevices) + audioOutputDevices = Object.values(me.audioOutputDevices); + else + audioOutputDevices = []; + + return ( + +
+ + + + { webcams.length > 0 ? + intl.formatMessage({ + id : 'settings.selectCamera', + defaultMessage : 'Select video device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectCamera', + defaultMessage : 'Unable to select video device' + }) + } + + +
+
+ + + + { audioDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudio', + defaultMessage : 'Select audio device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudio', + defaultMessage : 'Unable to select audio device' + }) + } + + +
+ { 'audioOutputSupportedBrowsers' in window.config && + window.config.audioOutputSupportedBrowsers.includes(me.browser.name) && +
+ + + + { audioOutputDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudioOutput', + defaultMessage : 'Select audio output device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudioOutput', + defaultMessage : 'Unable to select audio output device' + }) + } + + +
+ } +
+ + + + + + +
+
+ ); +}; + +MediaSettings.propTypes = +{ + roomClient : PropTypes.any.isRequired, + me : appPropTypes.Me.isRequired, + settings : PropTypes.object.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => +{ + return { + me : state.me, + settings : state.settings + }; +}; + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.me === next.me && + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(MediaSettings))); \ No newline at end of file diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 064aa2f..6633829 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -1,22 +1,25 @@ import React from 'react'; import { connect } from 'react-redux'; -import * as appPropTypes from '../appPropTypes'; import { withStyles } from '@material-ui/core/styles'; -import { withRoomContext } from '../../RoomContext'; import * as roomActions from '../../actions/roomActions'; -import * as settingsActions from '../../actions/settingsActions'; import PropTypes from 'prop-types'; import { useIntl, FormattedMessage } from 'react-intl'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; +import MediaSettings from './MediaSettings'; +import AppearenceSettings from './AppearenceSettings'; +import AdvancedSettings from './AdvancedSettings'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogActions from '@material-ui/core/DialogActions'; import Button from '@material-ui/core/Button'; -import MenuItem from '@material-ui/core/MenuItem'; -import FormHelperText from '@material-ui/core/FormHelperText'; -import FormControl from '@material-ui/core/FormControl'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import Select from '@material-ui/core/Select'; -import Checkbox from '@material-ui/core/Checkbox'; + +const tabs = +[ + 'media', + 'appearence', + 'advanced' +]; const styles = (theme) => ({ @@ -43,106 +46,27 @@ const styles = (theme) => width : '90vw' } }, - setting : + tabsHeader : { - padding : theme.spacing(2) - }, - formControl : - { - display : 'flex' + flexGrow : 1 } }); const Settings = ({ - roomClient, - room, - me, - settings, - onToggleAdvancedMode, - onTogglePermanentTopBar, + currentSettingsTab, + settingsOpen, handleCloseSettings, - handleChangeMode, + setSettingsTab, classes }) => { const intl = useIntl(); - const modes = [ { - value : 'democratic', - label : intl.formatMessage({ - id : 'label.democratic', - defaultMessage : 'Democratic view' - }) - }, { - value : 'filmstrip', - label : intl.formatMessage({ - id : 'label.filmstrip', - defaultMessage : 'Filmstrip view' - }) - } ]; - - const resolutions = [ { - value : 'low', - label : intl.formatMessage({ - id : 'label.low', - defaultMessage : 'Low' - }) - }, - { - value : 'medium', - label : intl.formatMessage({ - id : 'label.medium', - defaultMessage : 'Medium' - }) - }, - { - value : 'high', - label : intl.formatMessage({ - id : 'label.high', - defaultMessage : 'High (HD)' - }) - }, - { - value : 'veryhigh', - label : intl.formatMessage({ - id : 'label.veryHigh', - defaultMessage : 'Very high (FHD)' - }) - }, - { - value : 'ultra', - label : intl.formatMessage({ - id : 'label.ultra', - defaultMessage : 'Ultra (UHD)' - }) - } ]; - - let webcams; - - if (me.webcamDevices) - webcams = Object.values(me.webcamDevices); - else - webcams = []; - - let audioDevices; - - if (me.audioDevices) - audioDevices = Object.values(me.audioDevices); - else - audioDevices = []; - - let audioOutputDevices; - - if (me.audioOutputDevices) - audioOutputDevices = Object.values(me.audioOutputDevices); - else - audioOutputDevices = []; - return ( handleCloseSettings({ settingsOpen: false })} + open={settingsOpen} + onClose={() => handleCloseSettings(false)} classes={{ paper : classes.dialogPaper }} @@ -153,254 +77,40 @@ const Settings = ({ defaultMessage='Settings' /> -
- - - - { webcams.length > 0 ? - intl.formatMessage({ - id : 'settings.selectCamera', - defaultMessage : 'Select video device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectCamera', - defaultMessage : 'Unable to select video device' - }) - } - - -
-
- - - - { audioDevices.length > 0 ? - intl.formatMessage({ - id : 'settings.selectAudio', - defaultMessage : 'Select audio device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectAudio', - defaultMessage : 'Unable to select audio device' - }) - } - - -
- { 'audioOutputSupportedBrowsers' in window.config && - window.config.audioOutputSupportedBrowsers.includes(me.browser.name) && -
- - - - { audioOutputDevices.length > 0 ? - intl.formatMessage({ - id : 'settings.selectAudioOutput', - defaultMessage : 'Select audio output device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectAudioOutput', - defaultMessage : 'Unable to select audio output device' - }) - } - - -
- } -
- - - - - - -
-
- - - - - - -
- } - label={intl.formatMessage({ - id : 'settings.advancedMode', - defaultMessage : 'Advanced mode' - })} - /> - { settings.advancedMode && - - { !window.config.lockLastN && -
- - - - - - -
+ setSettingsTab(tabs[value])} + indicatorColor='primary' + textColor='primary' + variant='fullWidth' + > + } - label={intl.formatMessage({ - id : 'settings.permanentTopBar', - defaultMessage : 'Permanent top bar' - })} - /> -
- } + /> + + + + {currentSettingsTab === 'media' && } + {currentSettingsTab === 'appearence' && } + {currentSettingsTab === 'advanced' && } - - - -
- */} { lobbyPeers.length > 0 ? } - + +
+ ); +}; + +ExtraVideo.propTypes = +{ + roomClient : PropTypes.object.isRequired, + extraVideoOpen : PropTypes.bool.isRequired, + webcamDevices : PropTypes.object, + handleCloseExtraVideo : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + webcamDevices : state.me.webcamDevices, + extraVideoOpen : state.room.extraVideoOpen + }); + +const mapDispatchToProps = { + handleCloseExtraVideo : roomActions.setExtraVideoOpen +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.me.webcamDevices === next.me.webcamDevices && + prev.room.extraVideoOpen === next.room.extraVideoOpen + ); + } + } +)(withStyles(styles)(ExtraVideo))); \ No newline at end of file diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 89bf385..64e56d5 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { @@ -14,11 +14,14 @@ import * as toolareaActions from '../../actions/toolareaActions'; import { useIntl, FormattedMessage } from 'react-intl'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; +import MenuItem from '@material-ui/core/MenuItem'; +import Menu from '@material-ui/core/Menu'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import Avatar from '@material-ui/core/Avatar'; import Badge from '@material-ui/core/Badge'; +import ExtensionIcon from '@material-ui/icons/Extension'; import AccountCircle from '@material-ui/icons/AccountCircle'; import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenExitIcon from '@material-ui/icons/FullscreenExit'; @@ -27,6 +30,7 @@ import SecurityIcon from '@material-ui/icons/Security'; import PeopleIcon from '@material-ui/icons/People'; import LockIcon from '@material-ui/icons/Lock'; import LockOpenIcon from '@material-ui/icons/LockOpen'; +import VideoCallIcon from '@material-ui/icons/VideoCall'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; @@ -89,6 +93,10 @@ const styles = (theme) => green : { color : 'rgba(0, 153, 0, 1)' + }, + moreAction : + { + margin : theme.spacing(0, 0, 0, 1) } }); @@ -127,6 +135,18 @@ const TopBar = (props) => { const intl = useIntl(); + const [ moreActionsElement, setMoreActionsElement ] = useState(null); + + const handleMoreActionsOpen = (event) => + { + setMoreActionsElement(event.currentTarget); + }; + + const handleMoreActionsClose = () => + { + setMoreActionsElement(null); + }; + const { roomClient, room, @@ -140,6 +160,7 @@ const TopBar = (props) => fullscreen, onFullscreen, setSettingsOpen, + setExtraVideoOpen, setLockDialogOpen, toggleToolArea, openUsersTab, @@ -149,6 +170,8 @@ const TopBar = (props) => classes } = props; + const isMoreActionsMenuOpen = Boolean(moreActionsElement); + const lockTooltip = room.locked ? intl.formatMessage({ id : 'tooltip.unLockRoom', @@ -183,196 +206,230 @@ const TopBar = (props) => }); return ( - - - toggleToolArea()} - > - + + + toggleToolArea()} + > + + + + + { window.config.logo && Logo } + - - - - { window.config.logo && Logo } - - { window.config.title ? window.config.title : 'Multiparty meeting' } - -
-
- { fullscreenEnabled && - - - { fullscreen ? - - : - - } - - - } - + { window.config.title ? window.config.title : 'Multiparty meeting' } + +
+
openUsersTab()} > - - - + - - - setSettingsOpen(!room.settingsOpen)} - > - - - - - - - { - if (room.locked) - { - roomClient.unlockRoom(); - } - else - { - roomClient.lockRoom(); - } - }} - > - { room.locked ? - - : - - } - - - - { lobbyPeers.length > 0 && - - + { fullscreenEnabled && + setLockDialogOpen(!room.lockDialogOpen)} + onClick={onFullscreen} > - - - + { fullscreen ? + + : + + } - - - } - { loginEnabled && - + + } + openUsersTab()} + > + + + + + + + - { - loggedIn ? roomClient.logout() : roomClient.login(); - }} + onClick={() => setSettingsOpen(!room.settingsOpen)} > - { myPicture ? - - : - - } + - } -
- +
+ + + + + { + handleMoreActionsClose(); + setExtraVideoOpen(!room.extraVideoOpen); + }} + > + roomClient.close()} - > - - -
- - + /> +

Add video

+ + + ); }; @@ -391,6 +448,7 @@ TopBar.propTypes = onFullscreen : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired, setSettingsOpen : PropTypes.func.isRequired, + setExtraVideoOpen : PropTypes.func.isRequired, setLockDialogOpen : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, openUsersTab : PropTypes.func.isRequired, @@ -430,6 +488,10 @@ const mapDispatchToProps = (dispatch) => { dispatch(roomActions.setSettingsOpen(settingsOpen)); }, + setExtraVideoOpen : (extraVideoOpen) => + { + dispatch(roomActions.setExtraVideoOpen(extraVideoOpen)); + }, setLockDialogOpen : (lockDialogOpen) => { dispatch(roomActions.setLockDialogOpen(lockDialogOpen)); diff --git a/app/src/components/Room.js b/app/src/components/Room.js index fbd4cc5..cdda66f 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -24,6 +24,7 @@ import LockDialog from './AccessControl/LockDialog/LockDialog'; import Settings from './Settings/Settings'; import TopBar from './Controls/TopBar'; import WakeLock from 'react-wakelock-react16'; +import ExtraVideo from './Controls/ExtraVideo'; const TIMEOUT = 5 * 1000; @@ -217,6 +218,10 @@ class Room extends React.PureComponent { room.settingsOpen && } + + { room.extraVideoOpen && + + }
); } diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index a33c171..fd22aff 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -37,6 +37,11 @@ export const screenProducersSelector = createSelector( (producers) => Object.values(producers).filter((producer) => producer.source === 'screen') ); +export const extraVideoProducersSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).filter((producer) => producer.source === 'extravideo') +); + export const micProducerSelector = createSelector( producersSelect, (producers) => Object.values(producers).find((producer) => producer.source === 'mic') @@ -67,6 +72,24 @@ export const screenConsumerSelector = createSelector( (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') ); +export const spotlightScreenConsumerSelector = createSelector( + spotlightsSelector, + consumersSelect, + (spotlights, consumers) => + Object.values(consumers).filter( + (consumer) => consumer.source === 'screen' && spotlights.includes(consumer.peerId) + ) +); + +export const spotlightExtraVideoConsumerSelector = createSelector( + spotlightsSelector, + consumersSelect, + (spotlights, consumers) => + Object.values(consumers).filter( + (consumer) => consumer.source === 'extravideo' && spotlights.includes(consumer.peerId) + ) +); + export const passiveMicConsumerSelector = createSelector( spotlightsSelector, consumersSelect, @@ -114,21 +137,33 @@ export const raisedHandsSelector = createSelector( export const videoBoxesSelector = createSelector( spotlightsLengthSelector, screenProducersSelector, - screenConsumerSelector, - (spotlightsLength, screenProducers, screenConsumers) => - spotlightsLength + 1 + screenProducers.length + screenConsumers.length + spotlightScreenConsumerSelector, + extraVideoProducersSelector, + spotlightExtraVideoConsumerSelector, + ( + spotlightsLength, + screenProducers, + screenConsumers, + extraVideoProducers, + extraVideoConsumers + ) => + spotlightsLength + 1 + screenProducers.length + + screenConsumers.length + extraVideoProducers.length + + extraVideoConsumers.length ); export const meProducersSelector = createSelector( micProducerSelector, webcamProducerSelector, screenProducerSelector, - (micProducer, webcamProducer, screenProducer) => + extraVideoProducersSelector, + (micProducer, webcamProducer, screenProducer, extraVideoProducers) => { return { micProducer, webcamProducer, - screenProducer + screenProducer, + extraVideoProducers }; } ); @@ -151,8 +186,10 @@ export const makePeerConsumerSelector = () => consumersArray.find((consumer) => consumer.source === 'webcam'); const screenConsumer = consumersArray.find((consumer) => consumer.source === 'screen'); + const extraVideoConsumers = + consumersArray.filter((consumer) => consumer.source === 'extravideo'); - return { micConsumer, webcamConsumer, screenConsumer }; + return { micConsumer, webcamConsumer, screenConsumer, extraVideoConsumers }; } ); }; diff --git a/app/src/components/appPropTypes.js b/app/src/components/appPropTypes.js index 0969c7b..0ae4fc2 100644 --- a/app/src/components/appPropTypes.js +++ b/app/src/components/appPropTypes.js @@ -18,9 +18,9 @@ export const Me = PropTypes.shape( export const Producer = PropTypes.shape( { id : PropTypes.string.isRequired, - source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired, + source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired, deviceLabel : PropTypes.string, - type : PropTypes.oneOf([ 'front', 'back', 'screen' ]), + type : PropTypes.oneOf([ 'front', 'back', 'screen', 'extravideo' ]), paused : PropTypes.bool.isRequired, track : PropTypes.any, codec : PropTypes.string.isRequired @@ -37,7 +37,7 @@ export const Consumer = PropTypes.shape( { id : PropTypes.string.isRequired, peerId : PropTypes.string.isRequired, - source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired, + source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired, locallyPaused : PropTypes.bool.isRequired, remotelyPaused : PropTypes.bool.isRequired, profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]), diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 08fa664..35c1e12 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -20,6 +20,7 @@ const initialState = selectedPeerId : null, spotlights : [], settingsOpen : false, + extraVideoOpen : false, currentSettingsTab : 'media', // media, appearence, advanced lockDialogOpen : false, joined : false, @@ -114,6 +115,13 @@ const room = (state = initialState, action) => return { ...state, settingsOpen }; } + case 'SET_EXTRA_VIDEO_OPEN': + { + const { extraVideoOpen } = action.payload; + + return { ...state, extraVideoOpen }; + } + case 'SET_SETTINGS_TAB': { const { tab } = action.payload; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 1dd2f75..77de748 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "设置", "settings.camera": "视频设备", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 7cf5ddd..a6a9af4 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -57,6 +57,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -103,6 +104,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Nastavení", "settings.camera": "Kamera", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 5e69940..9820bcd 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Einstellungen", "settings.camera": "Kamera", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 096f14b..163b51d 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Indstillinger", "settings.camera": "Kamera", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index c5de505..6b6cdab 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Ρυθμίσεις", "settings.camera": "Κάμερα", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 88bb3be..38414b7 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -58,6 +58,7 @@ "room.moderatoractions": "Moderator actions", "room.raisedHand": "{displayName} raised their hand", "room.loweredHand": "{displayName} put their hand down", + "room.extraVideo": "Extra video", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", @@ -104,6 +105,7 @@ "label.media": "Media", "label.appearence": "Appearence", "label.advanced": "Advanced", + "label.addVideo": "Add video", "settings.settings": "Settings", "settings.camera": "Camera", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 032493d..b149a8a 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Ajustes", "settings.camera": "Cámara", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 67beebe..6d694e3 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Paramètres", "settings.camera": "Caméra", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 28c5c0f..c047ce4 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Postavke", "settings.camera": "Kamera", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e85ffa2..44e9931 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Beállítások", "settings.camera": "Kamera", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 3166480..302a8fb 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -103,6 +104,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Impostazioni", "settings.camera": "Videocamera", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index fb741b0..375ffae 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -58,6 +58,7 @@ "room.moderatoractions": "Moderatorhandlinger", "room.raisedHand": "{displayName} rakk opp hånden", "room.loweredHand": "{displayName} tok ned hånden", + "room.extraVideo": "Ekstra video", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", @@ -104,6 +105,7 @@ "label.media": "Media", "label.appearence": "Utseende", "label.advanced": "Avansert", + "label.addVideo": "Legg til video", "settings.settings": "Innstillinger", "settings.camera": "Kamera", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index dee0dcc..75a6b15 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Ustawienia", "settings.camera": "Kamera", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index af6b783..a823925 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Definições", "settings.camera": "Camera", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index e37dda3..c63133f 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Setări", "settings.camera": "Cameră video", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 62a22ed..8502bb3 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Ayarlar", "settings.camera": "Kamera", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index de5b736..b783913 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -58,6 +58,7 @@ "room.moderatoractions": null, "room.raisedHand": null, "room.loweredHand": null, + "room.extraVideo": null, "me.mutedPTT": null, @@ -104,6 +105,7 @@ "label.media": null, "label.appearence": null, "label.advanced": null, + "label.addVideo": null, "settings.settings": "Налаштування", "settings.camera": "Камера", From 28bad32f6975492a075f87aa2ee39b16daa5238f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 00:40:39 +0200 Subject: [PATCH 119/158] Add permission for sending extra video, fixes #280 --- app/src/components/Controls/TopBar.js | 50 +++++++++++++++------------ app/src/reducers/room.js | 1 + server/config/config.example.js | 2 ++ server/lib/Room.js | 8 +++++ 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 64e56d5..77970dc 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -165,6 +165,7 @@ const TopBar = (props) => toggleToolArea, openUsersTab, unread, + canProduceExtraVideo, canLock, canPromote, classes @@ -414,6 +415,7 @@ const TopBar = (props) => > { handleMoreActionsClose(); @@ -435,28 +437,29 @@ const TopBar = (props) => TopBar.propTypes = { - roomClient : PropTypes.object.isRequired, - room : appPropTypes.Room.isRequired, - peersLength : PropTypes.number, - lobbyPeers : PropTypes.array, - permanentTopBar : PropTypes.bool, - myPicture : PropTypes.string, - loggedIn : PropTypes.bool.isRequired, - loginEnabled : PropTypes.bool.isRequired, - fullscreenEnabled : PropTypes.bool, - fullscreen : PropTypes.bool, - onFullscreen : PropTypes.func.isRequired, - setToolbarsVisible : PropTypes.func.isRequired, - setSettingsOpen : PropTypes.func.isRequired, - setExtraVideoOpen : PropTypes.func.isRequired, - setLockDialogOpen : PropTypes.func.isRequired, - toggleToolArea : PropTypes.func.isRequired, - openUsersTab : PropTypes.func.isRequired, - unread : PropTypes.number.isRequired, - canLock : PropTypes.bool.isRequired, - canPromote : PropTypes.bool.isRequired, - classes : PropTypes.object.isRequired, - theme : PropTypes.object.isRequired + roomClient : PropTypes.object.isRequired, + room : appPropTypes.Room.isRequired, + peersLength : PropTypes.number, + lobbyPeers : PropTypes.array, + permanentTopBar : PropTypes.bool, + myPicture : PropTypes.string, + loggedIn : PropTypes.bool.isRequired, + loginEnabled : PropTypes.bool.isRequired, + fullscreenEnabled : PropTypes.bool, + fullscreen : PropTypes.bool, + onFullscreen : PropTypes.func.isRequired, + setToolbarsVisible : PropTypes.func.isRequired, + setSettingsOpen : PropTypes.func.isRequired, + setExtraVideoOpen : PropTypes.func.isRequired, + setLockDialogOpen : PropTypes.func.isRequired, + toggleToolArea : PropTypes.func.isRequired, + openUsersTab : PropTypes.func.isRequired, + unread : PropTypes.number.isRequired, + canProduceExtraVideo : PropTypes.bool.isRequired, + canLock : PropTypes.bool.isRequired, + canPromote : PropTypes.bool.isRequired, + classes : PropTypes.object.isRequired, + theme : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -470,6 +473,9 @@ const mapStateToProps = (state) => myPicture : state.me.picture, unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles + raisedHandsSelector(state), + canProduceExtraVideo : + state.me.roles.some((role) => + state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)), canLock : state.me.roles.some((role) => state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)), diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 35c1e12..0e422df 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -36,6 +36,7 @@ const initialState = SEND_CHAT : [], MODERATE_CHAT : [], SHARE_SCREEN : [], + EXTRA_VIDEO : [], SHARE_FILE : [], MODERATE_FILES : [], MODERATE_ROOM : [] diff --git a/server/config/config.example.js b/server/config/config.example.js index a1f9b84..12bf938 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -235,6 +235,8 @@ module.exports = MODERATE_CHAT : [ userRoles.MODERATOR ], // The role(s) have permission to share screen SHARE_SCREEN : [ userRoles.NORMAL ], + // The role(s) have permission to produce extra video + EXTRA_VIDEO : [ userRoles.NORMAL ], // The role(s) have permission to share files SHARE_FILE : [ userRoles.NORMAL ], // The role(s) have permission to moderate files diff --git a/server/lib/Room.js b/server/lib/Room.js index fc9b2e9..4250fd5 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -24,6 +24,7 @@ const permissionsFromRoles = SEND_CHAT : [ userRoles.NORMAL ], MODERATE_CHAT : [ userRoles.MODERATOR ], SHARE_SCREEN : [ userRoles.NORMAL ], + EXTRA_VIDEO : [ userRoles.NORMAL ], SHARE_FILE : [ userRoles.NORMAL ], MODERATE_FILES : [ userRoles.MODERATOR ], MODERATE_ROOM : [ userRoles.MODERATOR ], @@ -747,6 +748,13 @@ class Room extends EventEmitter ) throw new Error('peer not authorized'); + if ( + appData.source === 'extravideo' && + !peer.roles.some( + (role) => permissionsFromRoles.EXTRA_VIDEO.includes(role)) + ) + throw new Error('peer not authorized'); + // Ensure the Peer is joined. if (!peer.joined) throw new Error('Peer not yet joined'); From 29f3c5b036393cd51fcb2917eb0c37268bf41345 Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 07:33:24 +0200 Subject: [PATCH 120/158] MIT License --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a47de2..a62aa4e 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ This started as a fork of the [work](https://github.com/versatica/mediasoup-demo ## License -MIT +MIT License (see `LICENSE.md`) Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project. From dd8173081b633a0ea3ee1c57bb89e068f667d88f Mon Sep 17 00:00:00 2001 From: christian-2 <49752982+christian-2@users.noreply.github.com> Date: Mon, 4 May 2020 07:36:29 +0200 Subject: [PATCH 121/158] MIT License --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3d9d19 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 GÉANT Association + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 5d2634acbeb4e2ca233a65a183599a3988d2aa16 Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 07:49:09 +0200 Subject: [PATCH 122/158] MIT License --- LICENSE => LICENSE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE => LICENSE.md (100%) diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md From 2ed7c5711cb05752ec96582b2d56a2283fc4ea31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 08:49:22 +0200 Subject: [PATCH 123/158] Missing translation in extra video menu item --- app/src/components/Controls/TopBar.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 77970dc..cbe4bee 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -424,11 +424,16 @@ const TopBar = (props) => > -

Add video

+

+ +

From e5fcda9fbf35999d9ddd2cf0895c3611ff0b7317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Mon, 4 May 2020 08:51:36 +0200 Subject: [PATCH 124/158] Bump version to 3.3.0 for ansible config templates --- 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 9392a4a..ec92c5b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "multiparty-meeting", - "version": "3.2.0", + "version": "3.3.0", "private": true, "description": "multiparty meeting service", "author": "Håvar Aambø Fosstveit ", diff --git a/server/package.json b/server/package.json index c1b7ad4..50c5262 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "multiparty-meeting-server", - "version": "3.2.0", + "version": "3.3.0", "private": true, "description": "multiparty meeting server", "author": "Håvar Aambø Fosstveit ", From 9adcc807ddc50f74383995e63af20a1e0f2599af Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 09:47:10 +0200 Subject: [PATCH 125/158] bug fixes --- server/lib/promExporter.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/server/lib/promExporter.js b/server/lib/promExporter.js index 706e06a..af277eb 100644 --- a/server/lib/promExporter.js +++ b/server/lib/promExporter.js @@ -35,7 +35,7 @@ common_labels = function(both, fn) { } } } - throw new Error('cannot find generic labels'); + throw new Error('cannot find common labels'); } set_value = function(key, m, labels, v) { @@ -48,21 +48,20 @@ set_value = function(key, m, labels, v) { m.set(labels, v); break; default: - throw new Error(`unexpected metric: ${metric}`); + throw new Error(`unexpected metric: ${m}`); } } - addr = async function(ip, port) { if ('PROM_DEIDENTIFY' in process.env) { let a = ip.split('.') for (let i = 0; i < a.length - 2; i++) { a[i] = 'xx'; } - return a.join('.'); + return `${a.join('.')}:${port}`; } else if ('PROM_NUMERIC' in process.env) { - return ip; + return `${ip}:${port}`; } else { try { From 84f77f38139baf587f73d04e7d13477128c41d1a Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 10:57:24 +0200 Subject: [PATCH 126/158] employ config.js --- prom.md | 15 ++--- server/config/config.example.js | 9 +++ server/lib/promExporter.js | 115 +++++++++++++++----------------- server/server.js | 4 +- 4 files changed, 70 insertions(+), 73 deletions(-) diff --git a/prom.md b/prom.md index e5c3b39..51ba2e8 100644 --- a/prom.md +++ b/prom.md @@ -3,7 +3,7 @@ The goal of this version is to offer a few basic metrics for initial testing. The set of supported metrics can be extended. -The current implementation is +The current implementation is partly [unconventional](https://prometheus.io/docs/instrumenting/writing_exporters) in that it creates new metrics each time but does not register a custom collector. Reasons are that the exporter should @@ -17,18 +17,13 @@ of `multiparty-meeting` but connected as an interactive client. ## Configuration -| `.env` | description | -|--------|-------| -| `DEBUG` | e.g. `*WARN*,*ERROR*,*:prom` for debugging | -| `PROM_DEIDENTIFY` | if set, deidentify IP addresses | -| `PROM_NUMERIC` | if set, show numerical IP addresses | -| `PROM_PORT` | if set, enable exporter on this port | -| `PROM_QUIET` | if set, include fewer labels | +See `prometheus` in `server/config/config.example.js` for options and +applicable defaults. If `multiparty-meeting` was installed with [`mm-absible`](https://github.com/misi/mm-ansible) -it may be necessary to open the `iptables` firewall that was established -with `ferm` for incoming TCP traffic on `PROM_PORT`. +it may be necessary to open the `iptables` firewall for incoming TCP traffic +on the allocated port (see `/etc/ferm/ferm.conf`). ## Metrics diff --git a/server/config/config.example.js b/server/config/config.example.js index 12bf938..6ff279a 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -342,4 +342,13 @@ module.exports = maxIncomingBitrate : 1500000 } } + // Prometheus exporter + /* + prometheus: { + deidentify: false, // deidentify IP addresses + numeric: false, // show numeric IP addresses + port: 8889, // allocated port + quiet: false // include fewer labels + } + */ }; diff --git a/server/lib/promExporter.js b/server/lib/promExporter.js index af277eb..e54f241 100644 --- a/server/lib/promExporter.js +++ b/server/lib/promExporter.js @@ -52,33 +52,6 @@ set_value = function(key, m, labels, v) { } } -addr = async function(ip, port) { - if ('PROM_DEIDENTIFY' in process.env) { - let a = ip.split('.') - for (let i = 0; i < a.length - 2; i++) { - a[i] = 'xx'; - } - return `${a.join('.')}:${port}`; - } - else if ('PROM_NUMERIC' in process.env) { - return `${ip}:${port}`; - } - else { - try { - let a = await resolver.reverse(ip); - ip = a[0]; - } - catch (err) { - logger.error(`reverse DNS query failed: ${ip} ${err.code}`); - } - return `${ip}:${port}`; - } -} - -quiet = function(s) { - return 'PROM_QUIET' in process.env ? '' : s; -} - collect = async function(registry, rooms, peers) { metrics = function(subsystem) { @@ -191,46 +164,64 @@ collect = async function(registry, rooms, peers) { } } -module.exports = async function(rooms, peers) { - try { - logger.debug(`PROM_DEIDENTIFY=${process.env.PROM_DEIDENTIFY}`); - logger.debug(`PROM_NUMERIC=${process.env.PROM_NUMERIC}`); - logger.debug(`PROM_PORT=${process.env.PROM_PORT}`); - logger.debug(`PROM_QUIET=${process.env.PROM_QUIET}`); - let s_port = process.env.PROM_PORT; - if (!s_port) { - logger.info('exporter disabled'); +module.exports = async function(rooms, peers, config) { + + addr = async function(ip, port) { + if (config.deidentify) { + let a = ip.split('.') + for (let i = 0; i < a.length - 2; i++) { + a[i] = 'xx'; + } + return `${a.join('.')}:${port}`; + } + else if (config.numeric) { + return `${ip}:${port}`; } else { - let n_port = Number(s_port); - if (Number.isNaN(n_port)) { - throw new TypeError(`PROM_PORT has illegal value: ${s_port}`); + try { + let a = await resolver.reverse(ip); + ip = a[0]; } - - mediasoup.observer.on('newworker', worker => { - logger.debug(`observing newworker ${worker.pid} #${workers.size}`); - workers.set(worker.pid, worker); - worker.observer.on('close', () => { - logger.debug(`observing close worker ${worker.pid} #${workers.size - 1}`); - workers.delete(worker.pid); - }); - }); - - let app = express(); - app.get('/', async (req, res) => { - logger.debug(`GET ${req.originalUrl}`); - let registry = new prom.Registry(); - await collect(registry, rooms, peers); - res.set('Content-Type', registry.contentType); - let data = registry.metrics(); - res.end(data); - }); - let server = app.listen(n_port, () => { - address = server.address(); - logger.info(`listening ${address.address}:${address.port}`); - }); + catch (err) { + logger.error(`reverse DNS query failed: ${ip} ${err.code}`); + } + return `${ip}:${port}`; } } + + quiet = function(s) { + return config.quiet ? '' : s; + } + + try { + logger.debug(`config.deidentify=${config.deidentify}`); + logger.debug(`config.numeric=${config.numeric}`); + logger.debug(`config.port=${config.port}`); + logger.debug(`config.quiet=${config.quiet}`); + + mediasoup.observer.on('newworker', worker => { + logger.debug(`observing newworker ${worker.pid} #${workers.size}`); + workers.set(worker.pid, worker); + worker.observer.on('close', () => { + logger.debug(`observing close worker ${worker.pid} #${workers.size - 1}`); + workers.delete(worker.pid); + }); + }); + + let app = express(); + app.get('/', async (req, res) => { + logger.debug(`GET ${req.originalUrl}`); + let registry = new prom.Registry(); + await collect(registry, rooms, peers); + res.set('Content-Type', registry.contentType); + let data = registry.metrics(); + res.end(data); + }); + let server = app.listen(config.port || 8889, () => { + address = server.address(); + logger.info(`listening ${address.address}:${address.port}`); + }); + } catch (err) { logger.error(err); } diff --git a/server/server.js b/server/server.js index 90e299a..82aa9ab 100755 --- a/server/server.js +++ b/server/server.js @@ -134,7 +134,9 @@ async function run() await interactiveServer(rooms, peers); // start Prometheus exporter - await promExporter(rooms, peers); + if (config.prometheus) { + await promExporter(rooms, peers, config.prometheus); + } if (typeof(config.auth) === 'undefined') { From f4986cc9d7e2fd11ea57e8842e5e8cff2f50cdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 11:14:09 +0200 Subject: [PATCH 127/158] Make participant list more compact --- app/src/components/MeetingDrawer/ParticipantList/ListMe.js | 3 +-- .../MeetingDrawer/ParticipantList/ParticipantList.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index 0bc8446..762af00 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -16,8 +16,7 @@ const styles = (theme) => width : '100%', overflow : 'hidden', cursor : 'auto', - display : 'flex', - padding : theme.spacing(1) + display : 'flex' }, avatar : { diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index 8847c06..af35dbd 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -31,12 +31,10 @@ const styles = (theme) => }, listheader : { - padding : theme.spacing(1), fontWeight : 'bolder' }, listItem : { - padding : theme.spacing(1), width : '100%', overflow : 'hidden', cursor : 'pointer', From b7aad16f651851efd623694da491a01df279ade8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 13:54:13 +0200 Subject: [PATCH 128/158] Clean up CSS, make room for buttons, fixes #283 --- app/src/components/MeetingDrawer/Chat/ChatModerator.js | 6 +----- .../MeetingDrawer/FileSharing/FileSharingModerator.js | 6 +----- .../MeetingDrawer/ParticipantList/ListModerator.js | 7 ++----- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/app/src/components/MeetingDrawer/Chat/ChatModerator.js b/app/src/components/MeetingDrawer/Chat/ChatModerator.js index c8b48e0..a35675b 100644 --- a/app/src/components/MeetingDrawer/Chat/ChatModerator.js +++ b/app/src/components/MeetingDrawer/Chat/ChatModerator.js @@ -10,12 +10,8 @@ const styles = (theme) => ({ root : { - padding : theme.spacing(1), - width : '100%', - overflow : 'hidden', - cursor : 'auto', display : 'flex', - listStyleType : 'none', + padding : theme.spacing(1), boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)', backgroundColor : 'rgba(255, 255, 255, 1)' }, diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js index dad17a0..e38e54c 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js @@ -10,12 +10,8 @@ const styles = (theme) => ({ root : { - padding : theme.spacing(1), - width : '100%', - overflow : 'hidden', - cursor : 'auto', display : 'flex', - listStyleType : 'none', + padding : theme.spacing(1), boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)', backgroundColor : 'rgba(255, 255, 255, 1)' }, diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js index caaea73..c10506a 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js @@ -10,11 +10,8 @@ const styles = (theme) => ({ root : { - padding : theme.spacing(1), - width : '100%', - overflow : 'hidden', - cursor : 'auto', - display : 'flex' + padding : theme.spacing(1), + display : 'flex' }, divider : { From f6c76f372a6061caaf75903f3bdc926624e2e990 Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 14:22:31 +0200 Subject: [PATCH 129/158] satisfy ESLint --- server/lib/promExporter.js | 410 +++++++++++++++++++++---------------- server/server.js | 5 +- 2 files changed, 236 insertions(+), 179 deletions(-) diff --git a/server/lib/promExporter.js b/server/lib/promExporter.js index e54f241..cb34f21 100644 --- a/server/lib/promExporter.js +++ b/server/lib/promExporter.js @@ -4,225 +4,281 @@ const mediasoup = require('mediasoup'); const prom = require('prom-client'); const Logger = require('./Logger'); -const Peer = require('./Peer'); -const Room = require('./Room'); const logger = new Logger('prom'); const resolver = new Resolver(); - const workers = new Map(); -const label_names = [ +const labelNames = [ 'pid', 'room_id', 'peer_id', 'display_name', 'user_agent', 'transport_id', 'proto', 'local_addr', 'remote_addr', 'id', 'kind', 'codec', 'type' ]; const metadata = { - 'byteCount': { metric_type: prom.Counter, unit: 'bytes' }, - 'score': { metric_type: prom.Gauge } -} + 'byteCount' : { metricType: prom.Counter, unit: 'bytes' }, + 'score' : { metricType: prom.Gauge } +}; -common_labels = function(both, fn) { - for (let [room_id, room] of rooms) { - for (let [peer_id, peer] of peers) { - if (fn(peer)) { - let display_name = peer._displayName; +module.exports = async function(rooms, peers, config) +{ + const collect = async function(registry) + { + const newMetrics = function(subsystem) + { + const namespace = 'mediasoup'; + const metrics = new Map(); - let user_agent = peer._socket.client.request.headers['user-agent']; - let kind = both.kind; - let codec = both.rtpParameters.codecs[0].mimeType.split('/')[1]; - return { room_id, peer_id, display_name, user_agent, kind, codec }; - } - } - } - throw new Error('cannot find common labels'); -} + for (const key in metadata) + { + if (Object.prototype.hasOwnProperty.call(metadata, key)) + { + const value = metadata[key]; + const name = key.split(/(?=[A-Z])/).join('_') + .toLowerCase(); + const unit = value.unit; + const metricType = value.metricType; + let s = `${namespace}_${subsystem}_${name}`; -set_value = function(key, m, labels, v) { - logger.debug(`set_value key=${key} v=${v}`); - switch (metadata[key].metric_type) { - case prom.Counter: - m.inc(labels, v); - break; - case prom.Gauge: - m.set(labels, v); - break; - default: - throw new Error(`unexpected metric: ${m}`); - } -} + if (unit) + { + s += `_${unit}`; + } + const m = new metricType({ + name : s, help : `${subsystem}.${key}`, labelNames : labelNames, registers : [ registry ] }); -collect = async function(registry, rooms, peers) { - - metrics = function(subsystem) { - let namespace = 'mediasoup'; - let metrics = new Map(); - for (let key in metadata) { - value = metadata[key]; - let name = key.split(/(?=[A-Z])/).join('_').toLowerCase(); - let unit = value.unit; - let metric_type = value.metric_type; - let s = `${namespace}_${subsystem}_${name}`; - if (unit) { - s += `_${unit}`; - } - m = new metric_type({name: s, help: `${subsystem}.${key}`, - labelNames: label_names, registers: [registry]}); - metrics.set(key, m); - } - return metrics; - } - - logger.debug('collect'); - const m_rooms = new prom.Gauge({name: 'edumeet_rooms', help: '#rooms', - registers: [registry]}); - m_rooms.set(rooms.size); - const m_peers = new prom.Gauge({name: 'edumeet_peers', help: '#peers', - labelNames: ['room_id'], registers: [registry]}); - for (let [room_id, room] of rooms) { - m_peers.labels(room_id).set(Object.keys(room._peers).length); - } - - const m_consumer = metrics('consumer'); - const m_producer = metrics('producer'); - for (let [pid, worker] of workers) { - logger.debug(`visiting worker ${pid}`); - for (let router of worker._routers) { - logger.debug(`visiting router ${router.id}`); - for (let [transport_id, transport] of router._transports) { - logger.debug(`visiting transport ${transport_id}`); - let transport_j = await transport.dump(); - if (transport_j.iceState != 'completed') { - logger.debug(`skipping transport ${transport_id}}: ${transport_j.iceState}`); - continue; + metrics.set(key, m); } - let ice_selected_tuple = transport_j.iceSelectedTuple; - let proto = ice_selected_tuple.protocol - let local_addr = await addr(ice_selected_tuple.localIp, - ice_selected_tuple.localPort); - let remote_addr = await addr(ice_selected_tuple.remoteIp, - ice_selected_tuple.remotePort); - for (let [producer_id, producer] of transport._producers) { - logger.debug(`visiting producer ${producer_id}`); - let { room_id, peer_id, display_name, user_agent, kind, codec } = - common_labels(producer, peer => peer._producers.has(producer_id)); - let a = await producer.getStats(); - for (let x of a) { - let type = x.type; - let labels = { - 'pid': pid, - 'room_id': room_id, - 'peer_id': peer_id, - 'display_name': display_name, - 'user_agent': user_agent, - 'transport_id': quiet(transport_id), - 'proto': proto, - 'local_addr': local_addr, - 'remote_addr': remote_addr, - 'id': quiet(producer_id), - 'kind': kind, - 'codec': codec, - 'type': type - } - for (let [key, m] of m_producer) { - set_value(key, m, labels, x[key]); - } + } + + return metrics; + }; + + const commonLabels = function(both, fn) + { + for (const roomId of rooms.keys()) + { + for (const [ peerId, peer ] of peers) + { + if (fn(peer)) + { + const displayName = peer._displayName; + const userAgent = peer._socket.client.request.headers['user-agent']; + const kind = both.kind; + const codec = both.rtpParameters.codecs[0].mimeType.split('/')[1]; + + return { roomId, peerId, displayName, userAgent, kind, codec }; } } - for (let [consumer_id, consumer] of transport._consumers) { - logger.debug(`visiting consumer ${consumer_id}`); - let { room_id, peer_id, display_name, user_agent, kind, codec } = - common_labels(consumer, peer => peer._consumers.has(consumer_id)); - let a = await consumer.getStats(); - for (let x of a) { - if (x.type == 'inbound-rtp') { - continue; + } + throw new Error('cannot find common labels'); + }; + + const addr = async function(ip, port) + { + if (config.deidentify) + { + const a = ip.split('.'); + + for (let i = 0; i < a.length - 2; i++) + { + a[i] = 'xx'; + } + + return `${a.join('.')}:${port}`; + } + else if (config.numeric) + { + return `${ip}:${port}`; + } + else + { + try + { + const a = await resolver.reverse(ip); + + ip = a[0]; + } + catch (err) + { + logger.error(`reverse DNS query failed: ${ip} ${err.code}`); + } + + return `${ip}:${port}`; + } + }; + + const quiet = function(s) + { + return config.quiet ? '' : s; + }; + + const setValue = function(key, m, labels, v) + { + logger.debug(`setValue key=${key} v=${v}`); + switch (metadata[key].metricType) + { + case prom.Counter: + m.inc(labels, v); + break; + case prom.Gauge: + m.set(labels, v); + break; + default: + throw new Error(`unexpected metric: ${m}`); + } + }; + + logger.debug('collect'); + const mRooms = new prom.Gauge({ name: 'edumeet_rooms', help: '#rooms', registers: [ registry ] }); + + mRooms.set(rooms.size); + const mPeers = new prom.Gauge({ name: 'edumeet_peers', help: '#peers', labelNames: [ 'room_id' ], registers: [ registry ] }); + + for (const [ roomId, room ] of rooms) + { + mPeers.labels(roomId).set(Object.keys(room._peers).length); + } + + const mConsumer = newMetrics('consumer'); + const mProducer = newMetrics('producer'); + + for (const [ pid, worker ] of workers) + { + logger.debug(`visiting worker ${pid}`); + for (const router of worker._routers) + { + logger.debug(`visiting router ${router.id}`); + for (const [ transportId, transport ] of router._transports) + { + logger.debug(`visiting transport ${transportId}`); + const transportJson = await transport.dump(); + + if (transportJson.iceState != 'completed') + { + logger.debug(`skipping transport ${transportId}}: ${transportJson.iceState}`); + continue; + } + const iceSelectedTuple = transportJson.iceSelectedTuple; + const proto = iceSelectedTuple.protocol; + const localAddr = await addr(iceSelectedTuple.localIp, + iceSelectedTuple.localPort); + const remoteAddr = await addr(iceSelectedTuple.remoteIp, + iceSelectedTuple.remotePort); + + for (const [ producerId, producer ] of transport._producers) + { + logger.debug(`visiting producer ${producerId}`); + const { roomId, peerId, displayName, userAgent, kind, codec } = + commonLabels(producer, (peer) => peer._producers.has(producerId)); + const a = await producer.getStats(); + + for (const x of a) + { + const type = x.type; + const labels = { + 'pid' : pid, + 'room_id' : roomId, + 'peer_id' : peerId, + 'display_name' : displayName, + 'user_agent' : userAgent, + 'transport_id' : quiet(transportId), + 'proto' : proto, + 'local_addr' : localAddr, + 'remote_addr' : remoteAddr, + 'id' : quiet(producerId), + 'kind' : kind, + 'codec' : codec, + 'type' : type + }; + + for (const [ key, m ] of mProducer) + { + setValue(key, m, labels, x[key]); + } } - let type = x.type; - let labels = { - 'pid': pid, - 'room_id': room_id, - 'peer_id': peer_id, - 'display_name': display_name, - 'user_agent': user_agent, - 'transport_id': quiet(transport_id), - 'proto': proto, - 'local_addr': local_addr, - 'remote_addr': remote_addr, - 'id': quiet(consumer_id), - 'kind': kind, - 'codec': codec, - 'type': type - } - for (let [key, m] of m_consumer) { - set_value(key, m, labels, x[key]); + } + for (const [ consumerId, consumer ] of transport._consumers) + { + logger.debug(`visiting consumer ${consumerId}`); + const { roomId, peerId, displayName, userAgent, kind, codec } = + commonLabels(consumer, (peer) => peer._consumers.has(consumerId)); + const a = await consumer.getStats(); + + for (const x of a) + { + if (x.type == 'inbound-rtp') + { + continue; + } + const type = x.type; + const labels = + { + 'pid' : pid, + 'room_id' : roomId, + 'peer_id' : peerId, + 'display_name' : displayName, + 'user_agent' : userAgent, + 'transport_id' : quiet(transportId), + 'proto' : proto, + 'local_addr' : localAddr, + 'remote_addr' : remoteAddr, + 'id' : quiet(consumerId), + 'kind' : kind, + 'codec' : codec, + 'type' : type + }; + + for (const [ key, m ] of mConsumer) + { + setValue(key, m, labels, x[key]); + } } } } } } - } -} + }; -module.exports = async function(rooms, peers, config) { - - addr = async function(ip, port) { - if (config.deidentify) { - let a = ip.split('.') - for (let i = 0; i < a.length - 2; i++) { - a[i] = 'xx'; - } - return `${a.join('.')}:${port}`; - } - else if (config.numeric) { - return `${ip}:${port}`; - } - else { - try { - let a = await resolver.reverse(ip); - ip = a[0]; - } - catch (err) { - logger.error(`reverse DNS query failed: ${ip} ${err.code}`); - } - return `${ip}:${port}`; - } - } - - quiet = function(s) { - return config.quiet ? '' : s; - } - - try { + try + { logger.debug(`config.deidentify=${config.deidentify}`); logger.debug(`config.numeric=${config.numeric}`); logger.debug(`config.port=${config.port}`); logger.debug(`config.quiet=${config.quiet}`); - mediasoup.observer.on('newworker', worker => { + mediasoup.observer.on('newworker', (worker) => + { logger.debug(`observing newworker ${worker.pid} #${workers.size}`); workers.set(worker.pid, worker); - worker.observer.on('close', () => { + worker.observer.on('close', () => + { logger.debug(`observing close worker ${worker.pid} #${workers.size - 1}`); workers.delete(worker.pid); }); }); - let app = express(); - app.get('/', async (req, res) => { + const app = express(); + + app.get('/', async (req, res) => + { logger.debug(`GET ${req.originalUrl}`); - let registry = new prom.Registry(); - await collect(registry, rooms, peers); + const registry = new prom.Registry(); + + await collect(registry); res.set('Content-Type', registry.contentType); - let data = registry.metrics(); + const data = registry.metrics(); + res.end(data); }); - let server = app.listen(config.port || 8889, () => { - address = server.address(); + const server = app.listen(config.port || 8889, () => + { + const address = server.address(); + logger.info(`listening ${address.address}:${address.port}`); }); } - catch (err) { + catch (err) + { logger.error(err); } -} +}; diff --git a/server/server.js b/server/server.js index 82aa9ab..dbf9a8e 100755 --- a/server/server.js +++ b/server/server.js @@ -134,9 +134,10 @@ async function run() await interactiveServer(rooms, peers); // start Prometheus exporter - if (config.prometheus) { + if (config.prometheus) + { await promExporter(rooms, peers, config.prometheus); - } + } if (typeof(config.auth) === 'undefined') { From 7282287d933ec93b4336b3cb0e4d2d2c85de1f76 Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 14:34:50 +0200 Subject: [PATCH 130/158] CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..10afef4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Source code contributions into `server/` should pass static code analysis as performed by `npm run lint`. From ff285b2d87d0777154c18d934cf45078900e2e3f Mon Sep 17 00:00:00 2001 From: christian2 Date: Mon, 4 May 2020 14:47:02 +0200 Subject: [PATCH 131/158] app as well as server --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10afef4..d5c8efc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -Source code contributions into `server/` should pass static code analysis as performed by `npm run lint`. +Source code contributions should pass static code analysis as performed by `npm run lint` in `server` and `app` respectively. From a1ed79c5dbba16f904d142949f968730c84194ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 15:09:05 +0200 Subject: [PATCH 132/158] A button to promote all peers from lobby, fixes #287 --- app/src/RoomClient.js | 20 +++++++ app/src/actions/roomActions.js | 6 ++ .../AccessControl/LockDialog/ListLobbyPeer.js | 25 +++++--- .../AccessControl/LockDialog/LockDialog.js | 24 +++++++- app/src/reducers/room.js | 60 ++++++++++--------- app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + server/lib/Lobby.js | 4 +- 23 files changed, 117 insertions(+), 39 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index d52631d..52b9383 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1274,6 +1274,26 @@ export default class RoomClient roomActions.setSelectedPeer(peerId)); } + async promoteAllLobbyPeers() + { + logger.debug('promoteAllLobbyPeers()'); + + store.dispatch( + roomActions.setLobbyPeersPromotionInProgress(true)); + + try + { + await this.sendRequest('promoteAllPeers'); + } + catch (error) + { + logger.error('promoteAllLobbyPeers() [error:"%o"]', error); + } + + store.dispatch( + roomActions.setLobbyPeersPromotionInProgress(false)); + } + async promoteLobbyPeer(peerId) { logger.debug('promoteLobbyPeer() [peerId:"%s"]', peerId); diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index ddcfcc4..30ce37c 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -123,6 +123,12 @@ export const toggleConsumerFullscreen = (consumerId) => payload : { consumerId } }); +export const setLobbyPeersPromotionInProgress = (flag) => + ({ + type : 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS', + payload : { flag } + }); + export const setMuteAllInProgress = (flag) => ({ type : 'MUTE_ALL_IN_PROGRESS', diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js index 97f1ff6..8f9843a 100644 --- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js +++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js @@ -27,6 +27,7 @@ const ListLobbyPeer = (props) => const { roomClient, peer, + promotionInProgress, canPromote, classes } = props; @@ -55,7 +56,11 @@ const ListLobbyPeer = (props) => })} > { e.stopPropagation(); @@ -71,18 +76,20 @@ const ListLobbyPeer = (props) => ListLobbyPeer.propTypes = { - roomClient : PropTypes.any.isRequired, - advancedMode : PropTypes.bool, - peer : PropTypes.object.isRequired, - canPromote : PropTypes.bool.isRequired, - classes : PropTypes.object.isRequired + roomClient : PropTypes.any.isRequired, + advancedMode : PropTypes.bool, + peer : PropTypes.object.isRequired, + promotionInProgress : PropTypes.bool.isRequired, + canPromote : PropTypes.bool.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state, { id }) => { return { - peer : state.lobbyPeers[id], - canPromote : + peer : state.lobbyPeers[id], + promotionInProgress : state.room.lobbyPeersPromotionInProgress, + canPromote : state.me.roles.some((role) => state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) }; @@ -97,6 +104,8 @@ export default withRoomContext(connect( { return ( prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room.lobbyPeersPromotionInProgress === + next.room.lobbyPeersPromotionInProgress && prev.me.roles === next.me.roles && prev.lobbyPeers === next.lobbyPeers ); diff --git a/app/src/components/AccessControl/LockDialog/LockDialog.js b/app/src/components/AccessControl/LockDialog/LockDialog.js index 04048cd..4d6cd24 100644 --- a/app/src/components/AccessControl/LockDialog/LockDialog.js +++ b/app/src/components/AccessControl/LockDialog/LockDialog.js @@ -51,9 +51,11 @@ const styles = (theme) => }); const LockDialog = ({ + roomClient, room, handleCloseLockDialog, lobbyPeers, + canPromote, classes }) => { @@ -102,6 +104,20 @@ const LockDialog = ({ } + - + { + (browser.platform === 'mobile') && canShareFiles && canShare && + } ); @@ -87,6 +118,7 @@ const FileSharing = (props) => FileSharing.propTypes = { roomClient : PropTypes.any.isRequired, + browser : PropTypes.object.isRequired, canShareFiles : PropTypes.bool.isRequired, tabOpen : PropTypes.bool.isRequired, canShare : PropTypes.bool.isRequired, @@ -97,6 +129,7 @@ const mapStateToProps = (state) => { return { canShareFiles : state.me.canShareFiles, + browser : state.me.browser, tabOpen : state.toolarea.currentToolTab === 'files', canShare : state.me.roles.some((role) => @@ -113,6 +146,7 @@ export default withRoomContext(connect( { return ( prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.me.browser === next.me.browser && prev.me.roles === next.me.roles && prev.me.canShareFiles === next.me.canShareFiles && prev.toolarea.currentToolTab === next.toolarea.currentToolTab diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index e82e9d5..f26d3c5 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -92,6 +92,7 @@ "label.filesharing": "文件共享", "label.participants": "参与者", "label.shareFile": "共享文件", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "不支持文件共享", "label.unknown": "未知", "label.democratic": "民主视图", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 1a2b3c5..4c6eda8 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -91,6 +91,7 @@ "label.filesharing": "Sdílení souborů", "label.participants": "Účastníci", "label.shareFile": "Sdílet soubor", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Sdílení souborů není podporováno", "label.unknown": "Neznámý", "label.democratic": "Rozvržení: Demokratické", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index a6f6ac2..63bece8 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -92,6 +92,7 @@ "label.filesharing": "Dateien", "label.participants": "Teilnehmer", "label.shareFile": "Datei hochladen", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt", "label.unknown": "Unbekannt", "label.democratic": "Demokratisch", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 4027363..7228963 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -92,6 +92,7 @@ "label.filesharing": "Fildeling", "label.participants": "Deltagere", "label.shareFile": "Del fil", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Fildeling er ikke understøttet", "label.unknown": "Ukendt", "label.democracy": "Galleri visning", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 5f93f42..dfc1f86 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -92,6 +92,7 @@ "label.filesharing": "Διαμοιρασμοός αρχείου", "label.participants": "Συμμετέχοντες", "label.shareFile": "Διαμοιραστείτε ένα αρχείο", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται", "label.unknown": "Άγνωστο", "label.democratic": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 8fe4fa1..0f38032 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -92,6 +92,7 @@ "label.filesharing": "File sharing", "label.participants": "Participants", "label.shareFile": "Share file", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "File sharing not supported", "label.unknown": "Unknown", "label.democratic": "Democratic view", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index d73506c..2d42660 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -92,6 +92,7 @@ "label.filesharing": "Compartir ficheros", "label.participants": "Participantes", "label.shareFile": "Compartir fichero", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Compartir ficheros no está disponible", "label.unknown": "Desconocido", "label.democratic": "Vista democrática", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index f884c36..753e8f3 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -92,6 +92,7 @@ "label.filesharing": "Partage de fichier", "label.participants": "Participants", "label.shareFile": "Partager un fichier", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Partage de fichier non supporté", "label.unknown": "Inconnu", "label.democratic": "Vue démocratique", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index eb9d08e..ca803be 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -92,6 +92,7 @@ "label.filesharing": "Dijeljenje datoteka", "label.participants": "Sudionici", "label.shareFile": "Dijeli datoteku", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano", "label.unknown": "Nepoznato", "label.democratic":"Demokratski prikaz", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index f25b06a..43228fa 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -92,6 +92,7 @@ "label.filesharing": "Fájl megosztás", "label.participants": "Résztvevők", "label.shareFile": "Fájl megosztása", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Fájl megosztás nem támogatott", "label.unknown": "Ismeretlen", "label.democratic": "Egyforma képméretű képkiosztás", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index d027e7a..8126d6f 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -91,6 +91,7 @@ "label.filesharing": "Condivisione file", "label.participants": "Partecipanti", "label.shareFile": "Condividi file", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Condivisione file non supportata", "label.unknown": "Sconosciuto", "label.democratic": "Vista Democratica", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index e803bda..d95a866 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -92,6 +92,7 @@ "label.filesharing": "Fildeling", "label.participants": "Deltakere", "label.shareFile": "Del fil", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Fildeling ikke støttet", "label.unknown": "Ukjent", "label.democratic": "Demokratisk", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 399f788..d366cf3 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -92,6 +92,7 @@ "label.filesharing": "Udostępnianie plików", "label.participants": "Uczestnicy", "label.shareFile": "Udostępnij plik", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane", "label.unknown": "Nieznane", "label.democratic": "Układ demokratyczny", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index d66d8da..867a3e5 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -92,6 +92,7 @@ "label.filesharing": "Partilha de ficheiro", "label.participants": "Participantes", "label.shareFile": "Partilhar ficheiro", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Partilha de ficheiro não disponível", "label.unknown": "Desconhecido", "label.democratic": "Vista democrática", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index b37b36a..ed8b24f 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -92,6 +92,7 @@ "label.filesharing": "Partajarea fișierelor", "label.participants": "Participanți", "label.shareFile": "Partajează fișierul", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată", "label.unknown": "Necunoscut", "label.democratic": "Distribuție egală a dimensiunii imaginii", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index d3d20e3..3d1b83b 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -92,6 +92,7 @@ "label.filesharing": "Dosya paylaşım", "label.participants": "Katılımcı", "label.shareFile": "Dosya paylaş", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor", "label.unknown": "Bilinmeyen", "label.democratic": "Demokratik görünüm", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index dcb3c7d..9b4c4b5 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -92,6 +92,7 @@ "label.filesharing": "Обмін файлами", "label.participants": "Учасники", "label.shareFile": "Надіслати файл", + "label.shareGalleryFile": null, "label.fileSharingUnsupported": "Обмін файлами не підтримується", "label.unknown": "Невідомо", "label.democrat": "Демократичний вигляд", From ac6ee1bfa3b8f482d5d70740939ca502076f3531 Mon Sep 17 00:00:00 2001 From: Astagor Date: Mon, 4 May 2020 19:31:50 +0200 Subject: [PATCH 137/158] Added limit for maximum number of users in a single room --- app/src/RoomClient.js | 7 +++++++ app/src/actions/roomActions.js | 6 ++++++ app/src/components/JoinDialog.js | 15 +++++++++++++++ app/src/reducers/room.js | 6 ++++++ app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + server/config/config.example.js | 2 ++ server/lib/Room.js | 8 ++++++++ 23 files changed, 61 insertions(+) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8dbc9d1..8a502be 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1963,6 +1963,13 @@ export default class RoomClient break; } + case 'overRoomLimit': + { + store.dispatch(roomActions.setOverRoomLimit(true)); + + break; + } + case 'roomReady': { const { turnServers } = notification.data; diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index 30ce37c..b90bf1b 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -40,6 +40,12 @@ export const setSignInRequired = (signInRequired) => payload : { signInRequired } }); +export const setOverRoomLimit = (overRoomLimit) => + ({ + type : 'SET_OVER_ROOM_LIMIT', + payload : { overRoomLimit } + }); + export const setAccessCode = (accessCode) => ({ type : 'SET_ACCESS_CODE', diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index a8493db..8a2bc23 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -83,6 +83,10 @@ const styles = (theme) => green : { color : 'rgba(0, 153, 0, 1)' + }, + red : + { + color : 'rgba(153, 0, 0, 1)' } }); @@ -281,6 +285,16 @@ const JoinDialog = ({ }} fullWidth /> + {!room.inLobby && room.overRoomLimit && + + + + } @@ -419,6 +433,7 @@ export default withRoomContext(connect( return ( prev.room.inLobby === next.room.inLobby && prev.room.signInRequired === next.room.signInRequired && + prev.room.overRoomLimit === next.room.overRoomLimit && prev.settings.displayName === next.settings.displayName && prev.me.displayNameInProgress === next.me.displayNameInProgress && prev.me.loginEnabled === next.me.loginEnabled && diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index f4bc6ab..a4bbfb4 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -6,6 +6,7 @@ const initialState = locked : false, inLobby : false, signInRequired : false, + overRoomLimit : false, // access code to the room if locked and joinByAccessCode == true accessCode : '', // if true: accessCode is a possibility to open the room @@ -88,7 +89,12 @@ const room = (state = initialState, action) => return { ...state, signInRequired }; } + case 'SET_OVER_ROOM_LIMIT': + { + const { overRoomLimit } = action.payload; + return { ...state, overRoomLimit }; + } case 'SET_ACCESS_CODE': { const { accessCode } = action.payload; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index e82e9d5..a5ac918 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 1a2b3c5..0acac2a 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -58,6 +58,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/de.json b/app/src/translations/de.json index a6f6ac2..eec3d7c 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 4027363..58bd0fc 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 5f93f42..4db8158 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 8fe4fa1..a40ba8f 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -59,6 +59,7 @@ "room.raisedHand": "{displayName} raised their hand", "room.loweredHand": "{displayName} put their hand down", "room.extraVideo": "Extra video", + "room.overRoomLimit": null, "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index d73506c..85899ae 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index f884c36..0126ede 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index eb9d08e..14baba4 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index f25b06a..2648c60 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/it.json b/app/src/translations/it.json index d027e7a..d8f990c 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index e803bda..935841b 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -59,6 +59,7 @@ "room.raisedHand": "{displayName} rakk opp hånden", "room.loweredHand": "{displayName} tok ned hånden", "room.extraVideo": "Ekstra video", + "room.overRoomLimit": null, "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 399f788..abe6fb4 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index d66d8da..214b620 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index b37b36a..95c7952 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index d3d20e3..4cbad15 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index dcb3c7d..933fda0 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -59,6 +59,7 @@ "room.raisedHand": null, "room.loweredHand": null, "room.extraVideo": null, + "room.overRoomLimit": null, "me.mutedPTT": null, diff --git a/server/config/config.example.js b/server/config/config.example.js index 6ff279a..8af3615 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -247,6 +247,8 @@ module.exports = // When truthy, the room will be open to all users when as long as there // are allready users in the room activateOnHostJoin : true, + // When set, maxUsersPerRoom defines how many users can join a single room. If not set, there is not limit. + // maxUsersPerRoom : 20, // Mediasoup settings mediasoup : { diff --git a/server/lib/Room.js b/server/lib/Room.js index 9200be9..bbd0cba 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -177,6 +177,9 @@ class Room extends EventEmitter peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) ) this._peerJoining(peer); + else if ('maxUsersPerRoom' in config &&(this._getJoinedPeers().length + this._lobby.peerList().length) >= config.maxUsersPerRoom) { + this._handleOverRoomLimit(peer); + } else if (this._locked) this._parkPeer(peer); else @@ -188,6 +191,11 @@ class Room extends EventEmitter } } + _handleOverRoomLimit(peer) + { + this._notification(peer.socket, 'overRoomLimit'); + } + _handleGuest(peer) { if (config.activateOnHostJoin && !this.checkEmpty()) From 004fac3d1306e4decfca35b586c9d028b1faf2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Mon, 4 May 2020 21:48:14 +0200 Subject: [PATCH 138/158] Import Latvian translation --- app/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/index.js b/app/src/index.js index 90bfa4d..cdefb40 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -38,6 +38,7 @@ import messagesCzech from './translations/cs'; import messagesItalian from './translations/it'; import messagesUkrainian from './translations/uk'; import messagesTurkish from './translations/tr'; +import messagesLatvian from './translations/lv'; import './index.css'; @@ -63,7 +64,8 @@ const messages = 'cs' : messagesCzech, 'it' : messagesItalian, 'uk' : messagesUkrainian, - 'tr' : messagesTurkish + 'tr' : messagesTurkish, + 'lv' : messagesLatvian }; const locale = navigator.language.split(/[-_]/)[0]; // language without region code From 381f9cd7330f1051c56404ef76387987afc745e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 23:33:51 +0200 Subject: [PATCH 139/158] All peers enter the same router up to config.routerScaleSize. Then go to the next one, and keep going until all routers are filled up to config.routerScaleSize. After that simple put peers into routers with least peers. --- server/config/config.example.js | 2 + server/lib/Room.js | 117 +++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 740a9ae..3541327 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -60,6 +60,8 @@ module.exports = // When truthy, the room will be open to all users when the first // authenticated user has already joined the room. activateOnHostJoin : true, + // Room size before spreading to new router + routerScaleSize : 20, // Mediasoup settings mediasoup : { diff --git a/server/lib/Room.js b/server/lib/Room.js index 23f6d53..2dc2368 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -5,7 +5,7 @@ const config = require('../config/config'); const logger = new Logger('Room'); -const ROUTER_SCALE_SIZE = 40; +const ROUTER_SCALE_SIZE = config.routerScaleSize || 20; class Room extends EventEmitter { @@ -25,23 +25,30 @@ class Room extends EventEmitter // Router media codecs. const mediaCodecs = config.mediasoup.router.mediaCodecs; - const mediasoupRouters = []; + const mediasoupRouters = new Map(); + + let firstRouter = null; for (const worker of mediasoupWorkers) { const router = await worker.createRouter({ mediaCodecs }); - mediasoupRouters.push(router); + if (!firstRouter) + firstRouter = router; + + mediasoupRouters.set(router.id, router); } // Create a mediasoup AudioLevelObserver on first router - const audioLevelObserver = await mediasoupRouters[0].createAudioLevelObserver( + const audioLevelObserver = await firstRouter.createAudioLevelObserver( { maxEntries : 1, threshold : -80, interval : 800 }); + firstRouter = null; + return new Room({ roomId, mediasoupRouters, audioLevelObserver }); } @@ -81,6 +88,11 @@ class Room extends EventEmitter // Array of mediasoup Router instances. this._mediasoupRouters = mediasoupRouters; + // The router we are currently putting peers in + this._routerIterator = this._mediasoupRouters.values(); + + this._currentRouter = this._routerIterator.next().value; + // mediasoup AudioLevelObserver. this._audioLevelObserver = audioLevelObserver; @@ -102,8 +114,14 @@ class Room extends EventEmitter this._closed = true; + this._chatHistory = null; + + this._fileHistory = null; + this._lobby.close(); + this._lobby = null; + // Close the peers. for (const peer in this._peers) { @@ -117,11 +135,19 @@ class Room extends EventEmitter this._peers = null; // Close the mediasoup Routers. - for (const router of this._mediasoupRouters) + for (const router of this._mediasoupRouters.values()) { router.close(); } + this._routerIterator = null; + + this._currentRouter = null; + + this._mediasoupRouters.clear(); + + this._audioLevelObserver = null; + // Emit 'close' event. this.emit('close'); } @@ -330,7 +356,7 @@ class Room extends EventEmitter } } - _peerJoining(peer) + async _peerJoining(peer) { peer.socket.join(this._roomId); @@ -343,8 +369,8 @@ class Room extends EventEmitter this._peers[peer.id] = peer; - // Assign least loaded router - peer.routerId = this._getLeastLoadedRouter(); + // Assign routerId + peer.routerId = await this._getRouterId(); this._handlePeer(peer); this._notification(peer.socket, 'roomReady'); @@ -428,7 +454,7 @@ class Room extends EventEmitter async _handleSocketRequest(peer, request, cb) { const router = - this._mediasoupRouters.find((peerRouter) => peerRouter.id === peer.routerId); + this._mediasoupRouters.get(peer.routerId); switch (request.method) { @@ -632,9 +658,11 @@ class Room extends EventEmitter const producer = await transport.produce({ kind, rtpParameters, appData }); - for (const destinationRouter of this._mediasoupRouters) + const pipeRouters = this._getRoutersToPipeTo(peer.routerId); + + for (const [ routerId, destinationRouter ] of this._mediasoupRouters) { - if (destinationRouter !== router) + if (pipeRouters.includes(routerId)) { await router.pipeToRouter({ producerId : producer.id, @@ -1117,8 +1145,7 @@ class Room extends EventEmitter producer.id ); - const router = this._mediasoupRouters.find((producerRouter) => - producerRouter.id === producerPeer.routerId); + const router = this._mediasoupRouters.get(producerPeer.routerId); // Optimization: // - Create the server-side Consumer. If video, do it paused. @@ -1324,19 +1351,77 @@ class Room extends EventEmitter } } + async _pipeProducersToNewRouter() + { + const peersToPipe = + Object.values(this._peers) + .filter((peer) => peer.routerId !== this._currentRouter.id); + + for (const peer of peersToPipe) + { + const srcRouter = this._mediasoupRouters.get(peer.routerId); + + for (const producerId of peer.producers.keys()) + { + await srcRouter.pipeToRouter({ + producerId, + router : this._currentRouter + }); + } + } + } + + async _getRouterId() + { + if (this._currentRouter) + { + const routerLoad = + Object.values(this._peers) + .filter((peer) => peer.routerId === this._currentRouter.id).length; + + if (routerLoad >= ROUTER_SCALE_SIZE) + { + this._currentRouter = this._routerIterator.next().value; + + if (this._currentRouter) + { + await this._pipeProducersToNewRouter(); + + return this._currentRouter.id; + } + } + else + { + return this._currentRouter.id; + } + } + + return this._getLeastLoadedRouter(); + } + + // Returns an array of router ids we need to pipe to + _getRoutersToPipeTo(originRouterId) + { + return Object.values(this._peers) + .map((peer) => peer.routerId) + .filter((routerId, index, self) => + routerId !== originRouterId && self.indexOf(routerId) === index + ); + } + _getLeastLoadedRouter() { let load = Infinity; let id; - for (const router of this._mediasoupRouters) + for (const routerId of this._mediasoupRouters.keys()) { const routerLoad = - Object.values(this._peers).filter((peer) => peer.routerId === router.id).length; + Object.values(this._peers).filter((peer) => peer.routerId === routerId).length; if (routerLoad < load) { - id = router.id; + id = routerId; load = routerLoad; } } From 8c8a00f126cdf062ccb20f0c3cef932c9cce6ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 4 May 2020 23:53:38 +0200 Subject: [PATCH 140/158] Remove config option that is not used anymore --- server/config/config.example.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index ee41c3d..e8960f7 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -246,17 +246,11 @@ module.exports = }, // When truthy, the room will be open to all users when as long as there // are allready users in the room - activateOnHostJoin : true, - // If this is set to true, only signed-in users will be able - // to join a room directly. Non-signed-in users (guests) will - // always be put in the lobby regardless of room lock status. - // If false, there is no difference between guests and signed-in - // users when joining. - requireSignInToAccess : true, + activateOnHostJoin : true, // Room size before spreading to new router - routerScaleSize : 20, + routerScaleSize : 20, // Mediasoup settings - mediasoup : + mediasoup : { numWorkers : Object.keys(os.cpus()).length, // mediasoup Worker settings. From e039423dd5476d605ce7d452a072d71e10039ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 5 May 2020 00:53:31 +0200 Subject: [PATCH 141/158] Fix lint --- app/public/config/config.example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index cf2703d..3f0ce49 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -33,7 +33,7 @@ var config = */ audioOutputSupportedBrowsers : [ - 'chrome', + 'chrome', 'opera' ], // Socket.io request timeout From ab5893dbdf6bb9e26f689bc98c31fd78bf15628d Mon Sep 17 00:00:00 2001 From: Astagor Date: Tue, 5 May 2020 08:02:11 +0200 Subject: [PATCH 142/158] Fixed lint --- app/src/components/JoinDialog.js | 16 ++++++++-------- app/src/reducers/room.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 8a2bc23..3308622 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -286,14 +286,14 @@ const JoinDialog = ({ fullWidth /> {!room.inLobby && room.overRoomLimit && - - - + + + } diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index a4bbfb4..6d47d42 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -6,7 +6,7 @@ const initialState = locked : false, inLobby : false, signInRequired : false, - overRoomLimit : false, + overRoomLimit : false, // access code to the room if locked and joinByAccessCode == true accessCode : '', // if true: accessCode is a possibility to open the room From 7e6795986efe166bd9e1c2a085178a8f6bbe763c Mon Sep 17 00:00:00 2001 From: Astagor Date: Tue, 5 May 2020 08:08:35 +0200 Subject: [PATCH 143/158] Fixed lint server --- server/lib/Room.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index d43bf24..ec8febf 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -214,7 +214,8 @@ class Room extends EventEmitter peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) ) this._peerJoining(peer); - else if ('maxUsersPerRoom' in config &&(this._getJoinedPeers().length + this._lobby.peerList().length) >= config.maxUsersPerRoom) { + else if ('maxUsersPerRoom' in config &&(this._getJoinedPeers().length + this._lobby.peerList().length) >= config.maxUsersPerRoom) + { this._handleOverRoomLimit(peer); } else if (this._locked) From 5b8f2d83a9059713057be777bf7f66b4a8a7b69c Mon Sep 17 00:00:00 2001 From: Astagor Date: Tue, 5 May 2020 08:25:33 +0200 Subject: [PATCH 144/158] Added a div to wrapp buttons in FileSharing --- .../MeetingDrawer/FileSharing/FileSharing.js | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index 66a0d15..e74f0be 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -25,6 +25,10 @@ const styles = (theme) => button : { margin : theme.spacing(1) + }, + shareButtonsWrapper : + { + display : 'flex' } }); @@ -72,45 +76,47 @@ const FileSharing = (props) => return ( - (e.target.value = null)} - id='share-files-button' - /> - - - { - (browser.platform === 'mobile') && canShareFiles && canShare && - } +
+ (e.target.value = null)} + id='share-files-button' + /> + + + { + (browser.platform === 'mobile') && canShareFiles && canShare && + } +
); From 897b99cdbe0b1f81dd97cae050faa2791e684ab3 Mon Sep 17 00:00:00 2001 From: Astagor Date: Tue, 5 May 2020 08:31:45 +0200 Subject: [PATCH 145/158] Fixed lint --- .../MeetingDrawer/FileSharing/FileSharing.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index e74f0be..010aa1b 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -28,7 +28,7 @@ const styles = (theme) => }, shareButtonsWrapper : { - display : 'flex' + display : 'flex' } }); @@ -91,7 +91,7 @@ const FileSharing = (props) => type='file' disabled={!canShare} onChange={handleFileChange} - accept="image/*" + accept='image/*' id='share-files-gallery-button' /> { - (browser.platform === 'mobile') && canShareFiles && canShare && + (browser.platform === 'mobile') && canShareFiles && canShare && }
@@ -124,7 +124,7 @@ const FileSharing = (props) => FileSharing.propTypes = { roomClient : PropTypes.any.isRequired, - browser : PropTypes.object.isRequired, + browser : PropTypes.object.isRequired, canShareFiles : PropTypes.bool.isRequired, tabOpen : PropTypes.bool.isRequired, canShare : PropTypes.bool.isRequired, @@ -135,7 +135,7 @@ const mapStateToProps = (state) => { return { canShareFiles : state.me.canShareFiles, - browser : state.me.browser, + browser : state.me.browser, tabOpen : state.toolarea.currentToolTab === 'files', canShare : state.me.roles.some((role) => From e950ec9dbe16ac5990393a660f7222d721794813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Fri, 24 Apr 2020 23:42:59 +0200 Subject: [PATCH 146/158] Add an error handler to Express to dump OIDC errors with uuid --- server/server.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/server.js b/server/server.js index c3aec39..5188fd1 100755 --- a/server/server.js +++ b/server/server.js @@ -35,6 +35,7 @@ const RedisStore = require('connect-redis')(expressSession); const sharedSession = require('express-socket.io-session'); const interactiveServer = require('./lib/interactiveServer'); const promExporter = require('./lib/promExporter'); +const { v4: uuidv4 } = require('uuid'); /* eslint-disable no-console */ console.log('- process.env.DEBUG:', process.env.DEBUG); @@ -157,6 +158,24 @@ async function run() // Run WebSocketServer. await runWebSocketServer(); + function errorHandler(err, req, res, next) + { + const trackingId = uuidv4(); + + res.status(500).send( + `

Internal Server Error

+

If you report this error, please also report this + tracking ID which makes it possible to locate your session + in the logs which are available to the system administrator: + ${trackingId}

` + ); + logger.error( + 'Express error handler dump with tracking ID: %s, error dump: %o', + trackingId, err); + } + + app.use(errorHandler); + // Log rooms status every 30 seconds. setInterval(() => { From a46de5ff54c6642300d06d834881d086a08d45ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Mon, 4 May 2020 02:32:06 +0200 Subject: [PATCH 147/158] eslint disable unused vars for next --- server/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.js b/server/server.js index 5188fd1..673c6e5 100755 --- a/server/server.js +++ b/server/server.js @@ -158,6 +158,7 @@ async function run() // Run WebSocketServer. await runWebSocketServer(); + // eslint-disable-next-line no-unused-vars function errorHandler(err, req, res, next) { const trackingId = uuidv4(); From e2421f094fa0083f78d1dcff99736169e4103695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 09:48:26 +0200 Subject: [PATCH 148/158] Shuffle workers to get routers on random cores. Increase routerScaleSize default to 40. --- server/config/config.example.js | 5 +++-- server/lib/Room.js | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 5b47582..9d27f11 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -247,10 +247,11 @@ module.exports = // When truthy, the room will be open to all users when as long as there // are allready users in the room activateOnHostJoin : true, - // When set, maxUsersPerRoom defines how many users can join a single room. If not set, there is not limit. + // When set, maxUsersPerRoom defines how many users can join + // a single room. If not set, there is no limit. // maxUsersPerRoom : 20, // Room size before spreading to new router - routerScaleSize : 20, + routerScaleSize : 40, // Mediasoup settings mediasoup : { diff --git a/server/lib/Room.js b/server/lib/Room.js index ec8febf..1d30944 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -31,7 +31,7 @@ const permissionsFromRoles = ...config.permissionsFromRoles }; -const ROUTER_SCALE_SIZE = config.routerScaleSize || 20; +const ROUTER_SCALE_SIZE = config.routerScaleSize || 40; class Room extends EventEmitter { @@ -48,6 +48,9 @@ class Room extends EventEmitter { logger.info('create() [roomId:"%s"]', roomId); + // Shuffle workers to get random cores + let shuffledWorkers = mediasoupWorkers.sort(() => Math.random() - 0.5); + // Router media codecs. const mediaCodecs = config.mediasoup.router.mediaCodecs; @@ -55,7 +58,7 @@ class Room extends EventEmitter let firstRouter = null; - for (const worker of mediasoupWorkers) + for (const worker of shuffledWorkers) { const router = await worker.createRouter({ mediaCodecs }); @@ -74,6 +77,7 @@ class Room extends EventEmitter }); firstRouter = null; + shuffledWorkers = null; return new Room({ roomId, mediasoupRouters, audioLevelObserver }); } From 7adcef19a3815fc71ae1a5116a18e9039580cce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 09:56:21 +0200 Subject: [PATCH 149/158] Change to total number of peers, not just joined. Styling. --- server/lib/Room.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 1d30944..0f89b4d 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -218,7 +218,12 @@ class Room extends EventEmitter peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) ) this._peerJoining(peer); - else if ('maxUsersPerRoom' in config &&(this._getJoinedPeers().length + this._lobby.peerList().length) >= config.maxUsersPerRoom) + else if ( + 'maxUsersPerRoom' in config && + ( + Object.keys(this._peers).length + + this._lobby.peerList().length + ) >= config.maxUsersPerRoom) { this._handleOverRoomLimit(peer); } From 65f47d4b8dfd9f5ae4ba3dce3dd26e6b5fb97b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 10:31:41 +0200 Subject: [PATCH 150/158] Fix screensharing on Edge --- app/src/ScreenShare.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/ScreenShare.js b/app/src/ScreenShare.js index 180fe2a..2ff1bcc 100644 --- a/app/src/ScreenShare.js +++ b/app/src/ScreenShare.js @@ -225,10 +225,7 @@ export default class ScreenShare return new DisplayMediaScreenShare(); } case 'chrome': - { - return new DisplayMediaScreenShare(); - } - case 'msedge': + case 'edge': { return new DisplayMediaScreenShare(); } From dd6016e855a10b45aa81c69dfd816402aacd47d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 13:06:16 +0200 Subject: [PATCH 151/158] Remove unused function --- server/server.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/server/server.js b/server/server.js index 673c6e5..42112fe 100755 --- a/server/server.js +++ b/server/server.js @@ -56,10 +56,6 @@ if ('StatusLogger' in config) // @type {Array} const mediasoupWorkers = []; -// Index of next mediasoup Worker to use. -// @type {Number} -let nextMediasoupWorkerIdx = 0; - // Map of Room instances indexed by roomId. const rooms = new Map(); @@ -639,19 +635,6 @@ async function runMediasoupWorkers() } } -/** - * Get next mediasoup Worker. - */ -function getMediasoupWorker() -{ - const worker = mediasoupWorkers[nextMediasoupWorkerIdx]; - - if (++nextMediasoupWorkerIdx === mediasoupWorkers.length) - nextMediasoupWorkerIdx = 0; - - return worker; -} - /** * Get a Room instance (or create one if it does not exist). */ From dd9c0bb8971041754ad337dee1eb4f91495ee2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 5 May 2020 13:36:32 +0200 Subject: [PATCH 152/158] Update hungarian translation with the missing ones --- app/src/translations/hu.json | 66 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e21ebf3..e185cfb 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -4,9 +4,9 @@ "socket.reconnected": "Sikeres újarkapcsolódás", "socket.requestError": "Sikertelen szerver lekérés", - "room.chooseRoom": null, + "room.chooseRoom": "Válaszd ki a konferenciaszobát", "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", - "room.consentUnderstand": "I understand", + "room.consentUnderstand": "Megértettem", "room.joined": "Csatlakozátál a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához", "room.youLocked": "A konferenciába való belépés letiltva", @@ -40,7 +40,7 @@ "room.audioVideo": "Hang és Videó", "room.youAreReady": "Ok, kész vagy", "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", - "room.locketWait": "A konferencia szobába a a belépés tilos - Várj amíg valaki be nem enged ...", + "room.locketWait": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...", "room.lobbyAdministration": "Előszoba adminisztráció", "room.peersInLobby": "Résztvevők az előszobában", "room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában", @@ -49,22 +49,22 @@ "room.spotlights": "Látható résztvevők", "room.passive": "Passzív résztvevők", "room.videoPaused": "Ez a videóstream szünetel", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, - "room.clearChat": null, - "room.clearFileSharing": null, - "room.speechUnsupported": null, - "room.moderatoractions": null, - "room.raisedHand": null, - "room.loweredHand": null, - "room.extraVideo": null, - "room.overRoomLimit": null, + "room.muteAll": "Mindenki némítása", + "room.stopAllVideo": "Mindenki video némítása", + "room.closeMeeting": "Konferencia lebontása", + "room.clearChat": "Chat történelem kiürítése", + "room.clearFileSharing": "File megosztás kiürítése", + "room.speechUnsupported": "A böngésződ nem támogatja a hangszint", + "room.moderatoractions": "Moderátor funkciók", + "room.raisedHand": "{displayName} jelentkezik", + "room.loweredHand": "{displayName} leeresztette a kezét", + "room.extraVideo": "Kiegészítő videó", + "room.overRoomLimit": "A konferenciaszoba betelt..", - "me.mutedPTT": null, + "me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a szóköz billentyűt", - "roles.gotRole": null, - "roles.lostRole": null, + "roles.gotRole": "{role} szerepet kaptál", + "roles.lostRole": "Elvesztetted a {role} szerepet", "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", @@ -76,10 +76,10 @@ "tooltip.lobby": "Az előszobában várakozók listája", "tooltip.settings": "Beállítások", "tooltip.participants": "Résztvevők", - "tooltip.kickParticipant": null, - "tooltip.muteParticipant": null, - "tooltip.muteParticipantVideo": null, - "tooltip.raisedHand": null, + "tooltip.kickParticipant": "Résztvevő kirúgása", + "tooltip.muteParticipant": "Résztvevő némítása", + "tooltip.muteParticipantVideo": "Résztvevő video némítása", + "tooltip.raisedHand": "Jelentkezés", "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", @@ -93,7 +93,7 @@ "label.filesharing": "Fájl megosztás", "label.participants": "Résztvevők", "label.shareFile": "Fájl megosztása", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Fájl megosztás galériából", "label.fileSharingUnsupported": "Fájl megosztás nem támogatott", "label.unknown": "Ismeretlen", "label.democratic": "Egyforma képméretű képkiosztás", @@ -104,11 +104,11 @@ "label.veryHigh": "Nagyon magas (FHD)", "label.ultra": "Ultra magas (UHD)", "label.close": "Bezár", - "label.media": null, - "label.appearence": null, - "label.advanced": null, - "label.addVideo": null, - "label.promoteAllPeers": null, + "label.media": "Média", + "label.appearence": "Megjelenés", + "label.advanced": "Részletek", + "label.addVideo": "Videó hozzáadása", + "label.promoteAllPeers": "Mindenkit beengedek", "settings.settings": "Beállítások", "settings.camera": "Kamera", @@ -126,8 +126,8 @@ "settings.advancedMode": "Részletes információk", "settings.permanentTopBar": "Állandó felső sáv", "settings.lastn": "A látható videók száma", - "settings.hiddenControls": null, - "settings.notificationSounds": null, + "settings.hiddenControls": "Média Gombok automatikus elrejtése", + "settings.notificationSounds": "Értesítések hangjelzjéssel", "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", @@ -169,8 +169,8 @@ "devices.cameraDisconnected": "A kamera kapcsolata lebomlott", "devices.cameraError": "Hiba történt a kamera elérése során", - "moderator.clearChat": null, - "moderator.clearFiles": null, - "moderator.muteAudio": null, - "moderator.muteVideo": null + "moderator.clearChat": "A moderátor kiürítette a chat történelmet", + "moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet", + "moderator.muteAudio": "A moderátor elnémította a hangod", + "moderator.muteVideo": "A moderátor elnémította a videód" } From 3f75a6c50638aa1e855e5d77e2f30a998ea2c5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 13:43:32 +0200 Subject: [PATCH 153/158] Fix icon colors --- app/src/components/AccessControl/LockDialog/ListLobbyPeer.js | 1 + app/src/components/MeetingDrawer/ParticipantList/ListMe.js | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js index 8f9843a..9e73e82 100644 --- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js +++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js @@ -61,6 +61,7 @@ const ListLobbyPeer = (props) => peer.promotionInProgress || promotionInProgress } + color='primary' onClick={(e) => { e.stopPropagation(); diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index 762af00..a32412f 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -65,6 +65,7 @@ const ListMe = (props) => })} className={me.raisedHand ? classes.green : null} disabled={me.raisedHandInProgress} + color='primary' onClick={(e) => { e.stopPropagation(); From 2bb64596b0193ca22731db16263b60a483d63246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 14:29:54 +0200 Subject: [PATCH 154/158] Fix button sizes and take care not to overflow container in filmstrip, ref #115 --- app/src/components/Containers/Me.js | 373 +++++++++++++------ app/src/components/Containers/Peer.js | 305 ++++++++++----- app/src/components/MeetingViews/Filmstrip.js | 6 +- 3 files changed, 474 insertions(+), 210 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 5e8e0dc..68ca46d 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -10,6 +10,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; import VideoView from '../VideoContainers/VideoView'; import Volume from './Volume'; import Fab from '@material-ui/core/Fab'; +import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import MicIcon from '@material-ui/icons/Mic'; import MicOffIcon from '@material-ui/icons/MicOff'; @@ -59,6 +60,19 @@ const styles = (theme) => margin : theme.spacing(1), pointerEvents : 'auto' }, + smallContainer : + { + backgroundColor : 'rgba(255, 255, 255, 0.9)', + margin : theme.spacing(0.25), + padding : theme.spacing(0.75), + boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)', + pointerEvents : 'auto', + transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + '&:hover' : + { + backgroundColor : 'rgba(213, 213, 213, 1)' + } + }, viewContainer : { position : 'relative', @@ -102,6 +116,10 @@ const styles = (theme) => '&.hover' : { opacity : 1 + }, + '&.smallContainer' : + { + fontSize : '3em' } } }, @@ -145,7 +163,7 @@ const Me = (props) => activeSpeaker, spacing, style, - smallButtons, + smallContainer, advancedMode, micProducer, webcamProducer, @@ -300,16 +318,18 @@ const Me = (props) => style={spacingStyle} >
-
- -
+ { !smallContainer && +
+ +
+ }
}, 2000); }} > -

+

- - { - if (micState === 'off') - roomClient.enableMic(); - else if (micState === 'on') - roomClient.muteMic(); - else - roomClient.unmuteMic(); - }} - > - { micState === 'on' ? - - : - - } - + { smallContainer ? + + { + if (micState === 'off') + roomClient.enableMic(); + else if (micState === 'on') + roomClient.muteMic(); + else + roomClient.unmuteMic(); + }} + > + { micState === 'on' ? + + : + + } + + : + + { + if (micState === 'off') + roomClient.enableMic(); + else if (micState === 'on') + roomClient.muteMic(); + else + roomClient.unmuteMic(); + }} + > + { micState === 'on' ? + + : + + } + + }
- - { - webcamState === 'on' ? - roomClient.disableWebcam() : - roomClient.enableWebcam(); - }} - > - { webcamState === 'on' ? - - : - - } - + { smallContainer ? + + { + webcamState === 'on' ? + roomClient.disableWebcam() : + roomClient.enableWebcam(); + }} + > + { webcamState === 'on' ? + + : + + } + + : + + { + webcamState === 'on' ? + roomClient.disableWebcam() : + roomClient.enableWebcam(); + }} + > + { webcamState === 'on' ? + + : + + } + + }
{ me.browser.platform !== 'mobile' &&
- - { - switch (screenState) - { - case 'on': - { - roomClient.disableScreenSharing(); - break; - } - case 'off': - { - roomClient.enableScreenSharing(); - break; - } - default: - { - break; - } + { smallContainer ? + - { (screenState === 'on' || screenState === 'unsupported') && - - } - { screenState === 'off' && - - } - + color='primary' + size='small' + onClick={() => + { + switch (screenState) + { + case 'on': + { + roomClient.disableScreenSharing(); + break; + } + case 'off': + { + roomClient.enableScreenSharing(); + break; + } + default: + { + break; + } + } + }} + > + { (screenState === 'on' || screenState === 'unsupported') && + + } + { screenState === 'off' && + + } + + + : + + { + switch (screenState) + { + case 'on': + { + roomClient.disableScreenSharing(); + break; + } + case 'off': + { + roomClient.enableScreenSharing(); + break; + } + default: + { + break; + } + } + }} + > + { (screenState === 'on' || screenState === 'unsupported') && + + } + { screenState === 'off' && + + } + + }
} @@ -537,21 +660,41 @@ const Me = (props) =>
- - { - roomClient.disableExtraVideo(producer.id); - }} - > - - + { smallContainer ? + + { + roomClient.disableExtraVideo(producer.id); + }} + > + + + + : + + { + roomClient.disableExtraVideo(producer.id); + }} + > + + + }
@@ -663,7 +806,7 @@ Me.propTypes = extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer), spacing : PropTypes.number, style : PropTypes.object, - smallButtons : PropTypes.bool, + smallContainer : PropTypes.bool, canShareScreen : PropTypes.bool.isRequired, classes : PropTypes.object.isRequired, theme : PropTypes.object.isRequired diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js index 3e6e776..2715c64 100644 --- a/app/src/components/Containers/Peer.js +++ b/app/src/components/Containers/Peer.js @@ -12,6 +12,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; import VideoView from '../VideoContainers/VideoView'; import Tooltip from '@material-ui/core/Tooltip'; import Fab from '@material-ui/core/Fab'; +import IconButton from '@material-ui/core/IconButton'; import VolumeUpIcon from '@material-ui/icons/VolumeUp'; import VolumeOffIcon from '@material-ui/icons/VolumeOff'; import NewWindowIcon from '@material-ui/icons/OpenInNew'; @@ -59,6 +60,19 @@ const styles = (theme) => { margin : theme.spacing(1) }, + smallContainer : + { + backgroundColor : 'rgba(255, 255, 255, 0.9)', + margin : theme.spacing(0.25), + padding : theme.spacing(0.75), + boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)', + pointerEvents : 'auto', + transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + '&:hover' : + { + backgroundColor : 'rgba(213, 213, 213, 1)' + } + }, viewContainer : { position : 'relative', @@ -130,7 +144,7 @@ const Peer = (props) => toggleConsumerWindow, spacing, style, - smallButtons, + smallContainer, windowConsumer, classes, theme @@ -236,28 +250,53 @@ const Peer = (props) => placement={smallScreen ? 'top' : 'left'} >
- - { - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} - > - { micEnabled ? - - : - - } - + { smallContainer ? + + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + : + + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + }
@@ -270,24 +309,46 @@ const Peer = (props) => placement={smallScreen ? 'top' : 'left'} >
- - { - toggleConsumerWindow(webcamConsumer); - }} - > - - + { smallContainer ? + + { + toggleConsumerWindow(webcamConsumer); + }} + > + + + : + + { + toggleConsumerWindow(webcamConsumer); + }} + > + + + }
} @@ -300,21 +361,40 @@ const Peer = (props) => placement={smallScreen ? 'top' : 'left'} >
- - { - toggleConsumerFullscreen(webcamConsumer); - }} - > - - + { smallContainer ? + + { + toggleConsumerFullscreen(webcamConsumer); + }} + > + + + : + + { + toggleConsumerFullscreen(webcamConsumer); + }} + > + + + }
@@ -428,24 +508,46 @@ const Peer = (props) => placement={smallScreen ? 'top' : 'left'} >
- - { - toggleConsumerWindow(consumer); - }} - > - - + { smallContainer ? + + { + toggleConsumerWindow(consumer); + }} + > + + + : + + { + toggleConsumerWindow(consumer); + }} + > + + + }
} @@ -458,21 +560,40 @@ const Peer = (props) => placement={smallScreen ? 'top' : 'left'} >
- - { - toggleConsumerFullscreen(consumer); - }} - > - - + { smallContainer ? + + { + toggleConsumerFullscreen(consumer); + }} + > + + + : + + { + toggleConsumerFullscreen(consumer); + }} + > + + + }
@@ -584,7 +705,7 @@ const Peer = (props) => !screenVisible || (windowConsumer === screenConsumer.id) } - size={smallButtons ? 'small' : 'large'} + size={smallContainer ? 'small' : 'large'} onClick={() => { toggleConsumerWindow(screenConsumer); @@ -611,7 +732,7 @@ const Peer = (props) => })} className={classes.fab} disabled={!screenVisible} - size={smallButtons ? 'small' : 'large'} + size={smallContainer ? 'small' : 'large'} onClick={() => { toggleConsumerFullscreen(screenConsumer); @@ -670,7 +791,7 @@ Peer.propTypes = browser : PropTypes.object.isRequired, spacing : PropTypes.number, style : PropTypes.object, - smallButtons : PropTypes.bool, + smallContainer : PropTypes.bool, toggleConsumerFullscreen : PropTypes.func.isRequired, toggleConsumerWindow : PropTypes.func.isRequired, classes : PropTypes.object.isRequired, diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index f78e5b5..a1a3cbe 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -49,7 +49,7 @@ const styles = () => }, '&.active' : { - opacity : '0.6' + borderColor : 'var(--selected-peer-border-color)' } }, hiddenToolBar : @@ -279,7 +279,7 @@ class Filmstrip extends React.PureComponent @@ -302,7 +302,7 @@ class Filmstrip extends React.PureComponent advancedMode={advancedMode} id={peerId} style={peerStyle} - smallButtons + smallContainer /> From bf837b3398857b996273a150f5e01b4e5ab669d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 14:31:02 +0200 Subject: [PATCH 155/158] Fix color on kick button --- app/src/components/MeetingDrawer/ParticipantList/ListPeer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index d4b1409..914ae04 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -173,6 +173,7 @@ const ListPeer = (props) => defaultMessage : 'Kick out participant' })} disabled={peer.peerKickInProgress} + color='secondary' onClick={(e) => { e.stopPropagation(); From 678abe05d0078a0af481ff529e043805957fc1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 15:00:58 +0200 Subject: [PATCH 156/158] Update translations --- app/src/components/JoinDialog.js | 2 +- app/src/components/MeetingDrawer/FileSharing/FileSharing.js | 2 +- app/src/translations/en.json | 4 ++-- app/src/translations/nb.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 3308622..3d2596f 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -290,7 +290,7 @@ const JoinDialog = ({ diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index 010aa1b..78ba569 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -65,7 +65,7 @@ const FileSharing = (props) => const buttonGalleryDescription = canShareFiles ? intl.formatMessage({ id : 'label.shareGalleryFile', - defaultMessage : 'Share from gallery' + defaultMessage : 'Share image' }) : intl.formatMessage({ diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 758e9f0..19c46b4 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -59,7 +59,7 @@ "room.raisedHand": "{displayName} raised their hand", "room.loweredHand": "{displayName} put their hand down", "room.extraVideo": "Extra video", - "room.overRoomLimit": null, + "room.overRoomLimit": "The room is full, retry after some time.", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", @@ -93,7 +93,7 @@ "label.filesharing": "File sharing", "label.participants": "Participants", "label.shareFile": "Share file", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Share image", "label.fileSharingUnsupported": "File sharing not supported", "label.unknown": "Unknown", "label.democratic": "Democratic view", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 38ccd74..2965f59 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -59,7 +59,7 @@ "room.raisedHand": "{displayName} rakk opp hånden", "room.loweredHand": "{displayName} tok ned hånden", "room.extraVideo": "Ekstra video", - "room.overRoomLimit": null, + "room.overRoomLimit": "Rommet er fullt, prøv igjen om litt.", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", @@ -93,7 +93,7 @@ "label.filesharing": "Fildeling", "label.participants": "Deltakere", "label.shareFile": "Del fil", - "label.shareGalleryFile": null, + "label.shareGalleryFile": "Del bilde", "label.fileSharingUnsupported": "Fildeling ikke støttet", "label.unknown": "Ukjent", "label.democratic": "Demokratisk", From e2211b000c1f61366b23092d6f37441accc7d187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 15:06:18 +0200 Subject: [PATCH 157/158] Fix button scaling in filmstrip --- app/src/components/Containers/Me.js | 4 ++-- app/src/components/Containers/Peer.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 68ca46d..f368089 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -63,8 +63,8 @@ const styles = (theme) => smallContainer : { backgroundColor : 'rgba(255, 255, 255, 0.9)', - margin : theme.spacing(0.25), - padding : theme.spacing(0.75), + margin : '0.5vmin', + padding : '0.5vmin', boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)', pointerEvents : 'auto', transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js index 2715c64..4fc495e 100644 --- a/app/src/components/Containers/Peer.js +++ b/app/src/components/Containers/Peer.js @@ -63,8 +63,8 @@ const styles = (theme) => smallContainer : { backgroundColor : 'rgba(255, 255, 255, 0.9)', - margin : theme.spacing(0.25), - padding : theme.spacing(0.75), + margin : '0.5vmin', + padding : '0.5vmin', boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)', pointerEvents : 'auto', transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', From 15fe4521b7fb6c4274e4271715170d7cffb55d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 15:15:26 +0200 Subject: [PATCH 158/158] Fix bug on resizing filmstrip --- app/src/components/MeetingViews/Filmstrip.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index a1a3cbe..d1cfba6 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -123,6 +123,9 @@ class Filmstrip extends React.PureComponent const root = this.rootContainer.current; + if (!root) + return; + const availableWidth = root.clientWidth; // Grid is: // 4/5 speaker