diff --git a/conference.js b/conference.js index b91fba106..a9a2a569a 100644 --- a/conference.js +++ b/conference.js @@ -4,18 +4,13 @@ import Invite from './modules/UI/invite/Invite'; import ContactList from './modules/UI/side_pannels/contactlist/ContactList'; import AuthHandler from './modules/UI/authentication/AuthHandler'; - -import ConnectionQuality from './modules/connectionquality/connectionquality'; - import Recorder from './modules/recorder/Recorder'; -import CQEvents from './service/connectionquality/CQEvents'; -import UIEvents from './service/UI/UIEvents'; - import mediaDeviceHelper from './modules/devices/mediaDeviceHelper'; import {reportError} from './modules/util/helpers'; +import UIEvents from './service/UI/UIEvents'; import UIUtil from './modules/UI/util/UIUtil'; const ConnectionEvents = JitsiMeetJS.events.connection; @@ -27,12 +22,9 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; const TrackEvents = JitsiMeetJS.events.track; const TrackErrors = JitsiMeetJS.errors.track; -let room, connection, localAudio, localVideo; +const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality; -/** - * Indicates whether the connection is interrupted or not. - */ -let connectionIsInterrupted = false; +let room, connection, localAudio, localVideo; /** * Indicates whether extension external installation is in progress or not. @@ -45,7 +37,6 @@ import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer"; * Known custom conference commands. */ const commands = { - CONNECTION_QUALITY: "stats", EMAIL: "email", AVATAR_URL: "avatar-url", AVATAR_ID: "avatar-id", @@ -685,7 +676,7 @@ export default { * false otherwise. */ isConnectionInterrupted () { - return connectionIsInterrupted; + return this._room.isConnectionInterrupted(); }, /** * Finds JitsiParticipant for given id. @@ -774,7 +765,7 @@ export default { * Returns the stats. */ getStats() { - return ConnectionQuality.getStats(); + return room.connectionQuality.getStats(); }, // end used by torture @@ -1108,7 +1099,6 @@ export default { room.on(ConferenceEvents.CONFERENCE_JOINED, () => { APP.UI.mucJoined(); APP.API.notifyConferenceJoined(APP.conference.roomName); - connectionIsInterrupted = false; APP.UI.markVideoInterrupted(false); }); @@ -1257,13 +1247,10 @@ export default { } room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => { - connectionIsInterrupted = true; - ConnectionQuality.updateLocalConnectionQuality(0); APP.UI.showLocalConnectionInterrupted(true); }); room.on(ConferenceEvents.CONNECTION_RESTORED, () => { - connectionIsInterrupted = false; APP.UI.showLocalConnectionInterrupted(false); }); @@ -1315,59 +1302,16 @@ export default { }); } - room.on(ConferenceEvents.CONNECTION_STATS, function (stats) { - // if we say video muted we will use old method of calculating - // quality and will not depend on localVideo if it is missing - ConnectionQuality.updateLocalStats( - stats, - connectionIsInterrupted, - localVideo ? localVideo.videoType : undefined, - localVideo ? localVideo.isMuted() : true, - localVideo ? localVideo.resolution : null); + room.on(ConnectionQualityEvents.LOCAL_STATS_UPDATED, + (stats) => { + APP.UI.updateLocalStats(stats.connectionQuality, stats); + }); - ConnectionQuality.addListener(CQEvents.LOCALSTATS_UPDATED, - (percent, stats) => { - APP.UI.updateLocalStats(percent, stats); - // Send only the data that remote participants care about. - let data = { - bitrate: stats.bitrate, - packetLoss: stats.packetLoss}; - if (localVideo && localVideo.resolution) { - data.resolution = localVideo.resolution; - } - - try { - room.broadcastEndpointMessage({ - type: this.commands.defaults.CONNECTION_QUALITY, - values: data }); - } catch (e) { - reportError(e); - } - }); - - room.on(ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - (participant, payload) => { - switch(payload.type) { - case this.commands.defaults.CONNECTION_QUALITY: { - let remoteVideo = participant.getTracks() - .find(tr => tr.isVideoTrack()); - ConnectionQuality.updateRemoteStats( - participant.getId(), - payload.values, - remoteVideo ? remoteVideo.videoType : undefined, - remoteVideo ? remoteVideo.isMuted() : undefined); - break; - } - default: - console.warn("Unknown datachannel message", payload); - } - }); - - ConnectionQuality.addListener(CQEvents.REMOTESTATS_UPDATED, - (id, percent, stats) => { - APP.UI.updateRemoteStats(id, percent, stats); - }); + room.on(ConnectionQualityEvents.REMOTE_STATS_UPDATED, + (id, stats) => { + APP.UI.updateRemoteStats(id, stats.connectionQuality, stats); + }); room.addCommandListener(this.commands.defaults.ETHERPAD, ({value}) => { APP.UI.initEtherpad(value); @@ -1378,9 +1322,10 @@ export default { APP.UI.setUserEmail(from, data.value); }); - room.addCommandListener(this.commands.defaults.AVATAR_URL, - (data, from) => { - APP.UI.setUserAvatarUrl(from, data.value); + room.addCommandListener( + this.commands.defaults.AVATAR_URL, + (data, from) => { + APP.UI.setUserAvatarUrl(from, data.value); }); room.addCommandListener(this.commands.defaults.AVATAR_ID, diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index 829c7c97d..18bff07ee 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -4,6 +4,23 @@ import JitsiPopover from "../util/JitsiPopover"; import VideoLayout from "./VideoLayout"; import UIUtil from "../util/UIUtil"; +/** + * Maps a connection quality value (in percent) to the width of the "full" icon. + */ +const qualityToWidth = [ + // Full (5 bars) + {percent: 80, width: "100%"}, + // 4 bars + {percent: 60, width: "80%"}, + // 3 bars + {percent: 40, width: "55%"}, + // 2 bars + {percent: 20, width: "40%"}, + // 1 bar + {percent: 0, width: "20%"} + // Note: we never show 0 bars. +]; + /** * Constructs new connection indicator. * @param videoContainer the video container associated with the indicator. @@ -24,24 +41,6 @@ function ConnectionIndicator(videoContainer, videoId) { this.create(); } -/** - * Values for the connection quality - * @type {{98: string, - * 81: string, - * 64: string, - * 47: string, - * 30: string, - * 0: string}} - */ -ConnectionIndicator.connectionQualityValues = { - 98: "100%", //full - 81: "80%",//4 bars - 64: "55%",//3 bars - 47: "40%",//2 bars - 30: "20%",//1 bar - 0: "0"//empty -}; - ConnectionIndicator.getIP = function(value) { return value.substring(0, value.lastIndexOf(":")); }; @@ -360,12 +359,10 @@ ConnectionIndicator.prototype.updateConnectionQuality = this.resolution = object.resolution; } } - for (var quality in ConnectionIndicator.connectionQualityValues) { - if (percent >= quality) { - this.fullIcon.style.width = - ConnectionIndicator.connectionQualityValues[quality]; - } - } + + let width = qualityToWidth.find(x => percent >= x.percent); + this.fullIcon.style.width = width.width; + if (object && typeof object.isResolutionHD === 'boolean') { this.isResolutionHD = object.isResolutionHD; } diff --git a/modules/connectionquality/connectionquality.js b/modules/connectionquality/connectionquality.js deleted file mode 100644 index 443a0492e..000000000 --- a/modules/connectionquality/connectionquality.js +++ /dev/null @@ -1,196 +0,0 @@ -/* global config */ -import EventEmitter from "events"; - -import CQEvents from "../../service/connectionquality/CQEvents"; - -const eventEmitter = new EventEmitter(); - -/** - * local stats - * @type {{}} - */ -var stats = {}; - -/** - * remote stats - * @type {{}} - */ -var remoteStats = {}; - -/** - * Quality percent( 100% - good, 0% - bad.) for the local user. - */ -var localConnectionQuality = 100; - -/** - * Quality percent( 100% - good, 0% - bad.) stored per id. - */ -var remoteConnectionQuality = {}; - -/** - * Calculates the quality percent based on passed new and old value. - * @param newVal the new value - * @param oldVal the old value - */ -function calculateQuality(newVal, oldVal) { - return (newVal <= oldVal) ? newVal : (9*oldVal + newVal) / 10; -} - -// webrtc table describing simulcast resolutions and used bandwidth -// https://chromium.googlesource.com/external/webrtc/+/master/webrtc/media/engine/simulcast.cc#42 -const _bandwidthMap = [ - { width: 1920, height: 1080, layers:3, max: 5000, min: 800 }, - { width: 1280, height: 720, layers:3, max: 2500, min: 600 }, - { width: 960, height: 540, layers:3, max: 900, min: 450 }, - { width: 640, height: 360, layers:2, max: 700, min: 150 }, - { width: 480, height: 270, layers:2, max: 450, min: 150 }, - { width: 320, height: 180, layers:1, max: 200, min: 30 } -]; - -/** - * We disable quality calculations based on bandwidth if simulcast is disabled, - * or enable it in case of no simulcast and we force it. - * @type {boolean} - */ -const disableQualityBasedOnBandwidth = - config.forceQualityBasedOnBandwidth ? false : config.disableSimulcast; - -/** - * Calculates the quality percentage based on the input resolution height and - * the upload reported by the client. The value is based on the interval from - * _bandwidthMap. - * @param inputHeight the resolution used to open the camera. - * @param upload the upload rate reported by client. - * @returns {int} the percent of upload based on _bandwidthMap and maximum value - * of 100, as values of the map are approximate and clients can stream above - * those values. Returns undefined if no result is found. - */ -function calculateQualityUsingUpload(inputHeight, upload) { - // found resolution from _bandwidthMap which height is equal or less than - // the inputHeight - let foundResolution = _bandwidthMap.find((r) => (r.height <= inputHeight)); - - if (!foundResolution) - return undefined; - - if (upload <= foundResolution.min) - return 0; - - return Math.min( - ((upload - foundResolution.min)*100) - / (foundResolution.max - foundResolution.min), - 100); -} - -export default { - /** - * Updates the local statistics - * @param data new statistics - * @param dontUpdateLocalConnectionQuality {boolean} if true - - * localConnectionQuality wont be recalculated. - * @param videoType the local video type - * @param isMuted current state of local video, whether it is muted - * @param resolution the current resolution used by local video - */ - updateLocalStats: - function (data, dontUpdateLocalConnectionQuality, - videoType, isMuted, resolution) { - stats = data; - if(!dontUpdateLocalConnectionQuality) { - let val = this._getNewQualityValue( - stats, - localConnectionQuality, - videoType, - isMuted, - resolution); - if (val !== undefined) - localConnectionQuality = val; - } - eventEmitter.emit( - CQEvents.LOCALSTATS_UPDATED, localConnectionQuality, stats); - }, - - /** - * Updates only the localConnectionQuality value - * @param values {int} the new value. should be from 0 - 100. - */ - updateLocalConnectionQuality: function (value) { - localConnectionQuality = value; - eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, localConnectionQuality, - stats); - }, - - /** - * Updates remote statistics - * @param id the id associated with the statistics - * @param data the statistics received - * @param remoteVideoType the video type of the remote video - * @param isRemoteVideoMuted whether remote video is muted - */ - updateRemoteStats: - function (id, data, remoteVideoType, isRemoteVideoMuted) { - if (!data || - !("packetLoss" in data) || - !("total" in data.packetLoss)) { - eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, id, null, null); - return; - } - - let inputResolution = data.resolution; - // Use only the fields we need - data = {bitrate: data.bitrate, packetLoss: data.packetLoss}; - - remoteStats[id] = data; - - let val = this._getNewQualityValue( - data, - remoteConnectionQuality[id], - remoteVideoType, - isRemoteVideoMuted, - inputResolution); - if (val !== undefined) - remoteConnectionQuality[id] = val; - - eventEmitter.emit( - CQEvents.REMOTESTATS_UPDATED, id, - remoteConnectionQuality[id], remoteStats[id]); - }, - - /** - * Returns the new quality value based on the input parameters. - * Used to calculate remote and local values. - * @param data the data - * @param lastQualityValue the last value we calculated - * @param videoType need to check whether we are screen sharing - * @param isMuted is video muted - * @param resolution the input resolution used by the camera - * @returns {*} the newly calculated value or undefined if no result - * @private - */ - _getNewQualityValue: - function (data, lastQualityValue, videoType, isMuted, resolution) { - if (disableQualityBasedOnBandwidth - || isMuted - || videoType === 'desktop' - || !resolution) { - return calculateQuality( - 100 - data.packetLoss.total, - lastQualityValue || 100); - } else { - return calculateQualityUsingUpload( - resolution, - data.bitrate.upload); - } - }, - - /** - * Returns the local statistics. - */ - getStats: function () { - return stats; - }, - - addListener: function (type, listener) { - eventEmitter.on(type, listener); - } -}; diff --git a/service/connectionquality/CQEvents.js b/service/connectionquality/CQEvents.js deleted file mode 100644 index 8eb85c397..000000000 --- a/service/connectionquality/CQEvents.js +++ /dev/null @@ -1,7 +0,0 @@ -var CQEvents = { - LOCALSTATS_UPDATED: "cq.localstats_updated", - REMOTESTATS_UPDATED: "cq.remotestats_updated", - STOP: "cq.stop" -}; - -module.exports = CQEvents; \ No newline at end of file