use statistics module from the library
This commit is contained in:
parent
103b5d71d0
commit
d19789005e
6
app.js
6
app.js
|
@ -17,7 +17,6 @@ import URLProcessor from "./modules/config/URLProcessor";
|
|||
import RoomnameGenerator from './modules/util/RoomnameGenerator';
|
||||
|
||||
import UI from "./modules/UI/UI";
|
||||
import statistics from "./modules/statistics/statistics";
|
||||
import settings from "./modules/settings/Settings";
|
||||
import conference from './conference';
|
||||
import API from './modules/API/API';
|
||||
|
@ -59,13 +58,10 @@ function buildRoomName () {
|
|||
|
||||
const APP = {
|
||||
UI,
|
||||
statistics,
|
||||
settings,
|
||||
conference,
|
||||
API,
|
||||
init () {
|
||||
this.connectionquality =
|
||||
require("./modules/connectionquality/connectionquality");
|
||||
this.desktopsharing =
|
||||
require("./modules/desktopsharing/desktopsharing");
|
||||
this.keyboardshortcut =
|
||||
|
@ -87,8 +83,6 @@ function init() {
|
|||
});
|
||||
|
||||
APP.desktopsharing.init(JitsiMeetJS.isDesktopSharingEnabled());
|
||||
APP.statistics.start();
|
||||
APP.connectionquality.init();
|
||||
APP.keyboardshortcut.init();
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
|
|
|
@ -5,6 +5,8 @@ import createRoomLocker from './modules/UI/authentication/RoomLocker';
|
|||
//FIXME:
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
|
||||
import ConnectionQuality from './modules/connectionquality/connectionquality';
|
||||
|
||||
import CQEvents from './service/connectionquality/CQEvents';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
import DSEvents from './service/desktopsharing/DesktopSharingEventTypes';
|
||||
|
@ -154,6 +156,7 @@ class ConferenceConnector {
|
|||
case ConferenceErrors.CONFERENCE_DESTROYED:
|
||||
{
|
||||
let [reason] = params;
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyConferenceDestroyed(reason);
|
||||
}
|
||||
break;
|
||||
|
@ -556,6 +559,7 @@ export default {
|
|||
});
|
||||
|
||||
room.on(ConferenceEvents.KICKED, () => {
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyKicked();
|
||||
// FIXME close
|
||||
});
|
||||
|
@ -599,14 +603,17 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
APP.connectionquality.addListener(
|
||||
room.on(ConferenceEvents.CONNECTION_STATS, function (stats) {
|
||||
ConnectionQuality.updateLocalStats(stats);
|
||||
});
|
||||
ConnectionQuality.addListener(
|
||||
CQEvents.LOCALSTATS_UPDATED,
|
||||
(percent, stats) => {
|
||||
APP.UI.updateLocalStats(percent, stats);
|
||||
|
||||
// send local stats to other users
|
||||
room.sendCommandOnce(Commands.CONNECTION_QUALITY, {
|
||||
children: APP.connectionquality.convertToMUCStats(stats),
|
||||
children: ConnectionQuality.convertToMUCStats(stats),
|
||||
attributes: {
|
||||
xmlns: 'http://jitsi.org/jitmeet/stats'
|
||||
}
|
||||
|
@ -614,17 +621,12 @@ export default {
|
|||
}
|
||||
);
|
||||
|
||||
APP.connectionquality.addListener(CQEvents.STOP, () => {
|
||||
APP.UI.hideStats();
|
||||
room.removeCommand(Commands.CONNECTION_QUALITY);
|
||||
});
|
||||
|
||||
// listen to remote stats
|
||||
room.addCommandListener(Commands.CONNECTION_QUALITY,(values, from) => {
|
||||
APP.connectionquality.updateRemoteStats(from, values);
|
||||
ConnectionQuality.updateRemoteStats(from, values);
|
||||
});
|
||||
|
||||
APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
|
||||
ConnectionQuality.addListener(CQEvents.REMOTESTATS_UPDATED,
|
||||
(id, percent, stats) => {
|
||||
APP.UI.updateRemoteStats(id, percent, stats);
|
||||
});
|
||||
|
|
|
@ -59,7 +59,7 @@ ConnectionIndicator.getStringFromArray = function (array) {
|
|||
* @returns {string} the html content.
|
||||
*/
|
||||
ConnectionIndicator.prototype.generateText = function () {
|
||||
var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
|
||||
var downloadBitrate, uploadBitrate, packetLoss, i;
|
||||
|
||||
var translate = APP.translation.translateString;
|
||||
|
||||
|
@ -86,44 +86,12 @@ ConnectionIndicator.prototype.generateText = function () {
|
|||
"%";
|
||||
}
|
||||
|
||||
var resolutionValue = null;
|
||||
if(this.resolution && this.id) {
|
||||
var keys = Object.keys(this.resolution);
|
||||
for(var ssrc in this.resolution) {
|
||||
// skip resolutions for ssrc that don't have this info
|
||||
// like receive-only ssrc for FF
|
||||
if(this.resolution[ssrc]
|
||||
&& this.resolution[ssrc].height != -1
|
||||
&& this.resolution[ssrc].width != -1)
|
||||
resolutionValue = this.resolution[ssrc];
|
||||
}
|
||||
}
|
||||
|
||||
if(this.id === null) {
|
||||
resolution = "";
|
||||
if(this.resolution === null || !Object.keys(this.resolution) ||
|
||||
Object.keys(this.resolution).length === 0) {
|
||||
resolution = "N/A";
|
||||
} else {
|
||||
for (i in this.resolution) {
|
||||
resolutionValue = this.resolution[i];
|
||||
if (resolutionValue) {
|
||||
if (resolutionValue.height &&
|
||||
resolutionValue.width) {
|
||||
resolution += (resolution === "" ? "" : ", ") +
|
||||
resolutionValue.width + "x" +
|
||||
resolutionValue.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(!resolutionValue ||
|
||||
!resolutionValue.height ||
|
||||
!resolutionValue.width) {
|
||||
resolution = "N/A";
|
||||
} else {
|
||||
resolution = resolutionValue.width + "x" + resolutionValue.height;
|
||||
}
|
||||
// GENERATE RESOLUTIONS STRING
|
||||
let resolutions = this.resolution || {};
|
||||
let resolutionStr = Object.keys(resolutions).map(function (ssrc) {
|
||||
let {width, height} = resolutions[ssrc];
|
||||
return `${width}x${height}`;
|
||||
}).join(', ') || 'N/A';
|
||||
|
||||
var result = "<table style='width:100%'>" +
|
||||
"<tr>" +
|
||||
|
@ -139,7 +107,7 @@ ConnectionIndicator.prototype.generateText = function () {
|
|||
"</tr><tr>" +
|
||||
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.resolution'>" +
|
||||
translate("connectionindicator.resolution") + "</span></td>" +
|
||||
"<td>" + resolution + "</td></tr></table>";
|
||||
"<td>" + resolutionStr + "</td></tr></table>";
|
||||
|
||||
if(this.videoContainer.videoSpanId == "localVideoContainer") {
|
||||
result += "<div class=\"jitsipopover_showmore\" " +
|
||||
|
|
|
@ -728,13 +728,9 @@ var VideoLayout = {
|
|||
* @param object
|
||||
*/
|
||||
updateLocalConnectionStats (percent, object) {
|
||||
let resolutions = {};
|
||||
if (object.resolution !== null) {
|
||||
resolutions = object.resolution;
|
||||
var id = Strophe.getResourceFromJid(
|
||||
APP.conference._room.room.session.me);
|
||||
object.resolution = resolutions[id];
|
||||
}
|
||||
let resolutions = object.resolution;
|
||||
|
||||
object.resolution = resolutions[APP.conference.localId];
|
||||
localVideoThumbnail.updateStatsIndicator(percent, object);
|
||||
|
||||
Object.keys(resolutions).forEach(function (id) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
/* global APP, require */
|
||||
/* jshint -W101 */
|
||||
var EventEmitter = require("events");
|
||||
var eventEmitter = new EventEmitter();
|
||||
var CQEvents = require("../../service/connectionquality/CQEvents");
|
||||
var StatisticsEvents = require("../../service/statistics/Events");
|
||||
import EventEmitter from "events";
|
||||
|
||||
import CQEvents from "../../service/connectionquality/CQEvents";
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
/**
|
||||
* local stats
|
||||
|
@ -50,16 +51,7 @@ function parseMUCStats(stats) {
|
|||
};
|
||||
}
|
||||
|
||||
var ConnectionQuality = {
|
||||
init: function () {
|
||||
APP.statistics.addListener(
|
||||
StatisticsEvents.CONNECTION_STATS, this.updateLocalStats
|
||||
);
|
||||
APP.statistics.addListener(
|
||||
StatisticsEvents.STOP, this.stopSendingStats
|
||||
);
|
||||
},
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Updates the local statistics
|
||||
* @param data new statistics
|
||||
|
@ -87,14 +79,6 @@ var ConnectionQuality = {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops statistics sending.
|
||||
*/
|
||||
stopSendingStats: function () {
|
||||
//notify UI about stopping statistics gathering
|
||||
eventEmitter.emit(CQEvents.STOP);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the local statistics.
|
||||
*/
|
||||
|
@ -125,5 +109,3 @@ var ConnectionQuality = {
|
|||
];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ConnectionQuality;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global config JitsiMeetJS */
|
||||
/* global config, JitsiMeetJS */
|
||||
|
||||
// Load the integration of a third-party analytics API such as Google Analytics.
|
||||
// Since we cannot guarantee the quality of the third-party service (e.g. their
|
||||
|
|
|
@ -1,664 +0,0 @@
|
|||
/* global require, ssrc2jid */
|
||||
/* jshint -W117 */
|
||||
/* jshint -W101 */
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
var StatisticsEvents = require("../../service/statistics/Events");
|
||||
|
||||
/* Whether we support the browser we are running into for logging statistics */
|
||||
var browserSupported = RTCBrowserType.isChrome() ||
|
||||
RTCBrowserType.isOpera() || RTCBrowserType.isFirefox();
|
||||
/**
|
||||
* Calculates packet lost percent using the number of lost packets and the
|
||||
* number of all packet.
|
||||
* @param lostPackets the number of lost packets
|
||||
* @param totalPackets the number of all packets.
|
||||
* @returns {number} packet loss percent
|
||||
*/
|
||||
function calculatePacketLoss(lostPackets, totalPackets) {
|
||||
if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
|
||||
return 0;
|
||||
return Math.round((lostPackets/totalPackets)*100);
|
||||
}
|
||||
|
||||
function getStatValue(item, name) {
|
||||
var browserType = RTCBrowserType.getBrowserType();
|
||||
if (!keyMap[browserType][name])
|
||||
throw "The property isn't supported!";
|
||||
var key = keyMap[browserType][name];
|
||||
return (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) ?
|
||||
item.stat(key) : item[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Peer statistics data holder.
|
||||
* @constructor
|
||||
*/
|
||||
function PeerStats()
|
||||
{
|
||||
this.ssrc2Loss = {};
|
||||
this.ssrc2AudioLevel = {};
|
||||
this.ssrc2bitrate = {};
|
||||
this.ssrc2resolution = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* The bandwidth
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.bandwidth = {};
|
||||
|
||||
/**
|
||||
* The bit rate
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.bitrate = {};
|
||||
|
||||
/**
|
||||
* The packet loss rate
|
||||
* @type {{}}
|
||||
*/
|
||||
PeerStats.packetLoss = null;
|
||||
|
||||
/**
|
||||
* Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
|
||||
* represented by this instance.
|
||||
* @param ssrc audio or video RTP stream SSRC.
|
||||
* @param lossRate new packet loss rate value to be set.
|
||||
*/
|
||||
PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
|
||||
{
|
||||
this.ssrc2Loss[ssrc] = lossRate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets resolution for given <tt>ssrc</tt> that belong to the peer
|
||||
* represented by this instance.
|
||||
* @param ssrc audio or video RTP stream SSRC.
|
||||
* @param resolution new resolution value to be set.
|
||||
*/
|
||||
PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
|
||||
{
|
||||
if(resolution === null && this.ssrc2resolution[ssrc])
|
||||
{
|
||||
delete this.ssrc2resolution[ssrc];
|
||||
}
|
||||
else if(resolution !== null)
|
||||
this.ssrc2resolution[ssrc] = resolution;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
|
||||
* represented by this instance.
|
||||
* @param ssrc audio or video RTP stream SSRC.
|
||||
* @param bitrate new bitrate value to be set.
|
||||
*/
|
||||
PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
|
||||
{
|
||||
if(this.ssrc2bitrate[ssrc])
|
||||
{
|
||||
this.ssrc2bitrate[ssrc].download += bitrate.download;
|
||||
this.ssrc2bitrate[ssrc].upload += bitrate.upload;
|
||||
}
|
||||
else {
|
||||
this.ssrc2bitrate[ssrc] = bitrate;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
|
||||
* the stream which belongs to the peer represented by this instance.
|
||||
* @param ssrc RTP stream SSRC for which current audio level value will be
|
||||
* updated.
|
||||
* @param audioLevel the new audio level value to be set. Value is truncated to
|
||||
* fit the range from 0 to 1.
|
||||
*/
|
||||
PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
|
||||
{
|
||||
// Range limit 0 - 1
|
||||
this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
|
||||
};
|
||||
|
||||
function formatAudioLevel(audioLevel) {
|
||||
return Math.min(Math.max(audioLevel, 0), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array with the transport information.
|
||||
* @type {Array}
|
||||
*/
|
||||
PeerStats.transport = [];
|
||||
|
||||
|
||||
/**
|
||||
* <tt>StatsCollector</tt> registers for stats updates of given
|
||||
* <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
|
||||
* stats are extracted and put in {@link PeerStats} objects. Once the processing
|
||||
* is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt>
|
||||
* instance as an event source.
|
||||
*
|
||||
* @param peerconnection webRTC peer connection object.
|
||||
* @param interval stats refresh interval given in ms.
|
||||
* @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
|
||||
* called on stats update.
|
||||
* @constructor
|
||||
*/
|
||||
function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
|
||||
{
|
||||
this.peerconnection = peerconnection;
|
||||
this.baselineAudioLevelsReport = null;
|
||||
this.currentStatsReport = null;
|
||||
this.baselineStatsReport = null;
|
||||
this.audioLevelsIntervalId = null;
|
||||
this.eventEmitter = eventEmitter;
|
||||
|
||||
/**
|
||||
* Gather PeerConnection stats once every this many milliseconds.
|
||||
*/
|
||||
this.GATHER_INTERVAL = 15000;
|
||||
|
||||
/**
|
||||
* Log stats via the focus once every this many milliseconds.
|
||||
*/
|
||||
this.LOG_INTERVAL = 60000;
|
||||
|
||||
/**
|
||||
* Gather stats and store them in this.statsToBeLogged.
|
||||
*/
|
||||
this.gatherStatsIntervalId = null;
|
||||
|
||||
/**
|
||||
* Send the stats already saved in this.statsToBeLogged to be logged via
|
||||
* the focus.
|
||||
*/
|
||||
this.logStatsIntervalId = null;
|
||||
|
||||
/**
|
||||
* Stores the statistics which will be send to the focus to be logged.
|
||||
*/
|
||||
this.statsToBeLogged =
|
||||
{
|
||||
timestamps: [],
|
||||
stats: {}
|
||||
};
|
||||
|
||||
// Updates stats interval
|
||||
this.audioLevelsIntervalMilis = audioLevelsInterval;
|
||||
|
||||
this.statsIntervalId = null;
|
||||
this.statsIntervalMilis = statsInterval;
|
||||
// Map of jids to PeerStats
|
||||
this.jid2stats = {};
|
||||
}
|
||||
|
||||
module.exports = StatsCollector;
|
||||
|
||||
/**
|
||||
* Stops stats updates.
|
||||
*/
|
||||
StatsCollector.prototype.stop = function () {
|
||||
if (this.audioLevelsIntervalId) {
|
||||
clearInterval(this.audioLevelsIntervalId);
|
||||
this.audioLevelsIntervalId = null;
|
||||
}
|
||||
|
||||
if (this.statsIntervalId)
|
||||
{
|
||||
clearInterval(this.statsIntervalId);
|
||||
this.statsIntervalId = null;
|
||||
}
|
||||
|
||||
if(this.logStatsIntervalId)
|
||||
{
|
||||
clearInterval(this.logStatsIntervalId);
|
||||
this.logStatsIntervalId = null;
|
||||
}
|
||||
|
||||
if(this.gatherStatsIntervalId)
|
||||
{
|
||||
clearInterval(this.gatherStatsIntervalId);
|
||||
this.gatherStatsIntervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback passed to <tt>getStats</tt> method.
|
||||
* @param error an error that occurred on <tt>getStats</tt> call.
|
||||
*/
|
||||
StatsCollector.prototype.errorCallback = function (error)
|
||||
{
|
||||
console.error("Get stats error", error);
|
||||
this.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts stats updates.
|
||||
*/
|
||||
StatsCollector.prototype.start = function ()
|
||||
{
|
||||
var self = this;
|
||||
if (!config.disableAudioLevels) {
|
||||
this.audioLevelsIntervalId = setInterval(
|
||||
function () {
|
||||
// Interval updates
|
||||
self.peerconnection.getStats(
|
||||
function (report) {
|
||||
var results = null;
|
||||
if (!report || !report.result ||
|
||||
typeof report.result != 'function') {
|
||||
results = report;
|
||||
}
|
||||
else {
|
||||
results = report.result();
|
||||
}
|
||||
//console.error("Got interval report", results);
|
||||
self.baselineAudioLevelsReport = results;
|
||||
},
|
||||
self.errorCallback
|
||||
);
|
||||
},
|
||||
self.audioLevelsIntervalMilis
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.disableStats && browserSupported) {
|
||||
this.statsIntervalId = setInterval(
|
||||
function () {
|
||||
// Interval updates
|
||||
self.peerconnection.getStats(
|
||||
function (report) {
|
||||
var results = null;
|
||||
if (!report || !report.result ||
|
||||
typeof report.result != 'function') {
|
||||
//firefox
|
||||
results = report;
|
||||
}
|
||||
else {
|
||||
//chrome
|
||||
results = report.result();
|
||||
}
|
||||
//console.error("Got interval report", results);
|
||||
self.currentStatsReport = results;
|
||||
try {
|
||||
self.processStatsReport();
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Unsupported key:" + e, e);
|
||||
}
|
||||
|
||||
self.baselineStatsReport = self.currentStatsReport;
|
||||
},
|
||||
self.errorCallback
|
||||
);
|
||||
},
|
||||
self.statsIntervalMilis
|
||||
);
|
||||
}
|
||||
|
||||
// Logging statistics does not support firefox
|
||||
if (config.logStats && (browserSupported && !RTCBrowserType.isFirefox())) {
|
||||
this.gatherStatsIntervalId = setInterval(
|
||||
function () {
|
||||
self.peerconnection.getStats(
|
||||
function (report) {
|
||||
self.addStatsToBeLogged(report.result());
|
||||
},
|
||||
function () {
|
||||
}
|
||||
);
|
||||
},
|
||||
this.GATHER_INTERVAL
|
||||
);
|
||||
|
||||
this.logStatsIntervalId = setInterval(
|
||||
function() { self.logStats(); },
|
||||
this.LOG_INTERVAL);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a certain record should be included in the logged statistics.
|
||||
*/
|
||||
function acceptStat(reportId, reportType, statName) {
|
||||
if (reportType == "googCandidatePair" && statName == "googChannelId")
|
||||
return false;
|
||||
|
||||
if (reportType == "ssrc") {
|
||||
if (statName == "googTrackId" ||
|
||||
statName == "transportId" ||
|
||||
statName == "ssrc")
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a certain record should be included in the logged statistics.
|
||||
*/
|
||||
function acceptReport(id, type) {
|
||||
if (id.substring(0, 15) == "googCertificate" ||
|
||||
id.substring(0, 9) == "googTrack" ||
|
||||
id.substring(0, 20) == "googLibjingleSession")
|
||||
return false;
|
||||
|
||||
if (type == "googComponent")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the stats to the format used for logging, and saves the data in
|
||||
* this.statsToBeLogged.
|
||||
* @param reports Reports as given by webkitRTCPerConnection.getStats.
|
||||
*/
|
||||
StatsCollector.prototype.addStatsToBeLogged = function (reports) {
|
||||
var self = this;
|
||||
var num_records = this.statsToBeLogged.timestamps.length;
|
||||
this.statsToBeLogged.timestamps.push(new Date().getTime());
|
||||
reports.map(function (report) {
|
||||
if (!acceptReport(report.id, report.type))
|
||||
return;
|
||||
var stat = self.statsToBeLogged.stats[report.id];
|
||||
if (!stat) {
|
||||
stat = self.statsToBeLogged.stats[report.id] = {};
|
||||
}
|
||||
stat.type = report.type;
|
||||
report.names().map(function (name) {
|
||||
if (!acceptStat(report.id, report.type, name))
|
||||
return;
|
||||
var values = stat[name];
|
||||
if (!values) {
|
||||
values = stat[name] = [];
|
||||
}
|
||||
while (values.length < num_records) {
|
||||
values.push(null);
|
||||
}
|
||||
values.push(report.stat(name));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
StatsCollector.prototype.logStats = function () {
|
||||
|
||||
if(!APP.conference._room.xmpp.sendLogs(this.statsToBeLogged))
|
||||
return;
|
||||
// Reset the stats
|
||||
this.statsToBeLogged.stats = {};
|
||||
this.statsToBeLogged.timestamps = [];
|
||||
};
|
||||
var keyMap = {};
|
||||
keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
|
||||
"ssrc": "ssrc",
|
||||
"packetsReceived": "packetsReceived",
|
||||
"packetsLost": "packetsLost",
|
||||
"packetsSent": "packetsSent",
|
||||
"bytesReceived": "bytesReceived",
|
||||
"bytesSent": "bytesSent"
|
||||
};
|
||||
keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
|
||||
"receiveBandwidth": "googAvailableReceiveBandwidth",
|
||||
"sendBandwidth": "googAvailableSendBandwidth",
|
||||
"remoteAddress": "googRemoteAddress",
|
||||
"transportType": "googTransportType",
|
||||
"localAddress": "googLocalAddress",
|
||||
"activeConnection": "googActiveConnection",
|
||||
"ssrc": "ssrc",
|
||||
"packetsReceived": "packetsReceived",
|
||||
"packetsSent": "packetsSent",
|
||||
"packetsLost": "packetsLost",
|
||||
"bytesReceived": "bytesReceived",
|
||||
"bytesSent": "bytesSent",
|
||||
"googFrameHeightReceived": "googFrameHeightReceived",
|
||||
"googFrameWidthReceived": "googFrameWidthReceived",
|
||||
"googFrameHeightSent": "googFrameHeightSent",
|
||||
"googFrameWidthSent": "googFrameWidthSent",
|
||||
"audioInputLevel": "audioInputLevel",
|
||||
"audioOutputLevel": "audioOutputLevel"
|
||||
};
|
||||
keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
|
||||
keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
|
||||
|
||||
|
||||
/**
|
||||
* Stats processing logic.
|
||||
*/
|
||||
StatsCollector.prototype.processStatsReport = function () {
|
||||
if (!this.baselineStatsReport) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var idx in this.currentStatsReport) {
|
||||
var now = this.currentStatsReport[idx];
|
||||
try {
|
||||
if (getStatValue(now, 'receiveBandwidth') ||
|
||||
getStatValue(now, 'sendBandwidth')) {
|
||||
PeerStats.bandwidth = {
|
||||
"download": Math.round(
|
||||
(getStatValue(now, 'receiveBandwidth')) / 1000),
|
||||
"upload": Math.round(
|
||||
(getStatValue(now, 'sendBandwidth')) / 1000)
|
||||
};
|
||||
}
|
||||
}
|
||||
catch(e){/*not supported*/}
|
||||
|
||||
if(now.type == 'googCandidatePair')
|
||||
{
|
||||
var ip, type, localIP, active;
|
||||
try {
|
||||
ip = getStatValue(now, 'remoteAddress');
|
||||
type = getStatValue(now, "transportType");
|
||||
localIP = getStatValue(now, "localAddress");
|
||||
active = getStatValue(now, "activeConnection");
|
||||
}
|
||||
catch(e){/*not supported*/}
|
||||
if(!ip || !type || !localIP || active != "true")
|
||||
continue;
|
||||
var addressSaved = false;
|
||||
for(var i = 0; i < PeerStats.transport.length; i++)
|
||||
{
|
||||
if(PeerStats.transport[i].ip == ip &&
|
||||
PeerStats.transport[i].type == type &&
|
||||
PeerStats.transport[i].localip == localIP)
|
||||
{
|
||||
addressSaved = true;
|
||||
}
|
||||
}
|
||||
if(addressSaved)
|
||||
continue;
|
||||
PeerStats.transport.push({localip: localIP, ip: ip, type: type});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(now.type == "candidatepair")
|
||||
{
|
||||
if(now.state == "succeeded")
|
||||
continue;
|
||||
|
||||
var local = this.currentStatsReport[now.localCandidateId];
|
||||
var remote = this.currentStatsReport[now.remoteCandidateId];
|
||||
PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
|
||||
ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
|
||||
|
||||
}
|
||||
|
||||
if (now.type != 'ssrc' && now.type != "outboundrtp" &&
|
||||
now.type != "inboundrtp") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var before = this.baselineStatsReport[idx];
|
||||
if (!before) {
|
||||
console.warn(getStatValue(now, 'ssrc') + ' not enough data');
|
||||
continue;
|
||||
}
|
||||
|
||||
var ssrc = getStatValue(now, 'ssrc');
|
||||
if(!ssrc)
|
||||
continue;
|
||||
var jid = APP.conference._room.room.getJidBySSRC(ssrc);
|
||||
if (!jid && (Date.now() - now.timestamp) < 3000) {
|
||||
console.warn("No jid for ssrc: " + ssrc);
|
||||
continue;
|
||||
}
|
||||
|
||||
var jidStats = this.jid2stats[jid];
|
||||
if (!jidStats) {
|
||||
jidStats = new PeerStats();
|
||||
this.jid2stats[jid] = jidStats;
|
||||
}
|
||||
|
||||
|
||||
var isDownloadStream = true;
|
||||
var key = 'packetsReceived';
|
||||
var packetsNow = getStatValue(now, key);
|
||||
if (typeof packetsNow === 'undefined' || packetsNow === null) {
|
||||
isDownloadStream = false;
|
||||
key = 'packetsSent';
|
||||
packetsNow = getStatValue(now, key);
|
||||
if (typeof packetsNow === 'undefined' || packetsNow === null) {
|
||||
console.warn("No packetsReceived nor packetsSent stat found");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!packetsNow || packetsNow < 0)
|
||||
packetsNow = 0;
|
||||
|
||||
var packetsBefore = getStatValue(before, key);
|
||||
if (!packetsBefore || packetsBefore < 0)
|
||||
packetsBefore = 0;
|
||||
var packetRate = packetsNow - packetsBefore;
|
||||
if (!packetRate || packetRate < 0)
|
||||
packetRate = 0;
|
||||
var currentLoss = getStatValue(now, 'packetsLost');
|
||||
if (!currentLoss || currentLoss < 0)
|
||||
currentLoss = 0;
|
||||
var previousLoss = getStatValue(before, 'packetsLost');
|
||||
if (!previousLoss || previousLoss < 0)
|
||||
previousLoss = 0;
|
||||
var lossRate = currentLoss - previousLoss;
|
||||
if (!lossRate || lossRate < 0)
|
||||
lossRate = 0;
|
||||
var packetsTotal = (packetRate + lossRate);
|
||||
|
||||
jidStats.setSsrcLoss(ssrc,
|
||||
{"packetsTotal": packetsTotal,
|
||||
"packetsLost": lossRate,
|
||||
"isDownloadStream": isDownloadStream});
|
||||
|
||||
|
||||
var bytesReceived = 0, bytesSent = 0;
|
||||
if(getStatValue(now, "bytesReceived")) {
|
||||
bytesReceived = getStatValue(now, "bytesReceived") -
|
||||
getStatValue(before, "bytesReceived");
|
||||
}
|
||||
|
||||
if(getStatValue(now, "bytesSent")) {
|
||||
bytesSent = getStatValue(now, "bytesSent") -
|
||||
getStatValue(before, "bytesSent");
|
||||
}
|
||||
|
||||
var time = Math.round((now.timestamp - before.timestamp) / 1000);
|
||||
if(bytesReceived <= 0 || time <= 0) {
|
||||
bytesReceived = 0;
|
||||
} else {
|
||||
bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
|
||||
}
|
||||
|
||||
if(bytesSent <= 0 || time <= 0) {
|
||||
bytesSent = 0;
|
||||
} else {
|
||||
bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
|
||||
}
|
||||
|
||||
jidStats.setSsrcBitrate(ssrc, {
|
||||
"download": bytesReceived,
|
||||
"upload": bytesSent});
|
||||
|
||||
var resolution = {height: null, width: null};
|
||||
try {
|
||||
if (getStatValue(now, "googFrameHeightReceived") &&
|
||||
getStatValue(now, "googFrameWidthReceived")) {
|
||||
resolution.height = getStatValue(now, "googFrameHeightReceived");
|
||||
resolution.width = getStatValue(now, "googFrameWidthReceived");
|
||||
}
|
||||
else if (getStatValue(now, "googFrameHeightSent") &&
|
||||
getStatValue(now, "googFrameWidthSent")) {
|
||||
resolution.height = getStatValue(now, "googFrameHeightSent");
|
||||
resolution.width = getStatValue(now, "googFrameWidthSent");
|
||||
}
|
||||
}
|
||||
catch(e){/*not supported*/}
|
||||
|
||||
if(resolution.height && resolution.width) {
|
||||
jidStats.setSsrcResolution(ssrc, resolution);
|
||||
} else {
|
||||
jidStats.setSsrcResolution(ssrc, null);
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
// Jid stats
|
||||
var totalPackets = {download: 0, upload: 0};
|
||||
var lostPackets = {download: 0, upload: 0};
|
||||
var bitrateDownload = 0;
|
||||
var bitrateUpload = 0;
|
||||
var resolutions = {};
|
||||
Object.keys(this.jid2stats).forEach(
|
||||
function (jid) {
|
||||
Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
|
||||
function (ssrc) {
|
||||
var type = "upload";
|
||||
if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
|
||||
type = "download";
|
||||
totalPackets[type] +=
|
||||
self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
|
||||
lostPackets[type] +=
|
||||
self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
|
||||
}
|
||||
);
|
||||
Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
|
||||
function (ssrc) {
|
||||
bitrateDownload +=
|
||||
self.jid2stats[jid].ssrc2bitrate[ssrc].download;
|
||||
bitrateUpload +=
|
||||
self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
|
||||
|
||||
delete self.jid2stats[jid].ssrc2bitrate[ssrc];
|
||||
}
|
||||
);
|
||||
resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
|
||||
}
|
||||
);
|
||||
|
||||
PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
|
||||
|
||||
PeerStats.packetLoss = {
|
||||
total:
|
||||
calculatePacketLoss(lostPackets.download + lostPackets.upload,
|
||||
totalPackets.download + totalPackets.upload),
|
||||
download:
|
||||
calculatePacketLoss(lostPackets.download, totalPackets.download),
|
||||
upload:
|
||||
calculatePacketLoss(lostPackets.upload, totalPackets.upload)
|
||||
};
|
||||
|
||||
let idResolution = {};
|
||||
if (resolutions) { // use id instead of jid
|
||||
Object.keys(resolutions).forEach(function (jid) {
|
||||
let id = Strophe.getResourceFromJid(jid);
|
||||
idResolution[id] = resolutions[jid];
|
||||
});
|
||||
}
|
||||
this.eventEmitter.emit(StatisticsEvents.CONNECTION_STATS,
|
||||
{
|
||||
"bitrate": PeerStats.bitrate,
|
||||
"packetLoss": PeerStats.packetLoss,
|
||||
"bandwidth": PeerStats.bandwidth,
|
||||
"resolution": idResolution,
|
||||
"transport": PeerStats.transport
|
||||
});
|
||||
PeerStats.transport = [];
|
||||
|
||||
};
|
|
@ -1,71 +0,0 @@
|
|||
/* global require, APP */
|
||||
/**
|
||||
* Created by hristo on 8/4/14.
|
||||
*/
|
||||
var RTPStats = require("./RTPStatsCollector.js");
|
||||
var EventEmitter = require("events");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var StatisticsEvents = require("../../service/statistics/Events");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
|
||||
var rtpStats = null;
|
||||
|
||||
function stopRemote() {
|
||||
if (rtpStats) {
|
||||
rtpStats.stop();
|
||||
eventEmitter.emit(StatisticsEvents.STOP);
|
||||
rtpStats = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startRemoteStats (peerconnection) {
|
||||
if (rtpStats) {
|
||||
rtpStats.stop();
|
||||
}
|
||||
|
||||
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
|
||||
rtpStats.start();
|
||||
}
|
||||
|
||||
function onDisposeConference(onUnload) {
|
||||
stopRemote();
|
||||
if (onUnload) {
|
||||
eventEmitter.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Indicates that this audio level is for local jid.
|
||||
* @type {string}
|
||||
*/
|
||||
LOCAL_JID: 'local',
|
||||
|
||||
addListener: function(type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
},
|
||||
removeListener: function (type, listener) {
|
||||
eventEmitter.removeListener(type, listener);
|
||||
},
|
||||
stop: function () {
|
||||
stopRemote();
|
||||
if (eventEmitter) {
|
||||
eventEmitter.removeAllListeners();
|
||||
}
|
||||
},
|
||||
start: function () {
|
||||
const xmpp = APP.conference._room.xmpp;
|
||||
xmpp.addListener(
|
||||
XMPPEvents.DISPOSE_CONFERENCE,
|
||||
onDisposeConference
|
||||
);
|
||||
//FIXME: we may want to change CALL INCOMING event to
|
||||
// onnegotiationneeded
|
||||
xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
|
||||
startRemoteStats(event.peerconnection);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
var RTCEvents = {
|
||||
RTC_READY: "rtc.ready",
|
||||
DATA_CHANNEL_OPEN: "rtc.data_channel_open",
|
||||
CREATE_OFFER_FAILED: "rtc.create_offer_failed",
|
||||
CREATE_ANSWER_FAILED: "rtc.create_answer_failed",
|
||||
SET_LOCAL_DESCRIPTION_FAILED: "rtc.set_local_description_failed",
|
||||
SET_REMOTE_DESCRIPTION_FAILED: "rtc.set_remote_description_failed",
|
||||
ADD_ICE_CANDIDATE_FAILED: "rtc.add_ice_candidate_failed",
|
||||
GET_USER_MEDIA_FAILED: "rtc.get_user_media_failed",
|
||||
LASTN_CHANGED: "rtc.lastn_changed",
|
||||
DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
|
||||
LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
|
||||
AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
|
||||
AUDIO_MUTE: "rtc.audio_mute",
|
||||
VIDEO_MUTE: "rtc.video_mute"
|
||||
};
|
||||
|
||||
module.exports = RTCEvents;
|
|
@ -1,13 +0,0 @@
|
|||
var StreamEventTypes = {
|
||||
EVENT_TYPE_LOCAL_CREATED: "stream.local_created",
|
||||
|
||||
EVENT_TYPE_LOCAL_CHANGED: "stream.local_changed",
|
||||
|
||||
EVENT_TYPE_LOCAL_ENDED: "stream.local_ended",
|
||||
|
||||
EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
|
||||
|
||||
EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended"
|
||||
};
|
||||
|
||||
module.exports = StreamEventTypes;
|
|
@ -1,12 +0,0 @@
|
|||
var AuthenticationEvents = {
|
||||
/**
|
||||
* Event callback arguments:
|
||||
* function(authenticationEnabled, userIdentity)
|
||||
* authenticationEnabled - indicates whether authentication has been enabled
|
||||
* in this session
|
||||
* userIdentity - if user has been logged in then it contains user name. If
|
||||
* contains 'null' or 'undefined' then user is not logged in.
|
||||
*/
|
||||
IDENTITY_UPDATED: "authentication.identity_updated"
|
||||
};
|
||||
module.exports = AuthenticationEvents;
|
|
@ -1,14 +0,0 @@
|
|||
module.exports = {
|
||||
/**
|
||||
* An event carrying connection statistics.
|
||||
*/
|
||||
CONNECTION_STATS: "statistics.connectionstats",
|
||||
/**
|
||||
* FIXME: needs documentation.
|
||||
*/
|
||||
AUDIO_LEVEL: "statistics.audioLevel",
|
||||
/**
|
||||
* FIXME: needs documentation.
|
||||
*/
|
||||
STOP: "statistics.stop"
|
||||
};
|
|
@ -1,92 +0,0 @@
|
|||
var XMPPEvents = {
|
||||
// Designates an event indicating that the connection to the XMPP server
|
||||
// failed.
|
||||
CONNECTION_FAILED: "xmpp.connection.failed",
|
||||
// Designates an event indicating that the media (ICE) connection was
|
||||
// interrupted. This should go to the RTC module.
|
||||
CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
|
||||
// Designates an event indicating that the media (ICE) connection was
|
||||
// restored. This should go to the RTC module.
|
||||
CONNECTION_RESTORED: "xmpp.connection.restored",
|
||||
// Designates an event indicating that an offer (e.g. Jingle
|
||||
// session-initiate) was received.
|
||||
CALL_INCOMING: "xmpp.callincoming.jingle",
|
||||
// Designates an event indicating that we were kicked from the XMPP MUC.
|
||||
KICKED: "xmpp.kicked",
|
||||
// Designates an event indicating that the userID for a specific JID has
|
||||
// changed.
|
||||
// Note: currently this event fires every time we receive presence from
|
||||
// someone (regardless of whether or not the "userID" changed).
|
||||
USER_ID_CHANGED: "xmpp.user_id_changed",
|
||||
// Designates an event indicating that we have joined the XMPP MUC.
|
||||
MUC_JOINED: "xmpp.muc_joined",
|
||||
// Designates an event indicating that a participant joined the XMPP MUC.
|
||||
MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
|
||||
// Designates an event indicating that a participant left the XMPP MUC.
|
||||
MUC_MEMBER_LEFT: "xmpp.muc_member_left",
|
||||
// Designates an event indicating that the MUC role of a participant has
|
||||
// changed.
|
||||
MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
|
||||
// Designates an event indicating that the XMPP MUC was destroyed.
|
||||
MUC_DESTROYED: "xmpp.muc_destroyed",
|
||||
// Designates an event indicating that the display name of a participant
|
||||
// has changed.
|
||||
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
|
||||
// Designates an event indicating that we received statistics from a
|
||||
// participant in the MUC.
|
||||
REMOTE_STATS: "xmpp.remote_stats",
|
||||
// Designates an event indicating that our role in the XMPP MUC has changed.
|
||||
LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
|
||||
// Designates an event indicating that the subject of the XMPP MUC has
|
||||
// changed.
|
||||
SUBJECT_CHANGED: "xmpp.subject_changed",
|
||||
// Designates an event indicating that an XMPP message in the MUC was
|
||||
// received.
|
||||
MESSAGE_RECEIVED: "xmpp.message_received",
|
||||
// Designates an event indicating that we sent an XMPP message to the MUC.
|
||||
SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
|
||||
// Designates an event indicating that the video type (e.g. 'camera' or
|
||||
// 'screen') for a participant has changed.
|
||||
// Note: currently this event fires every time we receive presence from
|
||||
// someone (regardless of whether or not the "video type" changed).
|
||||
PARTICIPANT_VIDEO_TYPE_CHANGED: "xmpp.video_type",
|
||||
// Designates an event indicating that a participant in the XMPP MUC has
|
||||
// advertised that they have audio muted (or unmuted).
|
||||
PARTICIPANT_AUDIO_MUTED: "xmpp.audio_muted",
|
||||
// Designates an event indicating that a participant in the XMPP MUC has
|
||||
// advertised that they have video muted (or unmuted).
|
||||
PARTICIPANT_VIDEO_MUTED: "xmpp.video_muted",
|
||||
// Designates an event indicating that the focus has asked us to mute our
|
||||
// audio.
|
||||
AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
|
||||
// Designates an event indicating that a moderator in the room changed the
|
||||
// "start muted" settings for the conference.
|
||||
START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
|
||||
// Designates an event indicating that we should join the conference with
|
||||
// audio and/or video muted.
|
||||
START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
|
||||
// Designates an event indicating that a remote participant's available
|
||||
// devices (whether he supports a audio and/or video) changed.
|
||||
// Note: currently this event fires every time we receive presence from
|
||||
// someone (regardless of whether or not the devices changed).
|
||||
DEVICE_AVAILABLE: "xmpp.device_available",
|
||||
|
||||
|
||||
PEERCONNECTION_READY: "xmpp.peerconnection_ready",
|
||||
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
|
||||
PASSWORD_REQUIRED: "xmpp.password_required",
|
||||
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
|
||||
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
|
||||
ETHERPAD: "xmpp.etherpad",
|
||||
BRIDGE_DOWN: "xmpp.bridge_down",
|
||||
PRESENCE_STATUS: "xmpp.presence_status",
|
||||
RESERVATION_ERROR: "xmpp.room_reservation_error",
|
||||
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
|
||||
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
|
||||
JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
|
||||
PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
|
||||
FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
|
||||
// xmpp is connected and obtained user media
|
||||
READY_TO_JOIN: 'xmpp.ready_to_join'
|
||||
};
|
||||
module.exports = XMPPEvents;
|
Loading…
Reference in New Issue