You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RTPStatsCollector.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. /* global ssrc2jid */
  2. /* jshint -W117 */
  3. var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
  4. /**
  5. * Calculates packet lost percent using the number of lost packets and the
  6. * number of all packet.
  7. * @param lostPackets the number of lost packets
  8. * @param totalPackets the number of all packets.
  9. * @returns {number} packet loss percent
  10. */
  11. function calculatePacketLoss(lostPackets, totalPackets) {
  12. if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
  13. return 0;
  14. return Math.round((lostPackets/totalPackets)*100);
  15. }
  16. function getStatValue(item, name) {
  17. if(!keyMap[APP.RTC.getBrowserType()][name])
  18. throw "The property isn't supported!";
  19. var key = keyMap[APP.RTC.getBrowserType()][name];
  20. return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
  21. }
  22. /**
  23. * Peer statistics data holder.
  24. * @constructor
  25. */
  26. function PeerStats()
  27. {
  28. this.ssrc2Loss = {};
  29. this.ssrc2AudioLevel = {};
  30. this.ssrc2bitrate = {};
  31. this.ssrc2resolution = {};
  32. }
  33. /**
  34. * The bandwidth
  35. * @type {{}}
  36. */
  37. PeerStats.bandwidth = {};
  38. /**
  39. * The bit rate
  40. * @type {{}}
  41. */
  42. PeerStats.bitrate = {};
  43. /**
  44. * The packet loss rate
  45. * @type {{}}
  46. */
  47. PeerStats.packetLoss = null;
  48. /**
  49. * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
  50. * represented by this instance.
  51. * @param ssrc audio or video RTP stream SSRC.
  52. * @param lossRate new packet loss rate value to be set.
  53. */
  54. PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
  55. {
  56. this.ssrc2Loss[ssrc] = lossRate;
  57. };
  58. /**
  59. * Sets resolution for given <tt>ssrc</tt> that belong to the peer
  60. * represented by this instance.
  61. * @param ssrc audio or video RTP stream SSRC.
  62. * @param resolution new resolution value to be set.
  63. */
  64. PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
  65. {
  66. if(resolution === null && this.ssrc2resolution[ssrc])
  67. {
  68. delete this.ssrc2resolution[ssrc];
  69. }
  70. else if(resolution !== null)
  71. this.ssrc2resolution[ssrc] = resolution;
  72. };
  73. /**
  74. * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
  75. * represented by this instance.
  76. * @param ssrc audio or video RTP stream SSRC.
  77. * @param bitrate new bitrate value to be set.
  78. */
  79. PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
  80. {
  81. if(this.ssrc2bitrate[ssrc])
  82. {
  83. this.ssrc2bitrate[ssrc].download += bitrate.download;
  84. this.ssrc2bitrate[ssrc].upload += bitrate.upload;
  85. }
  86. else {
  87. this.ssrc2bitrate[ssrc] = bitrate;
  88. }
  89. };
  90. /**
  91. * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
  92. * the stream which belongs to the peer represented by this instance.
  93. * @param ssrc RTP stream SSRC for which current audio level value will be
  94. * updated.
  95. * @param audioLevel the new audio level value to be set. Value is truncated to
  96. * fit the range from 0 to 1.
  97. */
  98. PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
  99. {
  100. // Range limit 0 - 1
  101. this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
  102. };
  103. function formatAudioLevel(audioLevel) {
  104. return Math.min(Math.max(audioLevel, 0), 1);
  105. }
  106. /**
  107. * Array with the transport information.
  108. * @type {Array}
  109. */
  110. PeerStats.transport = [];
  111. /**
  112. * <tt>StatsCollector</tt> registers for stats updates of given
  113. * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
  114. * stats are extracted and put in {@link PeerStats} objects. Once the processing
  115. * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt>
  116. * instance as an event source.
  117. *
  118. * @param peerconnection webRTC peer connection object.
  119. * @param interval stats refresh interval given in ms.
  120. * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
  121. * called on stats update.
  122. * @constructor
  123. */
  124. function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
  125. {
  126. this.peerconnection = peerconnection;
  127. this.baselineAudioLevelsReport = null;
  128. this.currentAudioLevelsReport = null;
  129. this.currentStatsReport = null;
  130. this.baselineStatsReport = null;
  131. this.audioLevelsIntervalId = null;
  132. this.eventEmitter = eventEmitter;
  133. /**
  134. * Gather PeerConnection stats once every this many milliseconds.
  135. */
  136. this.GATHER_INTERVAL = 10000;
  137. /**
  138. * Log stats via the focus once every this many milliseconds.
  139. */
  140. this.LOG_INTERVAL = 60000;
  141. /**
  142. * Gather stats and store them in this.statsToBeLogged.
  143. */
  144. this.gatherStatsIntervalId = null;
  145. /**
  146. * Send the stats already saved in this.statsToBeLogged to be logged via
  147. * the focus.
  148. */
  149. this.logStatsIntervalId = null;
  150. /**
  151. * Stores the statistics which will be send to the focus to be logged.
  152. */
  153. this.statsToBeLogged =
  154. {
  155. timestamps: [],
  156. stats: {}
  157. };
  158. // Updates stats interval
  159. this.audioLevelsIntervalMilis = audioLevelsInterval;
  160. this.statsIntervalId = null;
  161. this.statsIntervalMilis = statsInterval;
  162. // Map of jids to PeerStats
  163. this.jid2stats = {};
  164. }
  165. module.exports = StatsCollector;
  166. /**
  167. * Stops stats updates.
  168. */
  169. StatsCollector.prototype.stop = function () {
  170. if (this.audioLevelsIntervalId) {
  171. clearInterval(this.audioLevelsIntervalId);
  172. this.audioLevelsIntervalId = null;
  173. }
  174. if (this.statsIntervalId)
  175. {
  176. clearInterval(this.statsIntervalId);
  177. this.statsIntervalId = null;
  178. }
  179. if(this.logStatsIntervalId)
  180. {
  181. clearInterval(this.logStatsIntervalId);
  182. this.logStatsIntervalId = null;
  183. }
  184. if(this.gatherStatsIntervalId)
  185. {
  186. clearInterval(this.gatherStatsIntervalId);
  187. this.gatherStatsIntervalId = null;
  188. }
  189. };
  190. /**
  191. * Callback passed to <tt>getStats</tt> method.
  192. * @param error an error that occurred on <tt>getStats</tt> call.
  193. */
  194. StatsCollector.prototype.errorCallback = function (error)
  195. {
  196. console.error("Get stats error", error);
  197. this.stop();
  198. };
  199. /**
  200. * Starts stats updates.
  201. */
  202. StatsCollector.prototype.start = function ()
  203. {
  204. var self = this;
  205. if(!config.disableAudioLevels) {
  206. console.debug("set audio levels interval");
  207. this.audioLevelsIntervalId = setInterval(
  208. function () {
  209. // Interval updates
  210. self.peerconnection.getStats(
  211. function (report) {
  212. var results = null;
  213. if (!report || !report.result ||
  214. typeof report.result != 'function') {
  215. results = report;
  216. }
  217. else {
  218. results = report.result();
  219. }
  220. //console.error("Got interval report", results);
  221. self.currentAudioLevelsReport = results;
  222. self.processAudioLevelReport();
  223. self.baselineAudioLevelsReport =
  224. self.currentAudioLevelsReport;
  225. },
  226. self.errorCallback
  227. );
  228. },
  229. self.audioLevelsIntervalMilis
  230. );
  231. }
  232. if(!config.disableStats) {
  233. console.debug("set stats interval");
  234. this.statsIntervalId = setInterval(
  235. function () {
  236. // Interval updates
  237. self.peerconnection.getStats(
  238. function (report) {
  239. var results = null;
  240. if (!report || !report.result ||
  241. typeof report.result != 'function') {
  242. //firefox
  243. results = report;
  244. }
  245. else {
  246. //chrome
  247. results = report.result();
  248. }
  249. //console.error("Got interval report", results);
  250. self.currentStatsReport = results;
  251. try {
  252. self.processStatsReport();
  253. }
  254. catch (e) {
  255. console.error("Unsupported key:" + e, e);
  256. }
  257. self.baselineStatsReport = self.currentStatsReport;
  258. },
  259. self.errorCallback
  260. );
  261. },
  262. self.statsIntervalMilis
  263. );
  264. }
  265. if (config.logStats) {
  266. this.gatherStatsIntervalId = setInterval(
  267. function () {
  268. self.peerconnection.getStats(
  269. function (report) {
  270. self.addStatsToBeLogged(report.result());
  271. },
  272. function () {
  273. }
  274. );
  275. },
  276. this.GATHER_INTERVAL
  277. );
  278. this.logStatsIntervalId = setInterval(
  279. function() { self.logStats(); },
  280. this.LOG_INTERVAL);
  281. }
  282. };
  283. /**
  284. * Converts the stats to the format used for logging, and saves the data in
  285. * this.statsToBeLogged.
  286. * @param reports Reports as given by webkitRTCPerConnection.getStats.
  287. */
  288. StatsCollector.prototype.addStatsToBeLogged = function (reports) {
  289. var self = this;
  290. var num_records = this.statsToBeLogged.timestamps.length;
  291. this.statsToBeLogged.timestamps.push(new Date().getTime());
  292. reports.map(function (report) {
  293. var stat = self.statsToBeLogged.stats[report.id];
  294. if (!stat) {
  295. stat = self.statsToBeLogged.stats[report.id] = {};
  296. }
  297. stat.type = report.type;
  298. report.names().map(function (name) {
  299. var values = stat[name];
  300. if (!values) {
  301. values = stat[name] = [];
  302. }
  303. while (values.length < num_records) {
  304. values.push(null);
  305. }
  306. values.push(report.stat(name));
  307. });
  308. });
  309. };
  310. StatsCollector.prototype.logStats = function () {
  311. if(!APP.xmpp.sendLogs(this.statsToBeLogged))
  312. return;
  313. // Reset the stats
  314. this.statsToBeLogged.stats = {};
  315. this.statsToBeLogged.timestamps = [];
  316. };
  317. var keyMap = {};
  318. keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
  319. "ssrc": "ssrc",
  320. "packetsReceived": "packetsReceived",
  321. "packetsLost": "packetsLost",
  322. "packetsSent": "packetsSent",
  323. "bytesReceived": "bytesReceived",
  324. "bytesSent": "bytesSent"
  325. };
  326. keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
  327. "receiveBandwidth": "googAvailableReceiveBandwidth",
  328. "sendBandwidth": "googAvailableSendBandwidth",
  329. "remoteAddress": "googRemoteAddress",
  330. "transportType": "googTransportType",
  331. "localAddress": "googLocalAddress",
  332. "activeConnection": "googActiveConnection",
  333. "ssrc": "ssrc",
  334. "packetsReceived": "packetsReceived",
  335. "packetsSent": "packetsSent",
  336. "packetsLost": "packetsLost",
  337. "bytesReceived": "bytesReceived",
  338. "bytesSent": "bytesSent",
  339. "googFrameHeightReceived": "googFrameHeightReceived",
  340. "googFrameWidthReceived": "googFrameWidthReceived",
  341. "googFrameHeightSent": "googFrameHeightSent",
  342. "googFrameWidthSent": "googFrameWidthSent",
  343. "audioInputLevel": "audioInputLevel",
  344. "audioOutputLevel": "audioOutputLevel"
  345. };
  346. /**
  347. * Stats processing logic.
  348. */
  349. StatsCollector.prototype.processStatsReport = function () {
  350. if (!this.baselineStatsReport) {
  351. return;
  352. }
  353. for (var idx in this.currentStatsReport) {
  354. var now = this.currentStatsReport[idx];
  355. try {
  356. if (getStatValue(now, 'receiveBandwidth') ||
  357. getStatValue(now, 'sendBandwidth')) {
  358. PeerStats.bandwidth = {
  359. "download": Math.round(
  360. (getStatValue(now, 'receiveBandwidth')) / 1000),
  361. "upload": Math.round(
  362. (getStatValue(now, 'sendBandwidth')) / 1000)
  363. };
  364. }
  365. }
  366. catch(e){/*not supported*/}
  367. if(now.type == 'googCandidatePair')
  368. {
  369. var ip, type, localIP, active;
  370. try {
  371. ip = getStatValue(now, 'remoteAddress');
  372. type = getStatValue(now, "transportType");
  373. localIP = getStatValue(now, "localAddress");
  374. active = getStatValue(now, "activeConnection");
  375. }
  376. catch(e){/*not supported*/}
  377. if(!ip || !type || !localIP || active != "true")
  378. continue;
  379. var addressSaved = false;
  380. for(var i = 0; i < PeerStats.transport.length; i++)
  381. {
  382. if(PeerStats.transport[i].ip == ip &&
  383. PeerStats.transport[i].type == type &&
  384. PeerStats.transport[i].localip == localIP)
  385. {
  386. addressSaved = true;
  387. }
  388. }
  389. if(addressSaved)
  390. continue;
  391. PeerStats.transport.push({localip: localIP, ip: ip, type: type});
  392. continue;
  393. }
  394. if(now.type == "candidatepair")
  395. {
  396. if(now.state == "succeeded")
  397. continue;
  398. var local = this.currentStatsReport[now.localCandidateId];
  399. var remote = this.currentStatsReport[now.remoteCandidateId];
  400. PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
  401. ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
  402. }
  403. if (now.type != 'ssrc' && now.type != "outboundrtp" &&
  404. now.type != "inboundrtp") {
  405. continue;
  406. }
  407. var before = this.baselineStatsReport[idx];
  408. if (!before) {
  409. console.warn(getStatValue(now, 'ssrc') + ' not enough data');
  410. continue;
  411. }
  412. var ssrc = getStatValue(now, 'ssrc');
  413. if(!ssrc)
  414. continue;
  415. var jid = APP.xmpp.getJidFromSSRC(ssrc);
  416. if (!jid && (Date.now() - now.timestamp) < 3000) {
  417. console.warn("No jid for ssrc: " + ssrc);
  418. continue;
  419. }
  420. var jidStats = this.jid2stats[jid];
  421. if (!jidStats) {
  422. jidStats = new PeerStats();
  423. this.jid2stats[jid] = jidStats;
  424. }
  425. var isDownloadStream = true;
  426. var key = 'packetsReceived';
  427. if (!getStatValue(now, key))
  428. {
  429. isDownloadStream = false;
  430. key = 'packetsSent';
  431. if (!getStatValue(now, key))
  432. {
  433. console.warn("No packetsReceived nor packetSent stat found");
  434. continue;
  435. }
  436. }
  437. var packetsNow = getStatValue(now, key);
  438. if(!packetsNow || packetsNow < 0)
  439. packetsNow = 0;
  440. var packetsBefore = getStatValue(before, key);
  441. if(!packetsBefore || packetsBefore < 0)
  442. packetsBefore = 0;
  443. var packetRate = packetsNow - packetsBefore;
  444. if(!packetRate || packetRate < 0)
  445. packetRate = 0;
  446. var currentLoss = getStatValue(now, 'packetsLost');
  447. if(!currentLoss || currentLoss < 0)
  448. currentLoss = 0;
  449. var previousLoss = getStatValue(before, 'packetsLost');
  450. if(!previousLoss || previousLoss < 0)
  451. previousLoss = 0;
  452. var lossRate = currentLoss - previousLoss;
  453. if(!lossRate || lossRate < 0)
  454. lossRate = 0;
  455. var packetsTotal = (packetRate + lossRate);
  456. jidStats.setSsrcLoss(ssrc,
  457. {"packetsTotal": packetsTotal,
  458. "packetsLost": lossRate,
  459. "isDownloadStream": isDownloadStream});
  460. var bytesReceived = 0, bytesSent = 0;
  461. if(getStatValue(now, "bytesReceived"))
  462. {
  463. bytesReceived = getStatValue(now, "bytesReceived") -
  464. getStatValue(before, "bytesReceived");
  465. }
  466. if(getStatValue(now, "bytesSent"))
  467. {
  468. bytesSent = getStatValue(now, "bytesSent") -
  469. getStatValue(before, "bytesSent");
  470. }
  471. var time = Math.round((now.timestamp - before.timestamp) / 1000);
  472. if(bytesReceived <= 0 || time <= 0)
  473. {
  474. bytesReceived = 0;
  475. }
  476. else
  477. {
  478. bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
  479. }
  480. if(bytesSent <= 0 || time <= 0)
  481. {
  482. bytesSent = 0;
  483. }
  484. else
  485. {
  486. bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
  487. }
  488. jidStats.setSsrcBitrate(ssrc, {
  489. "download": bytesReceived,
  490. "upload": bytesSent});
  491. var resolution = {height: null, width: null};
  492. try {
  493. if (getStatValue(now, "googFrameHeightReceived") &&
  494. getStatValue(now, "googFrameWidthReceived")) {
  495. resolution.height = getStatValue(now, "googFrameHeightReceived");
  496. resolution.width = getStatValue(now, "googFrameWidthReceived");
  497. }
  498. else if (getStatValue(now, "googFrameHeightSent") &&
  499. getStatValue(now, "googFrameWidthSent")) {
  500. resolution.height = getStatValue(now, "googFrameHeightSent");
  501. resolution.width = getStatValue(now, "googFrameWidthSent");
  502. }
  503. }
  504. catch(e){/*not supported*/}
  505. if(resolution.height && resolution.width)
  506. {
  507. jidStats.setSsrcResolution(ssrc, resolution);
  508. }
  509. else
  510. {
  511. jidStats.setSsrcResolution(ssrc, null);
  512. }
  513. }
  514. var self = this;
  515. // Jid stats
  516. var totalPackets = {download: 0, upload: 0};
  517. var lostPackets = {download: 0, upload: 0};
  518. var bitrateDownload = 0;
  519. var bitrateUpload = 0;
  520. var resolutions = {};
  521. Object.keys(this.jid2stats).forEach(
  522. function (jid)
  523. {
  524. Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
  525. function (ssrc)
  526. {
  527. var type = "upload";
  528. if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
  529. type = "download";
  530. totalPackets[type] +=
  531. self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
  532. lostPackets[type] +=
  533. self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
  534. }
  535. );
  536. Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
  537. function (ssrc) {
  538. bitrateDownload +=
  539. self.jid2stats[jid].ssrc2bitrate[ssrc].download;
  540. bitrateUpload +=
  541. self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
  542. delete self.jid2stats[jid].ssrc2bitrate[ssrc];
  543. }
  544. );
  545. resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
  546. }
  547. );
  548. PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
  549. PeerStats.packetLoss = {
  550. total:
  551. calculatePacketLoss(lostPackets.download + lostPackets.upload,
  552. totalPackets.download + totalPackets.upload),
  553. download:
  554. calculatePacketLoss(lostPackets.download, totalPackets.download),
  555. upload:
  556. calculatePacketLoss(lostPackets.upload, totalPackets.upload)
  557. };
  558. this.eventEmitter.emit("statistics.connectionstats",
  559. {
  560. "bitrate": PeerStats.bitrate,
  561. "packetLoss": PeerStats.packetLoss,
  562. "bandwidth": PeerStats.bandwidth,
  563. "resolution": resolutions,
  564. "transport": PeerStats.transport
  565. });
  566. PeerStats.transport = [];
  567. };
  568. /**
  569. * Stats processing logic.
  570. */
  571. StatsCollector.prototype.processAudioLevelReport = function ()
  572. {
  573. if (!this.baselineAudioLevelsReport)
  574. {
  575. return;
  576. }
  577. for (var idx in this.currentAudioLevelsReport)
  578. {
  579. var now = this.currentAudioLevelsReport[idx];
  580. if (now.type != 'ssrc')
  581. {
  582. continue;
  583. }
  584. var before = this.baselineAudioLevelsReport[idx];
  585. if (!before)
  586. {
  587. console.warn(getStatValue(now, 'ssrc') + ' not enough data');
  588. continue;
  589. }
  590. var ssrc = getStatValue(now, 'ssrc');
  591. var jid = APP.xmpp.getJidFromSSRC(ssrc);
  592. if (!jid && (Date.now() - now.timestamp) < 3000)
  593. {
  594. console.warn("No jid for ssrc: " + ssrc);
  595. continue;
  596. }
  597. var jidStats = this.jid2stats[jid];
  598. if (!jidStats)
  599. {
  600. jidStats = new PeerStats();
  601. this.jid2stats[jid] = jidStats;
  602. }
  603. // Audio level
  604. var audioLevel = null;
  605. try {
  606. audioLevel = getStatValue(now, 'audioInputLevel');
  607. if (!audioLevel)
  608. audioLevel = getStatValue(now, 'audioOutputLevel');
  609. }
  610. catch(e) {/*not supported*/
  611. console.warn("Audio Levels are not available in the statistics.");
  612. clearInterval(this.audioLevelsIntervalId);
  613. return;
  614. }
  615. if (audioLevel)
  616. {
  617. // TODO: can't find specs about what this value really is,
  618. // but it seems to vary between 0 and around 32k.
  619. audioLevel = formatAudioLevel(audioLevel / 32767);
  620. var oldLevel = jidStats.ssrc2AudioLevel[ssrc];
  621. if(jid != APP.xmpp.myJid() && (!oldLevel || oldLevel != audioLevel))
  622. {
  623. jidStats.ssrc2AudioLevel[ssrc] = audioLevel;
  624. this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
  625. }
  626. }
  627. }
  628. };