commit
e679509c56
|
@ -4,18 +4,13 @@ import Invite from './modules/UI/invite/Invite';
|
||||||
import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
|
import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
|
||||||
|
|
||||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||||
|
|
||||||
import ConnectionQuality from './modules/connectionquality/connectionquality';
|
|
||||||
|
|
||||||
import Recorder from './modules/recorder/Recorder';
|
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 mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
||||||
|
|
||||||
import {reportError} from './modules/util/helpers';
|
import {reportError} from './modules/util/helpers';
|
||||||
|
|
||||||
|
import UIEvents from './service/UI/UIEvents';
|
||||||
import UIUtil from './modules/UI/util/UIUtil';
|
import UIUtil from './modules/UI/util/UIUtil';
|
||||||
|
|
||||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||||
|
@ -27,12 +22,9 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
|
||||||
const TrackEvents = JitsiMeetJS.events.track;
|
const TrackEvents = JitsiMeetJS.events.track;
|
||||||
const TrackErrors = JitsiMeetJS.errors.track;
|
const TrackErrors = JitsiMeetJS.errors.track;
|
||||||
|
|
||||||
let room, connection, localAudio, localVideo;
|
const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
|
||||||
|
|
||||||
/**
|
let room, connection, localAudio, localVideo;
|
||||||
* Indicates whether the connection is interrupted or not.
|
|
||||||
*/
|
|
||||||
let connectionIsInterrupted = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether extension external installation is in progress or not.
|
* 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.
|
* Known custom conference commands.
|
||||||
*/
|
*/
|
||||||
const commands = {
|
const commands = {
|
||||||
CONNECTION_QUALITY: "stats",
|
|
||||||
EMAIL: "email",
|
EMAIL: "email",
|
||||||
AVATAR_URL: "avatar-url",
|
AVATAR_URL: "avatar-url",
|
||||||
AVATAR_ID: "avatar-id",
|
AVATAR_ID: "avatar-id",
|
||||||
|
@ -685,7 +676,7 @@ export default {
|
||||||
* false otherwise.
|
* false otherwise.
|
||||||
*/
|
*/
|
||||||
isConnectionInterrupted () {
|
isConnectionInterrupted () {
|
||||||
return connectionIsInterrupted;
|
return this._room.isConnectionInterrupted();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Finds JitsiParticipant for given id.
|
* Finds JitsiParticipant for given id.
|
||||||
|
@ -774,7 +765,7 @@ export default {
|
||||||
* Returns the stats.
|
* Returns the stats.
|
||||||
*/
|
*/
|
||||||
getStats() {
|
getStats() {
|
||||||
return ConnectionQuality.getStats();
|
return room.connectionQuality.getStats();
|
||||||
},
|
},
|
||||||
// end used by torture
|
// end used by torture
|
||||||
|
|
||||||
|
@ -1108,7 +1099,6 @@ export default {
|
||||||
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
|
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
|
||||||
APP.UI.mucJoined();
|
APP.UI.mucJoined();
|
||||||
APP.API.notifyConferenceJoined(APP.conference.roomName);
|
APP.API.notifyConferenceJoined(APP.conference.roomName);
|
||||||
connectionIsInterrupted = false;
|
|
||||||
APP.UI.markVideoInterrupted(false);
|
APP.UI.markVideoInterrupted(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1257,13 +1247,10 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||||
connectionIsInterrupted = true;
|
|
||||||
ConnectionQuality.updateLocalConnectionQuality(0);
|
|
||||||
APP.UI.showLocalConnectionInterrupted(true);
|
APP.UI.showLocalConnectionInterrupted(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
||||||
connectionIsInterrupted = false;
|
|
||||||
APP.UI.showLocalConnectionInterrupted(false);
|
APP.UI.showLocalConnectionInterrupted(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1315,59 +1302,16 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
room.on(ConferenceEvents.CONNECTION_STATS, function (stats) {
|
room.on(ConnectionQualityEvents.LOCAL_STATS_UPDATED,
|
||||||
// if we say video muted we will use old method of calculating
|
(stats) => {
|
||||||
// quality and will not depend on localVideo if it is missing
|
APP.UI.updateLocalStats(stats.connectionQuality, stats);
|
||||||
ConnectionQuality.updateLocalStats(
|
|
||||||
stats,
|
|
||||||
connectionIsInterrupted,
|
|
||||||
localVideo ? localVideo.videoType : undefined,
|
|
||||||
localVideo ? localVideo.isMuted() : true,
|
|
||||||
localVideo ? localVideo.resolution : null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ConnectionQuality.addListener(CQEvents.LOCALSTATS_UPDATED,
|
room.on(ConnectionQualityEvents.REMOTE_STATS_UPDATED,
|
||||||
(percent, stats) => {
|
(id, stats) => {
|
||||||
APP.UI.updateLocalStats(percent, stats);
|
APP.UI.updateRemoteStats(id, stats.connectionQuality, 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.addCommandListener(this.commands.defaults.ETHERPAD, ({value}) => {
|
room.addCommandListener(this.commands.defaults.ETHERPAD, ({value}) => {
|
||||||
APP.UI.initEtherpad(value);
|
APP.UI.initEtherpad(value);
|
||||||
|
@ -1378,9 +1322,10 @@ export default {
|
||||||
APP.UI.setUserEmail(from, data.value);
|
APP.UI.setUserEmail(from, data.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
room.addCommandListener(this.commands.defaults.AVATAR_URL,
|
room.addCommandListener(
|
||||||
(data, from) => {
|
this.commands.defaults.AVATAR_URL,
|
||||||
APP.UI.setUserAvatarUrl(from, data.value);
|
(data, from) => {
|
||||||
|
APP.UI.setUserAvatarUrl(from, data.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
room.addCommandListener(this.commands.defaults.AVATAR_ID,
|
room.addCommandListener(this.commands.defaults.AVATAR_ID,
|
||||||
|
|
|
@ -4,6 +4,23 @@ import JitsiPopover from "../util/JitsiPopover";
|
||||||
import VideoLayout from "./VideoLayout";
|
import VideoLayout from "./VideoLayout";
|
||||||
import UIUtil from "../util/UIUtil";
|
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.
|
* Constructs new connection indicator.
|
||||||
* @param videoContainer the video container associated with the indicator.
|
* @param videoContainer the video container associated with the indicator.
|
||||||
|
@ -24,24 +41,6 @@ function ConnectionIndicator(videoContainer, videoId) {
|
||||||
this.create();
|
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) {
|
ConnectionIndicator.getIP = function(value) {
|
||||||
return value.substring(0, value.lastIndexOf(":"));
|
return value.substring(0, value.lastIndexOf(":"));
|
||||||
};
|
};
|
||||||
|
@ -360,12 +359,10 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||||
this.resolution = object.resolution;
|
this.resolution = object.resolution;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var quality in ConnectionIndicator.connectionQualityValues) {
|
|
||||||
if (percent >= quality) {
|
let width = qualityToWidth.find(x => percent >= x.percent);
|
||||||
this.fullIcon.style.width =
|
this.fullIcon.style.width = width.width;
|
||||||
ConnectionIndicator.connectionQualityValues[quality];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (object && typeof object.isResolutionHD === 'boolean') {
|
if (object && typeof object.isResolutionHD === 'boolean') {
|
||||||
this.isResolutionHD = object.isResolutionHD;
|
this.isResolutionHD = object.isResolutionHD;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,7 +0,0 @@
|
||||||
var CQEvents = {
|
|
||||||
LOCALSTATS_UPDATED: "cq.localstats_updated",
|
|
||||||
REMOTESTATS_UPDATED: "cq.remotestats_updated",
|
|
||||||
STOP: "cq.stop"
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = CQEvents;
|
|
Loading…
Reference in New Issue