| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270 | 
							- !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 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(!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"
 - };
 - 
 - 
 - /**
 -  * 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 != xmpp.myJid())
 -                 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();
 - }
 - 
 - function onDisposeConference(onUnload) {
 -     stopRemote();
 -     if(onUnload) {
 -         stopLocal();
 -         eventEmitter.removeAllListeners();
 -     }
 - }
 - 
 - 
 - 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);
 -     },
 - 
 -     start: function () {
 -         this.addConnectionStatsListener(connectionquality.updateLocalStats);
 -         this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
 -         RTC.addStreamListener(onStreamCreated,
 -             StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
 -         xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
 -     }
 - 
 - };
 - 
 - 
 - 
 - 
 - 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)
 - });
 
 
  |