jiti-meet/libs/modules/statistics.bundle.js

1290 lines
36 KiB
JavaScript

!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.statistics=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* Provides statistics for the local stream.
*/
/**
* Size of the webaudio analizer buffer.
* @type {number}
*/
var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
/**
* Value of the webaudio analizer smoothing time parameter.
* @type {number}
*/
var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
/**
* Converts time domain data array to audio level.
* @param array the time domain data array.
* @returns {number} the audio level
*/
function timeDomainDataToAudioLevel(samples) {
var maxVolume = 0;
var length = samples.length;
for (var i = 0; i < length; i++) {
if (maxVolume < samples[i])
maxVolume = samples[i];
}
return parseFloat(((maxVolume - 127) / 128).toFixed(3));
};
/**
* Animates audio level change
* @param newLevel the new audio level
* @param lastLevel the last audio level
* @returns {Number} the audio level to be set
*/
function animateLevel(newLevel, lastLevel)
{
var value = 0;
var diff = lastLevel - newLevel;
if(diff > 0.2)
{
value = lastLevel - 0.2;
}
else if(diff < -0.4)
{
value = lastLevel + 0.4;
}
else
{
value = newLevel;
}
return parseFloat(value.toFixed(3));
}
/**
* <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
*
* @param stream the local stream
* @param interval stats refresh interval given in ms.
* @param {function(LocalStatsCollector)} updateCallback the callback called on stats
* update.
* @constructor
*/
function LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.stream = stream;
this.intervalId = null;
this.intervalMilis = interval;
this.eventEmitter = eventEmitter;
this.audioLevel = 0;
this.statisticsService = statisticsService;
}
/**
* Starts the collecting the statistics.
*/
LocalStatsCollector.prototype.start = function () {
if (!window.AudioContext)
return;
var context = new AudioContext();
var analyser = context.createAnalyser();
analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
var source = context.createMediaStreamSource(this.stream);
source.connect(analyser);
var self = this;
this.intervalId = setInterval(
function () {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(array);
var audioLevel = timeDomainDataToAudioLevel(array);
if(audioLevel != self.audioLevel) {
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
self.eventEmitter.emit(
"statistics.audioLevel",
self.statisticsService.LOCAL_JID,
self.audioLevel);
}
},
this.intervalMilis
);
};
/**
* Stops collecting the statistics.
*/
LocalStatsCollector.prototype.stop = function () {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
};
module.exports = LocalStatsCollector;
},{}],2:[function(require,module,exports){
/* global focusMucJid, ssrc2jid */
/* jshint -W117 */
/**
* 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) {
if(!keyMap[RTC.getBrowserType()][name])
throw "The property isn't supported!";
var key = keyMap[RTC.getBrowserType()][name];
return RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? 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] = 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.currentAudioLevelsReport = null;
this.currentStatsReport = null;
this.baselineStatsReport = null;
this.audioLevelsIntervalId = null;
this.eventEmitter = eventEmitter;
/**
* Gather PeerConnection stats once every this many milliseconds.
*/
this.GATHER_INTERVAL = 10000;
/**
* 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;
clearInterval(this.statsIntervalId);
this.statsIntervalId = null;
clearInterval(this.logStatsIntervalId);
this.logStatsIntervalId = null;
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;
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.currentAudioLevelsReport = results;
self.processAudioLevelReport();
self.baselineAudioLevelsReport =
self.currentAudioLevelsReport;
},
self.errorCallback
);
},
self.audioLevelsIntervalMilis
);
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
);
if (config.logStats) {
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);
}
};
/**
* 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) {
var stat = self.statsToBeLogged.stats[report.id];
if (!stat) {
stat = self.statsToBeLogged.stats[report.id] = {};
}
stat.type = report.type;
report.names().map(function (name) {
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 (!focusMucJid) {
return;
}
var deflate = true;
var content = JSON.stringify(this.statsToBeLogged);
if (deflate) {
content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
}
content = Base64.encode(content);
// XEP-0337-ish
var message = $msg({to: focusMucJid, type: 'normal'});
message.c('log', { xmlns: 'urn:xmpp:eventlog',
id: 'PeerConnectionStats'});
message.c('message').t(content).up();
if (deflate) {
message.c('tag', {name: "deflated", value: "true"}).up();
}
message.up();
connection.send(message);
// 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"
};
/**
* 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 = ssrc2jid[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';
if (!getStatValue(now, key))
{
isDownloadStream = false;
key = 'packetsSent';
if (!getStatValue(now, key))
{
console.warn("No packetsReceived nor packetSent stat found");
continue;
}
}
var packetsNow = getStatValue(now, key);
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)
};
this.eventEmitter.emit("statistics.connectionstats",
{
"bitrate": PeerStats.bitrate,
"packetLoss": PeerStats.packetLoss,
"bandwidth": PeerStats.bandwidth,
"resolution": resolutions,
"transport": PeerStats.transport
});
PeerStats.transport = [];
};
/**
* Stats processing logic.
*/
StatsCollector.prototype.processAudioLevelReport = function ()
{
if (!this.baselineAudioLevelsReport)
{
return;
}
for (var idx in this.currentAudioLevelsReport)
{
var now = this.currentAudioLevelsReport[idx];
if (now.type != 'ssrc')
{
continue;
}
var before = this.baselineAudioLevelsReport[idx];
if (!before)
{
console.warn(getStatValue(now, 'ssrc') + ' not enough data');
continue;
}
var ssrc = getStatValue(now, 'ssrc');
var jid = ssrc2jid[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;
}
// Audio level
var audioLevel = null;
try {
audioLevel = getStatValue(now, 'audioInputLevel');
if (!audioLevel)
audioLevel = getStatValue(now, 'audioOutputLevel');
}
catch(e) {/*not supported*/
console.warn("Audio Levels are not available in the statistics.");
clearInterval(this.audioLevelsIntervalId);
return;
}
if (audioLevel)
{
// TODO: can't find specs about what this value really is,
// but it seems to vary between 0 and around 32k.
audioLevel = audioLevel / 32767;
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
if(jid != connection.emuc.myroomjid)
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
}
}
};
},{}],3:[function(require,module,exports){
/**
* Created by hristo on 8/4/14.
*/
var LocalStats = require("./LocalStatsCollector.js");
var RTPStats = require("./RTPStatsCollector.js");
var EventEmitter = require("events");
//These lines should be uncommented when require works in app.js
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
//var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
//var XMPPEvents = require("../service/xmpp/XMPPEvents");
var eventEmitter = new EventEmitter();
var localStats = null;
var rtpStats = null;
function stopLocal()
{
if(localStats)
{
localStats.stop();
localStats = null;
}
}
function stopRemote()
{
if(rtpStats)
{
rtpStats.stop();
eventEmitter.emit("statistics.stop");
rtpStats = null;
}
}
function startRemoteStats (peerconnection) {
if (config.enableRtpStats)
{
if(rtpStats)
{
rtpStats.stop();
rtpStats = null;
}
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
rtpStats.start();
}
}
function onStreamCreated(stream)
{
if(stream.getOriginalStream().getAudioTracks().length === 0)
return;
localStats = new LocalStats(stream.getOriginalStream(), 100, statistics,
eventEmitter);
localStats.start();
}
var statistics =
{
/**
* Indicates that this audio level is for local jid.
* @type {string}
*/
LOCAL_JID: 'local',
addAudioLevelListener: function(listener)
{
eventEmitter.on("statistics.audioLevel", listener);
},
removeAudioLevelListener: function(listener)
{
eventEmitter.removeListener("statistics.audioLevel", listener);
},
addConnectionStatsListener: function(listener)
{
eventEmitter.on("statistics.connectionstats", listener);
},
removeConnectionStatsListener: function(listener)
{
eventEmitter.removeListener("statistics.connectionstats", listener);
},
addRemoteStatsStopListener: function(listener)
{
eventEmitter.on("statistics.stop", listener);
},
removeRemoteStatsStopListener: function(listener)
{
eventEmitter.removeListener("statistics.stop", listener);
},
stop: function () {
stopLocal();
stopRemote();
if(eventEmitter)
{
eventEmitter.removeAllListeners();
}
},
stopRemoteStatistics: function()
{
stopRemote();
},
onConferenceCreated: function (event) {
startRemoteStats(event.peerconnection);
},
onDisposeConference: function (onUnload) {
stopRemote();
if(onUnload) {
stopLocal();
eventEmitter.removeAllListeners();
}
},
start: function () {
this.addConnectionStatsListener(connection_quality.updateLocalStats);
this.addRemoteStatsStopListener(connection_quality.stopSendingStats);
RTC.addStreamListener(onStreamCreated,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
}
};
module.exports = statistics;
},{"./LocalStatsCollector.js":1,"./RTPStatsCollector.js":2,"events":4}],4:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
function EventEmitter() {
this._events = this._events || {};
this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
if (!isNumber(n) || n < 0 || isNaN(n))
throw TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
EventEmitter.prototype.emit = function(type) {
var er, handler, len, args, i, listeners;
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events.error ||
(isObject(this._events.error) && !this._events.error.length)) {
er = arguments[1];
if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
throw TypeError('Uncaught, unspecified "error" event.');
}
return false;
}
}
handler = this._events[type];
if (isUndefined(handler))
return false;
if (isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
handler.apply(this, args);
}
} else if (isObject(handler)) {
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++)
listeners[i].apply(this, args);
}
return true;
};
EventEmitter.prototype.addListener = function(type, listener) {
var m;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events)
this._events = {};
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (this._events.newListener)
this.emit('newListener', type,
isFunction(listener.listener) ?
listener.listener : listener);
if (!this._events[type])
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
else if (isObject(this._events[type]))
// If we've already got an array, just append.
this._events[type].push(listener);
else
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
// Check for listener leak
if (isObject(this._events[type]) && !this._events[type].warned) {
var m;
if (!isUndefined(this._maxListeners)) {
m = this._maxListeners;
} else {
m = EventEmitter.defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
if (typeof console.trace === 'function') {
// not supported in IE 10
console.trace();
}
}
}
return this;
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.once = function(type, listener) {
if (!isFunction(listener))
throw TypeError('listener must be a function');
var fired = false;
function g() {
this.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
g.listener = listener;
this.on(type, g);
return this;
};
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
var list, position, length, i;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events || !this._events[type])
return this;
list = this._events[type];
length = list.length;
position = -1;
if (list === listener ||
(isFunction(list.listener) && list.listener === listener)) {
delete this._events[type];
if (this._events.removeListener)
this.emit('removeListener', type, listener);
} else if (isObject(list)) {
for (i = length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list.length = 0;
delete this._events[type];
} else {
list.splice(position, 1);
}
if (this._events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function(type) {
var key, listeners;
if (!this._events)
return this;
// not listening for removeListener, no need to emit
if (!this._events.removeListener) {
if (arguments.length === 0)
this._events = {};
else if (this._events[type])
delete this._events[type];
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (key in this._events) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = {};
return this;
}
listeners = this._events[type];
if (isFunction(listeners)) {
this.removeListener(type, listeners);
} else {
// LIFO order
while (listeners.length)
this.removeListener(type, listeners[listeners.length - 1]);
}
delete this._events[type];
return this;
};
EventEmitter.prototype.listeners = function(type) {
var ret;
if (!this._events || !this._events[type])
ret = [];
else if (isFunction(this._events[type]))
ret = [this._events[type]];
else
ret = this._events[type].slice();
return ret;
};
EventEmitter.listenerCount = function(emitter, type) {
var ret;
if (!emitter._events || !emitter._events[type])
ret = 0;
else if (isFunction(emitter._events[type]))
ret = 1;
else
ret = emitter._events[type].length;
return ret;
};
function isFunction(arg) {
return typeof arg === 'function';
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isUndefined(arg) {
return arg === void 0;
}
},{}]},{},[3])(3)
});