Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

statistics.bundle.js 36KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  1. !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){
  2. // Copyright Joyent, Inc. and other Node contributors.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a
  5. // copy of this software and associated documentation files (the
  6. // "Software"), to deal in the Software without restriction, including
  7. // without limitation the rights to use, copy, modify, merge, publish,
  8. // distribute, sublicense, and/or sell copies of the Software, and to permit
  9. // persons to whom the Software is furnished to do so, subject to the
  10. // following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included
  13. // in all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  16. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  18. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  19. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  20. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  21. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. function EventEmitter() {
  23. this._events = this._events || {};
  24. this._maxListeners = this._maxListeners || undefined;
  25. }
  26. module.exports = EventEmitter;
  27. // Backwards-compat with node 0.10.x
  28. EventEmitter.EventEmitter = EventEmitter;
  29. EventEmitter.prototype._events = undefined;
  30. EventEmitter.prototype._maxListeners = undefined;
  31. // By default EventEmitters will print a warning if more than 10 listeners are
  32. // added to it. This is a useful default which helps finding memory leaks.
  33. EventEmitter.defaultMaxListeners = 10;
  34. // Obviously not all Emitters should be limited to 10. This function allows
  35. // that to be increased. Set to zero for unlimited.
  36. EventEmitter.prototype.setMaxListeners = function(n) {
  37. if (!isNumber(n) || n < 0 || isNaN(n))
  38. throw TypeError('n must be a positive number');
  39. this._maxListeners = n;
  40. return this;
  41. };
  42. EventEmitter.prototype.emit = function(type) {
  43. var er, handler, len, args, i, listeners;
  44. if (!this._events)
  45. this._events = {};
  46. // If there is no 'error' event listener then throw.
  47. if (type === 'error') {
  48. if (!this._events.error ||
  49. (isObject(this._events.error) && !this._events.error.length)) {
  50. er = arguments[1];
  51. if (er instanceof Error) {
  52. throw er; // Unhandled 'error' event
  53. }
  54. throw TypeError('Uncaught, unspecified "error" event.');
  55. }
  56. }
  57. handler = this._events[type];
  58. if (isUndefined(handler))
  59. return false;
  60. if (isFunction(handler)) {
  61. switch (arguments.length) {
  62. // fast cases
  63. case 1:
  64. handler.call(this);
  65. break;
  66. case 2:
  67. handler.call(this, arguments[1]);
  68. break;
  69. case 3:
  70. handler.call(this, arguments[1], arguments[2]);
  71. break;
  72. // slower
  73. default:
  74. len = arguments.length;
  75. args = new Array(len - 1);
  76. for (i = 1; i < len; i++)
  77. args[i - 1] = arguments[i];
  78. handler.apply(this, args);
  79. }
  80. } else if (isObject(handler)) {
  81. len = arguments.length;
  82. args = new Array(len - 1);
  83. for (i = 1; i < len; i++)
  84. args[i - 1] = arguments[i];
  85. listeners = handler.slice();
  86. len = listeners.length;
  87. for (i = 0; i < len; i++)
  88. listeners[i].apply(this, args);
  89. }
  90. return true;
  91. };
  92. EventEmitter.prototype.addListener = function(type, listener) {
  93. var m;
  94. if (!isFunction(listener))
  95. throw TypeError('listener must be a function');
  96. if (!this._events)
  97. this._events = {};
  98. // To avoid recursion in the case that type === "newListener"! Before
  99. // adding it to the listeners, first emit "newListener".
  100. if (this._events.newListener)
  101. this.emit('newListener', type,
  102. isFunction(listener.listener) ?
  103. listener.listener : listener);
  104. if (!this._events[type])
  105. // Optimize the case of one listener. Don't need the extra array object.
  106. this._events[type] = listener;
  107. else if (isObject(this._events[type]))
  108. // If we've already got an array, just append.
  109. this._events[type].push(listener);
  110. else
  111. // Adding the second element, need to change to array.
  112. this._events[type] = [this._events[type], listener];
  113. // Check for listener leak
  114. if (isObject(this._events[type]) && !this._events[type].warned) {
  115. var m;
  116. if (!isUndefined(this._maxListeners)) {
  117. m = this._maxListeners;
  118. } else {
  119. m = EventEmitter.defaultMaxListeners;
  120. }
  121. if (m && m > 0 && this._events[type].length > m) {
  122. this._events[type].warned = true;
  123. console.error('(node) warning: possible EventEmitter memory ' +
  124. 'leak detected. %d listeners added. ' +
  125. 'Use emitter.setMaxListeners() to increase limit.',
  126. this._events[type].length);
  127. if (typeof console.trace === 'function') {
  128. // not supported in IE 10
  129. console.trace();
  130. }
  131. }
  132. }
  133. return this;
  134. };
  135. EventEmitter.prototype.on = EventEmitter.prototype.addListener;
  136. EventEmitter.prototype.once = function(type, listener) {
  137. if (!isFunction(listener))
  138. throw TypeError('listener must be a function');
  139. var fired = false;
  140. function g() {
  141. this.removeListener(type, g);
  142. if (!fired) {
  143. fired = true;
  144. listener.apply(this, arguments);
  145. }
  146. }
  147. g.listener = listener;
  148. this.on(type, g);
  149. return this;
  150. };
  151. // emits a 'removeListener' event iff the listener was removed
  152. EventEmitter.prototype.removeListener = function(type, listener) {
  153. var list, position, length, i;
  154. if (!isFunction(listener))
  155. throw TypeError('listener must be a function');
  156. if (!this._events || !this._events[type])
  157. return this;
  158. list = this._events[type];
  159. length = list.length;
  160. position = -1;
  161. if (list === listener ||
  162. (isFunction(list.listener) && list.listener === listener)) {
  163. delete this._events[type];
  164. if (this._events.removeListener)
  165. this.emit('removeListener', type, listener);
  166. } else if (isObject(list)) {
  167. for (i = length; i-- > 0;) {
  168. if (list[i] === listener ||
  169. (list[i].listener && list[i].listener === listener)) {
  170. position = i;
  171. break;
  172. }
  173. }
  174. if (position < 0)
  175. return this;
  176. if (list.length === 1) {
  177. list.length = 0;
  178. delete this._events[type];
  179. } else {
  180. list.splice(position, 1);
  181. }
  182. if (this._events.removeListener)
  183. this.emit('removeListener', type, listener);
  184. }
  185. return this;
  186. };
  187. EventEmitter.prototype.removeAllListeners = function(type) {
  188. var key, listeners;
  189. if (!this._events)
  190. return this;
  191. // not listening for removeListener, no need to emit
  192. if (!this._events.removeListener) {
  193. if (arguments.length === 0)
  194. this._events = {};
  195. else if (this._events[type])
  196. delete this._events[type];
  197. return this;
  198. }
  199. // emit removeListener for all listeners on all events
  200. if (arguments.length === 0) {
  201. for (key in this._events) {
  202. if (key === 'removeListener') continue;
  203. this.removeAllListeners(key);
  204. }
  205. this.removeAllListeners('removeListener');
  206. this._events = {};
  207. return this;
  208. }
  209. listeners = this._events[type];
  210. if (isFunction(listeners)) {
  211. this.removeListener(type, listeners);
  212. } else {
  213. // LIFO order
  214. while (listeners.length)
  215. this.removeListener(type, listeners[listeners.length - 1]);
  216. }
  217. delete this._events[type];
  218. return this;
  219. };
  220. EventEmitter.prototype.listeners = function(type) {
  221. var ret;
  222. if (!this._events || !this._events[type])
  223. ret = [];
  224. else if (isFunction(this._events[type]))
  225. ret = [this._events[type]];
  226. else
  227. ret = this._events[type].slice();
  228. return ret;
  229. };
  230. EventEmitter.listenerCount = function(emitter, type) {
  231. var ret;
  232. if (!emitter._events || !emitter._events[type])
  233. ret = 0;
  234. else if (isFunction(emitter._events[type]))
  235. ret = 1;
  236. else
  237. ret = emitter._events[type].length;
  238. return ret;
  239. };
  240. function isFunction(arg) {
  241. return typeof arg === 'function';
  242. }
  243. function isNumber(arg) {
  244. return typeof arg === 'number';
  245. }
  246. function isObject(arg) {
  247. return typeof arg === 'object' && arg !== null;
  248. }
  249. function isUndefined(arg) {
  250. return arg === void 0;
  251. }
  252. },{}],2:[function(require,module,exports){
  253. /**
  254. * Provides statistics for the local stream.
  255. */
  256. /**
  257. * Size of the webaudio analizer buffer.
  258. * @type {number}
  259. */
  260. var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
  261. /**
  262. * Value of the webaudio analizer smoothing time parameter.
  263. * @type {number}
  264. */
  265. var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
  266. /**
  267. * Converts time domain data array to audio level.
  268. * @param array the time domain data array.
  269. * @returns {number} the audio level
  270. */
  271. function timeDomainDataToAudioLevel(samples) {
  272. var maxVolume = 0;
  273. var length = samples.length;
  274. for (var i = 0; i < length; i++) {
  275. if (maxVolume < samples[i])
  276. maxVolume = samples[i];
  277. }
  278. return parseFloat(((maxVolume - 127) / 128).toFixed(3));
  279. };
  280. /**
  281. * Animates audio level change
  282. * @param newLevel the new audio level
  283. * @param lastLevel the last audio level
  284. * @returns {Number} the audio level to be set
  285. */
  286. function animateLevel(newLevel, lastLevel)
  287. {
  288. var value = 0;
  289. var diff = lastLevel - newLevel;
  290. if(diff > 0.2)
  291. {
  292. value = lastLevel - 0.2;
  293. }
  294. else if(diff < -0.4)
  295. {
  296. value = lastLevel + 0.4;
  297. }
  298. else
  299. {
  300. value = newLevel;
  301. }
  302. return parseFloat(value.toFixed(3));
  303. }
  304. /**
  305. * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
  306. *
  307. * @param stream the local stream
  308. * @param interval stats refresh interval given in ms.
  309. * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
  310. * update.
  311. * @constructor
  312. */
  313. function LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
  314. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  315. this.stream = stream;
  316. this.intervalId = null;
  317. this.intervalMilis = interval;
  318. this.eventEmitter = eventEmitter;
  319. this.audioLevel = 0;
  320. this.statisticsService = statisticsService;
  321. }
  322. /**
  323. * Starts the collecting the statistics.
  324. */
  325. LocalStatsCollector.prototype.start = function () {
  326. if (!window.AudioContext)
  327. return;
  328. var context = new AudioContext();
  329. var analyser = context.createAnalyser();
  330. analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
  331. analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
  332. var source = context.createMediaStreamSource(this.stream);
  333. source.connect(analyser);
  334. var self = this;
  335. this.intervalId = setInterval(
  336. function () {
  337. var array = new Uint8Array(analyser.frequencyBinCount);
  338. analyser.getByteTimeDomainData(array);
  339. var audioLevel = timeDomainDataToAudioLevel(array);
  340. if(audioLevel != self.audioLevel) {
  341. self.audioLevel = animateLevel(audioLevel, self.audioLevel);
  342. self.eventEmitter.emit(
  343. "statistics.audioLevel",
  344. self.statisticsService.LOCAL_JID,
  345. self.audioLevel);
  346. }
  347. },
  348. this.intervalMilis
  349. );
  350. };
  351. /**
  352. * Stops collecting the statistics.
  353. */
  354. LocalStatsCollector.prototype.stop = function () {
  355. if (this.intervalId) {
  356. clearInterval(this.intervalId);
  357. this.intervalId = null;
  358. }
  359. };
  360. module.exports = LocalStatsCollector;
  361. },{}],3:[function(require,module,exports){
  362. /* global focusMucJid, ssrc2jid */
  363. /* jshint -W117 */
  364. /**
  365. * Calculates packet lost percent using the number of lost packets and the
  366. * number of all packet.
  367. * @param lostPackets the number of lost packets
  368. * @param totalPackets the number of all packets.
  369. * @returns {number} packet loss percent
  370. */
  371. function calculatePacketLoss(lostPackets, totalPackets) {
  372. if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
  373. return 0;
  374. return Math.round((lostPackets/totalPackets)*100);
  375. }
  376. function getStatValue(item, name) {
  377. if(!keyMap[RTC.getBrowserType()][name])
  378. throw "The property isn't supported!";
  379. var key = keyMap[RTC.getBrowserType()][name];
  380. return RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
  381. }
  382. /**
  383. * Peer statistics data holder.
  384. * @constructor
  385. */
  386. function PeerStats()
  387. {
  388. this.ssrc2Loss = {};
  389. this.ssrc2AudioLevel = {};
  390. this.ssrc2bitrate = {};
  391. this.ssrc2resolution = {};
  392. }
  393. /**
  394. * The bandwidth
  395. * @type {{}}
  396. */
  397. PeerStats.bandwidth = {};
  398. /**
  399. * The bit rate
  400. * @type {{}}
  401. */
  402. PeerStats.bitrate = {};
  403. /**
  404. * The packet loss rate
  405. * @type {{}}
  406. */
  407. PeerStats.packetLoss = null;
  408. /**
  409. * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
  410. * represented by this instance.
  411. * @param ssrc audio or video RTP stream SSRC.
  412. * @param lossRate new packet loss rate value to be set.
  413. */
  414. PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
  415. {
  416. this.ssrc2Loss[ssrc] = lossRate;
  417. };
  418. /**
  419. * Sets resolution for given <tt>ssrc</tt> that belong to the peer
  420. * represented by this instance.
  421. * @param ssrc audio or video RTP stream SSRC.
  422. * @param resolution new resolution value to be set.
  423. */
  424. PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
  425. {
  426. if(resolution === null && this.ssrc2resolution[ssrc])
  427. {
  428. delete this.ssrc2resolution[ssrc];
  429. }
  430. else if(resolution !== null)
  431. this.ssrc2resolution[ssrc] = resolution;
  432. };
  433. /**
  434. * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
  435. * represented by this instance.
  436. * @param ssrc audio or video RTP stream SSRC.
  437. * @param bitrate new bitrate value to be set.
  438. */
  439. PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
  440. {
  441. if(this.ssrc2bitrate[ssrc])
  442. {
  443. this.ssrc2bitrate[ssrc].download += bitrate.download;
  444. this.ssrc2bitrate[ssrc].upload += bitrate.upload;
  445. }
  446. else {
  447. this.ssrc2bitrate[ssrc] = bitrate;
  448. }
  449. };
  450. /**
  451. * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
  452. * the stream which belongs to the peer represented by this instance.
  453. * @param ssrc RTP stream SSRC for which current audio level value will be
  454. * updated.
  455. * @param audioLevel the new audio level value to be set. Value is truncated to
  456. * fit the range from 0 to 1.
  457. */
  458. PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
  459. {
  460. // Range limit 0 - 1
  461. this.ssrc2AudioLevel[ssrc] = Math.min(Math.max(audioLevel, 0), 1);
  462. };
  463. /**
  464. * Array with the transport information.
  465. * @type {Array}
  466. */
  467. PeerStats.transport = [];
  468. /**
  469. * <tt>StatsCollector</tt> registers for stats updates of given
  470. * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
  471. * stats are extracted and put in {@link PeerStats} objects. Once the processing
  472. * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt>
  473. * instance as an event source.
  474. *
  475. * @param peerconnection webRTC peer connection object.
  476. * @param interval stats refresh interval given in ms.
  477. * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
  478. * called on stats update.
  479. * @constructor
  480. */
  481. function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
  482. {
  483. this.peerconnection = peerconnection;
  484. this.baselineAudioLevelsReport = null;
  485. this.currentAudioLevelsReport = null;
  486. this.currentStatsReport = null;
  487. this.baselineStatsReport = null;
  488. this.audioLevelsIntervalId = null;
  489. this.eventEmitter = eventEmitter;
  490. /**
  491. * Gather PeerConnection stats once every this many milliseconds.
  492. */
  493. this.GATHER_INTERVAL = 10000;
  494. /**
  495. * Log stats via the focus once every this many milliseconds.
  496. */
  497. this.LOG_INTERVAL = 60000;
  498. /**
  499. * Gather stats and store them in this.statsToBeLogged.
  500. */
  501. this.gatherStatsIntervalId = null;
  502. /**
  503. * Send the stats already saved in this.statsToBeLogged to be logged via
  504. * the focus.
  505. */
  506. this.logStatsIntervalId = null;
  507. /**
  508. * Stores the statistics which will be send to the focus to be logged.
  509. */
  510. this.statsToBeLogged =
  511. {
  512. timestamps: [],
  513. stats: {}
  514. };
  515. // Updates stats interval
  516. this.audioLevelsIntervalMilis = audioLevelsInterval;
  517. this.statsIntervalId = null;
  518. this.statsIntervalMilis = statsInterval;
  519. // Map of jids to PeerStats
  520. this.jid2stats = {};
  521. }
  522. module.exports = StatsCollector;
  523. /**
  524. * Stops stats updates.
  525. */
  526. StatsCollector.prototype.stop = function ()
  527. {
  528. if (this.audioLevelsIntervalId)
  529. {
  530. clearInterval(this.audioLevelsIntervalId);
  531. this.audioLevelsIntervalId = null;
  532. clearInterval(this.statsIntervalId);
  533. this.statsIntervalId = null;
  534. clearInterval(this.logStatsIntervalId);
  535. this.logStatsIntervalId = null;
  536. clearInterval(this.gatherStatsIntervalId);
  537. this.gatherStatsIntervalId = null;
  538. }
  539. };
  540. /**
  541. * Callback passed to <tt>getStats</tt> method.
  542. * @param error an error that occurred on <tt>getStats</tt> call.
  543. */
  544. StatsCollector.prototype.errorCallback = function (error)
  545. {
  546. console.error("Get stats error", error);
  547. this.stop();
  548. };
  549. /**
  550. * Starts stats updates.
  551. */
  552. StatsCollector.prototype.start = function ()
  553. {
  554. var self = this;
  555. this.audioLevelsIntervalId = setInterval(
  556. function ()
  557. {
  558. // Interval updates
  559. self.peerconnection.getStats(
  560. function (report)
  561. {
  562. var results = null;
  563. if(!report || !report.result || typeof report.result != 'function')
  564. {
  565. results = report;
  566. }
  567. else
  568. {
  569. results = report.result();
  570. }
  571. //console.error("Got interval report", results);
  572. self.currentAudioLevelsReport = results;
  573. self.processAudioLevelReport();
  574. self.baselineAudioLevelsReport =
  575. self.currentAudioLevelsReport;
  576. },
  577. self.errorCallback
  578. );
  579. },
  580. self.audioLevelsIntervalMilis
  581. );
  582. this.statsIntervalId = setInterval(
  583. function () {
  584. // Interval updates
  585. self.peerconnection.getStats(
  586. function (report)
  587. {
  588. var results = null;
  589. if(!report || !report.result || typeof report.result != 'function')
  590. {
  591. //firefox
  592. results = report;
  593. }
  594. else
  595. {
  596. //chrome
  597. results = report.result();
  598. }
  599. //console.error("Got interval report", results);
  600. self.currentStatsReport = results;
  601. try
  602. {
  603. self.processStatsReport();
  604. }
  605. catch (e)
  606. {
  607. console.error("Unsupported key:" + e, e);
  608. }
  609. self.baselineStatsReport = self.currentStatsReport;
  610. },
  611. self.errorCallback
  612. );
  613. },
  614. self.statsIntervalMilis
  615. );
  616. if (config.logStats) {
  617. this.gatherStatsIntervalId = setInterval(
  618. function () {
  619. self.peerconnection.getStats(
  620. function (report) {
  621. self.addStatsToBeLogged(report.result());
  622. },
  623. function () {
  624. }
  625. );
  626. },
  627. this.GATHER_INTERVAL
  628. );
  629. this.logStatsIntervalId = setInterval(
  630. function() { self.logStats(); },
  631. this.LOG_INTERVAL);
  632. }
  633. };
  634. /**
  635. * Converts the stats to the format used for logging, and saves the data in
  636. * this.statsToBeLogged.
  637. * @param reports Reports as given by webkitRTCPerConnection.getStats.
  638. */
  639. StatsCollector.prototype.addStatsToBeLogged = function (reports) {
  640. var self = this;
  641. var num_records = this.statsToBeLogged.timestamps.length;
  642. this.statsToBeLogged.timestamps.push(new Date().getTime());
  643. reports.map(function (report) {
  644. var stat = self.statsToBeLogged.stats[report.id];
  645. if (!stat) {
  646. stat = self.statsToBeLogged.stats[report.id] = {};
  647. }
  648. stat.type = report.type;
  649. report.names().map(function (name) {
  650. var values = stat[name];
  651. if (!values) {
  652. values = stat[name] = [];
  653. }
  654. while (values.length < num_records) {
  655. values.push(null);
  656. }
  657. values.push(report.stat(name));
  658. });
  659. });
  660. };
  661. StatsCollector.prototype.logStats = function () {
  662. if (!focusMucJid) {
  663. return;
  664. }
  665. var deflate = true;
  666. var content = JSON.stringify(this.statsToBeLogged);
  667. if (deflate) {
  668. content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
  669. }
  670. content = Base64.encode(content);
  671. // XEP-0337-ish
  672. var message = $msg({to: focusMucJid, type: 'normal'});
  673. message.c('log', { xmlns: 'urn:xmpp:eventlog',
  674. id: 'PeerConnectionStats'});
  675. message.c('message').t(content).up();
  676. if (deflate) {
  677. message.c('tag', {name: "deflated", value: "true"}).up();
  678. }
  679. message.up();
  680. connection.send(message);
  681. // Reset the stats
  682. this.statsToBeLogged.stats = {};
  683. this.statsToBeLogged.timestamps = [];
  684. };
  685. var keyMap = {};
  686. keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
  687. "ssrc": "ssrc",
  688. "packetsReceived": "packetsReceived",
  689. "packetsLost": "packetsLost",
  690. "packetsSent": "packetsSent",
  691. "bytesReceived": "bytesReceived",
  692. "bytesSent": "bytesSent"
  693. };
  694. keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
  695. "receiveBandwidth": "googAvailableReceiveBandwidth",
  696. "sendBandwidth": "googAvailableSendBandwidth",
  697. "remoteAddress": "googRemoteAddress",
  698. "transportType": "googTransportType",
  699. "localAddress": "googLocalAddress",
  700. "activeConnection": "googActiveConnection",
  701. "ssrc": "ssrc",
  702. "packetsReceived": "packetsReceived",
  703. "packetsSent": "packetsSent",
  704. "packetsLost": "packetsLost",
  705. "bytesReceived": "bytesReceived",
  706. "bytesSent": "bytesSent",
  707. "googFrameHeightReceived": "googFrameHeightReceived",
  708. "googFrameWidthReceived": "googFrameWidthReceived",
  709. "googFrameHeightSent": "googFrameHeightSent",
  710. "googFrameWidthSent": "googFrameWidthSent",
  711. "audioInputLevel": "audioInputLevel",
  712. "audioOutputLevel": "audioOutputLevel"
  713. };
  714. /**
  715. * Stats processing logic.
  716. */
  717. StatsCollector.prototype.processStatsReport = function () {
  718. if (!this.baselineStatsReport) {
  719. return;
  720. }
  721. for (var idx in this.currentStatsReport) {
  722. var now = this.currentStatsReport[idx];
  723. try {
  724. if (getStatValue(now, 'receiveBandwidth') ||
  725. getStatValue(now, 'sendBandwidth')) {
  726. PeerStats.bandwidth = {
  727. "download": Math.round(
  728. (getStatValue(now, 'receiveBandwidth')) / 1000),
  729. "upload": Math.round(
  730. (getStatValue(now, 'sendBandwidth')) / 1000)
  731. };
  732. }
  733. }
  734. catch(e){/*not supported*/}
  735. if(now.type == 'googCandidatePair')
  736. {
  737. var ip, type, localIP, active;
  738. try {
  739. ip = getStatValue(now, 'remoteAddress');
  740. type = getStatValue(now, "transportType");
  741. localIP = getStatValue(now, "localAddress");
  742. active = getStatValue(now, "activeConnection");
  743. }
  744. catch(e){/*not supported*/}
  745. if(!ip || !type || !localIP || active != "true")
  746. continue;
  747. var addressSaved = false;
  748. for(var i = 0; i < PeerStats.transport.length; i++)
  749. {
  750. if(PeerStats.transport[i].ip == ip &&
  751. PeerStats.transport[i].type == type &&
  752. PeerStats.transport[i].localip == localIP)
  753. {
  754. addressSaved = true;
  755. }
  756. }
  757. if(addressSaved)
  758. continue;
  759. PeerStats.transport.push({localip: localIP, ip: ip, type: type});
  760. continue;
  761. }
  762. if(now.type == "candidatepair")
  763. {
  764. if(now.state == "succeeded")
  765. continue;
  766. var local = this.currentStatsReport[now.localCandidateId];
  767. var remote = this.currentStatsReport[now.remoteCandidateId];
  768. PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
  769. ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
  770. }
  771. if (now.type != 'ssrc' && now.type != "outboundrtp" &&
  772. now.type != "inboundrtp") {
  773. continue;
  774. }
  775. var before = this.baselineStatsReport[idx];
  776. if (!before) {
  777. console.warn(getStatValue(now, 'ssrc') + ' not enough data');
  778. continue;
  779. }
  780. var ssrc = getStatValue(now, 'ssrc');
  781. if(!ssrc)
  782. continue;
  783. var jid = ssrc2jid[ssrc];
  784. if (!jid && (Date.now() - now.timestamp) < 3000) {
  785. console.warn("No jid for ssrc: " + ssrc);
  786. continue;
  787. }
  788. var jidStats = this.jid2stats[jid];
  789. if (!jidStats) {
  790. jidStats = new PeerStats();
  791. this.jid2stats[jid] = jidStats;
  792. }
  793. var isDownloadStream = true;
  794. var key = 'packetsReceived';
  795. if (!getStatValue(now, key))
  796. {
  797. isDownloadStream = false;
  798. key = 'packetsSent';
  799. if (!getStatValue(now, key))
  800. {
  801. console.warn("No packetsReceived nor packetSent stat found");
  802. continue;
  803. }
  804. }
  805. var packetsNow = getStatValue(now, key);
  806. if(!packetsNow || packetsNow < 0)
  807. packetsNow = 0;
  808. var packetsBefore = getStatValue(before, key);
  809. if(!packetsBefore || packetsBefore < 0)
  810. packetsBefore = 0;
  811. var packetRate = packetsNow - packetsBefore;
  812. if(!packetRate || packetRate < 0)
  813. packetRate = 0;
  814. var currentLoss = getStatValue(now, 'packetsLost');
  815. if(!currentLoss || currentLoss < 0)
  816. currentLoss = 0;
  817. var previousLoss = getStatValue(before, 'packetsLost');
  818. if(!previousLoss || previousLoss < 0)
  819. previousLoss = 0;
  820. var lossRate = currentLoss - previousLoss;
  821. if(!lossRate || lossRate < 0)
  822. lossRate = 0;
  823. var packetsTotal = (packetRate + lossRate);
  824. jidStats.setSsrcLoss(ssrc,
  825. {"packetsTotal": packetsTotal,
  826. "packetsLost": lossRate,
  827. "isDownloadStream": isDownloadStream});
  828. var bytesReceived = 0, bytesSent = 0;
  829. if(getStatValue(now, "bytesReceived"))
  830. {
  831. bytesReceived = getStatValue(now, "bytesReceived") -
  832. getStatValue(before, "bytesReceived");
  833. }
  834. if(getStatValue(now, "bytesSent"))
  835. {
  836. bytesSent = getStatValue(now, "bytesSent") -
  837. getStatValue(before, "bytesSent");
  838. }
  839. var time = Math.round((now.timestamp - before.timestamp) / 1000);
  840. if(bytesReceived <= 0 || time <= 0)
  841. {
  842. bytesReceived = 0;
  843. }
  844. else
  845. {
  846. bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
  847. }
  848. if(bytesSent <= 0 || time <= 0)
  849. {
  850. bytesSent = 0;
  851. }
  852. else
  853. {
  854. bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
  855. }
  856. jidStats.setSsrcBitrate(ssrc, {
  857. "download": bytesReceived,
  858. "upload": bytesSent});
  859. var resolution = {height: null, width: null};
  860. try {
  861. if (getStatValue(now, "googFrameHeightReceived") &&
  862. getStatValue(now, "googFrameWidthReceived")) {
  863. resolution.height = getStatValue(now, "googFrameHeightReceived");
  864. resolution.width = getStatValue(now, "googFrameWidthReceived");
  865. }
  866. else if (getStatValue(now, "googFrameHeightSent") &&
  867. getStatValue(now, "googFrameWidthSent")) {
  868. resolution.height = getStatValue(now, "googFrameHeightSent");
  869. resolution.width = getStatValue(now, "googFrameWidthSent");
  870. }
  871. }
  872. catch(e){/*not supported*/}
  873. if(resolution.height && resolution.width)
  874. {
  875. jidStats.setSsrcResolution(ssrc, resolution);
  876. }
  877. else
  878. {
  879. jidStats.setSsrcResolution(ssrc, null);
  880. }
  881. }
  882. var self = this;
  883. // Jid stats
  884. var totalPackets = {download: 0, upload: 0};
  885. var lostPackets = {download: 0, upload: 0};
  886. var bitrateDownload = 0;
  887. var bitrateUpload = 0;
  888. var resolutions = {};
  889. Object.keys(this.jid2stats).forEach(
  890. function (jid)
  891. {
  892. Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
  893. function (ssrc)
  894. {
  895. var type = "upload";
  896. if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
  897. type = "download";
  898. totalPackets[type] +=
  899. self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
  900. lostPackets[type] +=
  901. self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
  902. }
  903. );
  904. Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
  905. function (ssrc) {
  906. bitrateDownload +=
  907. self.jid2stats[jid].ssrc2bitrate[ssrc].download;
  908. bitrateUpload +=
  909. self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
  910. delete self.jid2stats[jid].ssrc2bitrate[ssrc];
  911. }
  912. );
  913. resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
  914. }
  915. );
  916. PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
  917. PeerStats.packetLoss = {
  918. total:
  919. calculatePacketLoss(lostPackets.download + lostPackets.upload,
  920. totalPackets.download + totalPackets.upload),
  921. download:
  922. calculatePacketLoss(lostPackets.download, totalPackets.download),
  923. upload:
  924. calculatePacketLoss(lostPackets.upload, totalPackets.upload)
  925. };
  926. this.eventEmitter.emit("statistics.connectionstats",
  927. {
  928. "bitrate": PeerStats.bitrate,
  929. "packetLoss": PeerStats.packetLoss,
  930. "bandwidth": PeerStats.bandwidth,
  931. "resolution": resolutions,
  932. "transport": PeerStats.transport
  933. });
  934. PeerStats.transport = [];
  935. };
  936. /**
  937. * Stats processing logic.
  938. */
  939. StatsCollector.prototype.processAudioLevelReport = function ()
  940. {
  941. if (!this.baselineAudioLevelsReport)
  942. {
  943. return;
  944. }
  945. for (var idx in this.currentAudioLevelsReport)
  946. {
  947. var now = this.currentAudioLevelsReport[idx];
  948. if (now.type != 'ssrc')
  949. {
  950. continue;
  951. }
  952. var before = this.baselineAudioLevelsReport[idx];
  953. if (!before)
  954. {
  955. console.warn(getStatValue(now, 'ssrc') + ' not enough data');
  956. continue;
  957. }
  958. var ssrc = getStatValue(now, 'ssrc');
  959. var jid = ssrc2jid[ssrc];
  960. if (!jid && (Date.now() - now.timestamp) < 3000)
  961. {
  962. console.warn("No jid for ssrc: " + ssrc);
  963. continue;
  964. }
  965. var jidStats = this.jid2stats[jid];
  966. if (!jidStats)
  967. {
  968. jidStats = new PeerStats();
  969. this.jid2stats[jid] = jidStats;
  970. }
  971. // Audio level
  972. var audioLevel = null;
  973. try {
  974. audioLevel = getStatValue(now, 'audioInputLevel');
  975. if (!audioLevel)
  976. audioLevel = getStatValue(now, 'audioOutputLevel');
  977. }
  978. catch(e) {/*not supported*/
  979. console.warn("Audio Levels are not available in the statistics.");
  980. clearInterval(this.audioLevelsIntervalId);
  981. return;
  982. }
  983. if (audioLevel)
  984. {
  985. // TODO: can't find specs about what this value really is,
  986. // but it seems to vary between 0 and around 32k.
  987. audioLevel = audioLevel / 32767;
  988. jidStats.setSsrcAudioLevel(ssrc, audioLevel);
  989. if(jid != connection.emuc.myroomjid)
  990. this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
  991. }
  992. }
  993. };
  994. },{}],4:[function(require,module,exports){
  995. /**
  996. * Created by hristo on 8/4/14.
  997. */
  998. var LocalStats = require("./LocalStatsCollector.js");
  999. var RTPStats = require("./RTPStatsCollector.js");
  1000. var EventEmitter = require("events");
  1001. //These lines should be uncommented when require works in app.js
  1002. //var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
  1003. //var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
  1004. //var XMPPEvents = require("../service/xmpp/XMPPEvents");
  1005. var eventEmitter = new EventEmitter();
  1006. var localStats = null;
  1007. var rtpStats = null;
  1008. function stopLocal()
  1009. {
  1010. if(localStats)
  1011. {
  1012. localStats.stop();
  1013. localStats = null;
  1014. }
  1015. }
  1016. function stopRemote()
  1017. {
  1018. if(rtpStats)
  1019. {
  1020. rtpStats.stop();
  1021. eventEmitter.emit("statistics.stop");
  1022. rtpStats = null;
  1023. }
  1024. }
  1025. function startRemoteStats (peerconnection) {
  1026. if (config.enableRtpStats)
  1027. {
  1028. if(rtpStats)
  1029. {
  1030. rtpStats.stop();
  1031. rtpStats = null;
  1032. }
  1033. rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
  1034. rtpStats.start();
  1035. }
  1036. }
  1037. function onStreamCreated(stream)
  1038. {
  1039. if(stream.getAudioTracks().length === 0)
  1040. return;
  1041. localStats = new LocalStats(stream, 100, this,
  1042. eventEmitter);
  1043. localStats.start();
  1044. }
  1045. var statistics =
  1046. {
  1047. /**
  1048. * Indicates that this audio level is for local jid.
  1049. * @type {string}
  1050. */
  1051. LOCAL_JID: 'local',
  1052. addAudioLevelListener: function(listener)
  1053. {
  1054. eventEmitter.on("statistics.audioLevel", listener);
  1055. },
  1056. removeAudioLevelListener: function(listener)
  1057. {
  1058. eventEmitter.removeListener("statistics.audioLevel", listener);
  1059. },
  1060. addConnectionStatsListener: function(listener)
  1061. {
  1062. eventEmitter.on("statistics.connectionstats", listener);
  1063. },
  1064. removeConnectionStatsListener: function(listener)
  1065. {
  1066. eventEmitter.removeListener("statistics.connectionstats", listener);
  1067. },
  1068. addRemoteStatsStopListener: function(listener)
  1069. {
  1070. eventEmitter.on("statistics.stop", listener);
  1071. },
  1072. removeRemoteStatsStopListener: function(listener)
  1073. {
  1074. eventEmitter.removeListener("statistics.stop", listener);
  1075. },
  1076. stop: function () {
  1077. stopLocal();
  1078. stopRemote();
  1079. if(eventEmitter)
  1080. {
  1081. eventEmitter.removeAllListeners();
  1082. }
  1083. },
  1084. stopRemoteStatistics: function()
  1085. {
  1086. stopRemote();
  1087. },
  1088. onConferenceCreated: function (event) {
  1089. startRemoteStats(event.peerconnection);
  1090. },
  1091. onDisposeConference: function (onUnload) {
  1092. stopRemote();
  1093. if(onUnload) {
  1094. stopLocal();
  1095. eventEmitter.removeAllListeners();
  1096. }
  1097. },
  1098. start: function () {
  1099. RTC.addStreamListener(onStreamCreated,
  1100. StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
  1101. }
  1102. };
  1103. module.exports = statistics;
  1104. },{"./LocalStatsCollector.js":2,"./RTPStatsCollector.js":3,"events":1}]},{},[4])(4)
  1105. });