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.

AvgRTPStatsReporter.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /* global __filename */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. import * as ConnectionQualityEvents
  4. from '../../service/connectivity/ConnectionQualityEvents';
  5. import * as ConferenceEvents from '../../JitsiConferenceEvents';
  6. import * as MediaType from '../../service/RTC/MediaType';
  7. import RTCBrowserType from '../RTC/RTCBrowserType';
  8. import Statistics from './statistics';
  9. import * as VideoType from '../../service/RTC/VideoType';
  10. /**
  11. * All avg RTP stats are currently reported under 1 event name, but under
  12. * different keys. This constant stores the name of this event.
  13. * Example structure of "avg.rtp.stats" analytics event:
  14. *
  15. * {
  16. * "stat_avg_rtt": {
  17. * value: 200,
  18. * samples: [ 100, 200, 300 ]
  19. * },
  20. * "stat_avg_packetloss_total": {
  21. * value: 10,
  22. * samples: [ 5, 10, 15]
  23. * },
  24. * "p2p_stat_avg_packetloss_total": {
  25. * value: 15,
  26. * samples: [ 10, 15, 20]
  27. * }
  28. * }
  29. *
  30. * Note that the samples array is currently emitted for debug purposes only and
  31. * can be removed anytime soon from the structure.
  32. *
  33. * Also not all values are always present in "avg.rtp.stats", some of the values
  34. * are obtained and calculated as part of different process/event pipe. For
  35. * example {@link ConnectionAvgStats} instances are doing the reports for each
  36. * {@link TraceablePeerConnection} and work independently from the main stats
  37. * pipe.
  38. *
  39. * @type {string}
  40. */
  41. const AVG_RTP_STATS_EVENT = 'avg.rtp.stats';
  42. const logger = getLogger(__filename);
  43. /**
  44. * Figures out what prefix should be added to the stat name.
  45. * @param {boolean} isP2P is it P2P or JVB conference ?
  46. * @param {number} conferenceSize how many participants are there in
  47. * the conference (including us)
  48. * @return {string} "" (for JVB conference) or "p2p_" (for P2P) or "jvb121_"
  49. * (for JVB 2 participant conference).
  50. */
  51. function getPrefix(isP2P, conferenceSize) {
  52. return isP2P
  53. ? 'p2p_'
  54. : conferenceSize === 2 ? 'jvb121_' : '';
  55. }
  56. /**
  57. * This will calculate an average for one, named stat and submit it to
  58. * the analytics module when requested. It automatically counts the samples.
  59. */
  60. class AverageStatReport {
  61. /**
  62. * Creates new <tt>AverageStatReport</tt> for given name.
  63. * @param {string} name that's the name of the event that will be reported
  64. * to the analytics module.
  65. */
  66. constructor(name) {
  67. this.name = name;
  68. this.count = 0;
  69. this.sum = 0;
  70. this.samples = [];
  71. }
  72. /**
  73. * Adds the next value that will be included in the average when
  74. * {@link calculate} is called.
  75. * @param {number} nextValue
  76. */
  77. addNext(nextValue) {
  78. if (typeof nextValue !== 'number') {
  79. logger.error(
  80. `${this.name} - invalid value for idx: ${this.count}`,
  81. nextValue);
  82. } else if (!isNaN(nextValue)) {
  83. this.sum += nextValue;
  84. this.samples.push(nextValue);
  85. this.count += 1;
  86. }
  87. }
  88. /**
  89. * Calculates an average for the samples collected using {@link addNext}.
  90. * @return {number|NaN} an average of all collected samples or <tt>NaN</tt>
  91. * if no samples were collected.
  92. */
  93. calculate() {
  94. return this.sum / this.count;
  95. }
  96. /**
  97. * Appends the report to the analytics "data" object. The object will be
  98. * set under <tt>prefix</tt> + {@link this.name} key.
  99. * @param {Object} report the analytics "data" object
  100. * @param {string} prefix the prefix string that will be added at
  101. * the beginning of the key name.
  102. */
  103. appendReport(report, prefix) {
  104. const keyName = `${prefix}${this.name}`;
  105. report[keyName] = {
  106. value: this.calculate(),
  107. samples: this.samples
  108. };
  109. }
  110. /**
  111. * Clears all memory of any samples collected, so that new average can be
  112. * calculated using this instance.
  113. */
  114. reset() {
  115. this.samples = [];
  116. this.sum = 0;
  117. this.count = 0;
  118. }
  119. }
  120. /**
  121. * Class gathers the stats that are calculated and reported for a
  122. * {@link TraceablePeerConnection} even if it's not currently active. For
  123. * example we want to monitor RTT for the JVB connection while in P2P mode.
  124. */
  125. class ConnectionAvgStats {
  126. /**
  127. * Creates new <tt>ConnectionAvgStats</tt>
  128. * @param {JitsiConference} conference
  129. * @param {boolean} isP2P
  130. * @param {number} n the number of samples, before arithmetic mean is to be
  131. * calculated and values submitted to the analytics module.
  132. */
  133. constructor(conference, isP2P, n) {
  134. /**
  135. * Is this instance for JVB or P2P connection ?
  136. * @type {boolean}
  137. */
  138. this.isP2P = isP2P;
  139. /**
  140. * How many samples are to be included in arithmetic mean calculation.
  141. * @type {number}
  142. * @private
  143. */
  144. this._n = n;
  145. /**
  146. * The current sample index. Starts from 0 and goes up to {@link _n})
  147. * when analytics report will be submitted.
  148. * @type {number}
  149. * @private
  150. */
  151. this._sampleIdx = 0;
  152. /**
  153. * Average round trip time reported by the ICE candidate pair.
  154. * @type {AverageStatReport}
  155. */
  156. this._avgRTT = new AverageStatReport('stat_avg_rtt');
  157. /**
  158. * Map stores average RTT to the JVB reported by remote participants.
  159. * Mapped per participant id {@link JitsiParticipant.getId}.
  160. *
  161. * This is used only when {@link ConnectionAvgStats.isP2P} equals to
  162. * <tt>false</tt>.
  163. *
  164. * @type {Map<string,AverageStatReport>}
  165. * @private
  166. */
  167. this._avgRemoteRTTMap = new Map();
  168. /**
  169. * The conference for which stats will be collected and reported.
  170. * @type {JitsiConference}
  171. * @private
  172. */
  173. this._conference = conference;
  174. this._onConnectionStats = (tpc, stats) => {
  175. if (this.isP2P === tpc.isP2P) {
  176. this._calculateAvgStats(stats);
  177. }
  178. };
  179. conference.statistics.addConnectionStatsListener(
  180. this._onConnectionStats);
  181. if (!this.isP2P) {
  182. this._onUserLeft = id => this._avgRemoteRTTMap.delete(id);
  183. conference.on(ConferenceEvents.USER_LEFT, this._onUserLeft);
  184. this._onRemoteStatsUpdated
  185. = (id, data) => this._processRemoteStats(id, data);
  186. conference.on(
  187. ConnectionQualityEvents.REMOTE_STATS_UPDATED,
  188. this._onRemoteStatsUpdated);
  189. }
  190. }
  191. /**
  192. * Processes next batch of stats.
  193. * @param {go figure} data
  194. * @private
  195. */
  196. _calculateAvgStats(data) {
  197. if (!data) {
  198. logger.error('No stats');
  199. return;
  200. }
  201. if (RTCBrowserType.supportsRTTStatistics()) {
  202. if (data.transport && data.transport.length) {
  203. this._avgRTT.addNext(data.transport[0].rtt);
  204. }
  205. }
  206. this._sampleIdx += 1;
  207. if (this._sampleIdx >= this._n) {
  208. if (RTCBrowserType.supportsRTTStatistics()) {
  209. const batchReport = { };
  210. const confSize = this._conference.getParticipantCount();
  211. const prefix = getPrefix(this.isP2P, confSize);
  212. this._avgRTT.appendReport(batchReport, prefix);
  213. // Report end to end RTT only for JVB
  214. if (!this.isP2P) {
  215. const avgRemoteRTT = this._calculateAvgRemoteRTT();
  216. const avgLocalRTT = this._avgRTT.calculate();
  217. if (!isNaN(avgLocalRTT) && !isNaN(avgRemoteRTT)) {
  218. // eslint-disable-next-line camelcase
  219. batchReport[`${prefix}stat_avg_end2endrtt`]
  220. = { value: avgLocalRTT + avgRemoteRTT };
  221. }
  222. }
  223. Statistics.analytics.sendEvent(
  224. AVG_RTP_STATS_EVENT, batchReport);
  225. }
  226. this._resetAvgStats();
  227. }
  228. }
  229. /**
  230. * Calculates arithmetic mean of all RTTs towards the JVB reported by
  231. * participants.
  232. * @return {number|NaN} NaN if not available (not enough data)
  233. * @private
  234. */
  235. _calculateAvgRemoteRTT() {
  236. let count = 0, sum = 0;
  237. // FIXME should we ignore RTT for participant
  238. // who "is having connectivity issues" ?
  239. for (const remoteAvg of this._avgRemoteRTTMap.values()) {
  240. const avg = remoteAvg.calculate();
  241. if (!isNaN(avg)) {
  242. sum += avg;
  243. count += 1;
  244. remoteAvg.reset();
  245. }
  246. }
  247. return sum / count;
  248. }
  249. /**
  250. * Processes {@link ConnectionQualityEvents.REMOTE_STATS_UPDATED} to analyse
  251. * RTT towards the JVB reported by each participant.
  252. * @param {string} id {@link JitsiParticipant.getId}
  253. * @param {go figure in ConnectionQuality.js} data
  254. * @private
  255. */
  256. _processRemoteStats(id, data) {
  257. const validData = typeof data.jvbRTT === 'number';
  258. let rttAvg = this._avgRemoteRTTMap.get(id);
  259. if (!rttAvg && validData) {
  260. rttAvg = new AverageStatReport(`${id}_stat_rtt`);
  261. this._avgRemoteRTTMap.set(id, rttAvg);
  262. }
  263. if (validData) {
  264. rttAvg.addNext(data.jvbRTT);
  265. } else if (rttAvg) {
  266. this._avgRemoteRTTMap.delete(id);
  267. }
  268. }
  269. /**
  270. * Reset cache of all averages and {@link _sampleIdx}.
  271. * @private
  272. */
  273. _resetAvgStats() {
  274. this._avgRTT.reset();
  275. if (this._avgRemoteRTTMap) {
  276. this._avgRemoteRTTMap.clear();
  277. }
  278. this._sampleIdx = 0;
  279. }
  280. /**
  281. *
  282. */
  283. dispose() {
  284. this._conference.statistics.removeConnectionStatsListener(
  285. this._onConnectionStats);
  286. if (!this.isP2P) {
  287. this._conference.off(
  288. ConnectionQualityEvents.REMOTE_STATS_UPDATED,
  289. this._onRemoteStatsUpdated);
  290. this._conference.off(
  291. ConferenceEvents.USER_LEFT,
  292. this._onUserLeft);
  293. }
  294. }
  295. }
  296. /**
  297. * Reports average RTP statistics values (arithmetic mean) to the analytics
  298. * module for things like bit rate, bandwidth, packet loss etc. It keeps track
  299. * of the P2P vs JVB conference modes and submits the values under different
  300. * namespaces (the events for P2P mode have 'p2p.' prefix). Every switch between
  301. * P2P mode resets the data collected so far and averages are calculated from
  302. * scratch.
  303. */
  304. export default class AvgRTPStatsReporter {
  305. /**
  306. * Creates new instance of <tt>AvgRTPStatsReporter</tt>
  307. * @param {JitsiConference} conference
  308. * @param {number} n the number of samples, before arithmetic mean is to be
  309. * calculated and values submitted to the analytics module.
  310. */
  311. constructor(conference, n) {
  312. /**
  313. * How many {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED} samples
  314. * are to be included in arithmetic mean calculation.
  315. * @type {number}
  316. * @private
  317. */
  318. this._n = n;
  319. if (n > 0) {
  320. logger.info(`Avg RTP stats will be calculated every ${n} samples`);
  321. } else {
  322. logger.info('Avg RTP stats reports are disabled.');
  323. // Do not initialize
  324. return;
  325. }
  326. /**
  327. * The current sample index. Starts from 0 and goes up to {@link _n})
  328. * when analytics report will be submitted.
  329. * @type {number}
  330. * @private
  331. */
  332. this._sampleIdx = 0;
  333. /**
  334. * The conference for which stats will be collected and reported.
  335. * @type {JitsiConference}
  336. * @private
  337. */
  338. this._conference = conference;
  339. /**
  340. * Average audio upload bitrate
  341. * @type {AverageStatReport}
  342. * @private
  343. */
  344. this._avgAudioBitrateUp
  345. = new AverageStatReport('stat_avg_bitrate_audio_upload');
  346. /**
  347. * Average audio download bitrate
  348. * @type {AverageStatReport}
  349. * @private
  350. */
  351. this._avgAudioBitrateDown
  352. = new AverageStatReport('stat_avg_bitrate_audio_download');
  353. /**
  354. * Average video upload bitrate
  355. * @type {AverageStatReport}
  356. * @private
  357. */
  358. this._avgVideoBitrateUp
  359. = new AverageStatReport('stat_avg_bitrate_video_upload');
  360. /**
  361. * Average video download bitrate
  362. * @type {AverageStatReport}
  363. * @private
  364. */
  365. this._avgVideoBitrateDown
  366. = new AverageStatReport('stat_avg_bitrate_video_download');
  367. /**
  368. * Average upload bandwidth
  369. * @type {AverageStatReport}
  370. * @private
  371. */
  372. this._avgBandwidthUp
  373. = new AverageStatReport('stat_avg_bandwidth_upload');
  374. /**
  375. * Average download bandwidth
  376. * @type {AverageStatReport}
  377. * @private
  378. */
  379. this._avgBandwidthDown
  380. = new AverageStatReport('stat_avg_bandwidth_download');
  381. /**
  382. * Average total packet loss
  383. * @type {AverageStatReport}
  384. * @private
  385. */
  386. this._avgPacketLossTotal
  387. = new AverageStatReport('stat_avg_packetloss_total');
  388. /**
  389. * Average upload packet loss
  390. * @type {AverageStatReport}
  391. * @private
  392. */
  393. this._avgPacketLossUp
  394. = new AverageStatReport('stat_avg_packetloss_upload');
  395. /**
  396. * Average download packet loss
  397. * @type {AverageStatReport}
  398. * @private
  399. */
  400. this._avgPacketLossDown
  401. = new AverageStatReport('stat_avg_packetloss_download');
  402. /**
  403. * Average FPS for remote videos
  404. * @type {AverageStatReport}
  405. * @private
  406. */
  407. this._avgRemoteFPS = new AverageStatReport('stat_avg_framerate_remote');
  408. /**
  409. * Average FPS for remote screen streaming videos (reported only if not
  410. * a <tt>NaN</tt>).
  411. * @type {AverageStatReport}
  412. * @private
  413. */
  414. this._avgRemoteScreenFPS
  415. = new AverageStatReport('stat_avg_framerate_screen_remote');
  416. /**
  417. * Average FPS for local video (camera)
  418. * @type {AverageStatReport}
  419. * @private
  420. */
  421. this._avgLocalFPS = new AverageStatReport('stat_avg_framerate_local');
  422. /**
  423. * Average FPS for local screen streaming video (reported only if not
  424. * a <tt>NaN</tt>).
  425. * @type {AverageStatReport}
  426. * @private
  427. */
  428. this._avgLocalScreenFPS
  429. = new AverageStatReport('stat_avg_framerate_screen_local');
  430. /**
  431. * Average connection quality as defined by
  432. * the {@link ConnectionQuality} module.
  433. * @type {AverageStatReport}
  434. * @private
  435. */
  436. this._avgCQ = new AverageStatReport('stat_avg_cq');
  437. this._onLocalStatsUpdated = data => this._calculateAvgStats(data);
  438. conference.on(
  439. ConnectionQualityEvents.LOCAL_STATS_UPDATED,
  440. this._onLocalStatsUpdated);
  441. this._onP2PStatusChanged = () => {
  442. logger.debug('Resetting average stats calculation');
  443. this._resetAvgStats();
  444. this.jvbStatsMonitor._resetAvgStats();
  445. this.p2pStatsMonitor._resetAvgStats();
  446. };
  447. conference.on(
  448. ConferenceEvents.P2P_STATUS,
  449. this._onP2PStatusChanged);
  450. this.jvbStatsMonitor
  451. = new ConnectionAvgStats(conference, false /* JVB */, n);
  452. this.p2pStatsMonitor
  453. = new ConnectionAvgStats(conference, true /* P2P */, n);
  454. }
  455. /**
  456. * Processes next batch of stats reported on
  457. * {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED}.
  458. * @param {go figure} data
  459. * @private
  460. */
  461. _calculateAvgStats(data) {
  462. const isP2P = this._conference.isP2PActive();
  463. const confSize = this._conference.getParticipantCount();
  464. const prefix = getPrefix(isP2P, confSize);
  465. if (!isP2P && confSize < 2) {
  466. // There's no point in collecting stats for a JVB conference of 1.
  467. // That happens for short period of time after everyone leaves
  468. // the room, until Jicofo terminates the session.
  469. return;
  470. }
  471. /* Uncomment to figure out stats structure
  472. for (const key in data) {
  473. if (data.hasOwnProperty(key)) {
  474. logger.info(`local stat ${key}: `, data[key]);
  475. }
  476. } */
  477. if (!data) {
  478. logger.error('No stats');
  479. return;
  480. }
  481. const bitrate = data.bitrate;
  482. const bandwidth = data.bandwidth;
  483. const packetLoss = data.packetLoss;
  484. const frameRate = data.framerate;
  485. if (!bitrate) {
  486. logger.error('No "bitrate"');
  487. return;
  488. } else if (!bandwidth) {
  489. logger.error('No "bandwidth"');
  490. return;
  491. } else if (!packetLoss) {
  492. logger.error('No "packetloss"');
  493. return;
  494. } else if (!frameRate) {
  495. logger.error('No "framerate"');
  496. return;
  497. }
  498. this._avgAudioBitrateUp.addNext(bitrate.audio.upload);
  499. this._avgAudioBitrateDown.addNext(bitrate.audio.download);
  500. this._avgVideoBitrateUp.addNext(bitrate.video.upload);
  501. this._avgVideoBitrateDown.addNext(bitrate.video.download);
  502. if (RTCBrowserType.supportsBandwidthStatistics()) {
  503. this._avgBandwidthUp.addNext(bandwidth.upload);
  504. this._avgBandwidthDown.addNext(bandwidth.download);
  505. }
  506. this._avgPacketLossUp.addNext(packetLoss.upload);
  507. this._avgPacketLossDown.addNext(packetLoss.download);
  508. this._avgPacketLossTotal.addNext(packetLoss.total);
  509. this._avgCQ.addNext(data.connectionQuality);
  510. if (frameRate) {
  511. this._avgRemoteFPS.addNext(
  512. this._calculateAvgVideoFps(
  513. frameRate, false /* remote */, VideoType.CAMERA));
  514. this._avgRemoteScreenFPS.addNext(
  515. this._calculateAvgVideoFps(
  516. frameRate, false /* remote */, VideoType.DESKTOP));
  517. this._avgLocalFPS.addNext(
  518. this._calculateAvgVideoFps(
  519. frameRate, true /* local */, VideoType.CAMERA));
  520. this._avgLocalScreenFPS.addNext(
  521. this._calculateAvgVideoFps(
  522. frameRate, true /* local */, VideoType.DESKTOP));
  523. }
  524. this._sampleIdx += 1;
  525. if (this._sampleIdx >= this._n) {
  526. const batchReport = { };
  527. this._avgAudioBitrateUp.appendReport(batchReport, prefix);
  528. this._avgAudioBitrateDown.appendReport(batchReport, prefix);
  529. this._avgVideoBitrateUp.appendReport(batchReport, prefix);
  530. this._avgVideoBitrateDown.appendReport(batchReport, prefix);
  531. if (RTCBrowserType.supportsBandwidthStatistics()) {
  532. this._avgBandwidthUp.appendReport(batchReport, prefix);
  533. this._avgBandwidthDown.appendReport(batchReport, prefix);
  534. }
  535. this._avgPacketLossUp.appendReport(batchReport, prefix);
  536. this._avgPacketLossDown.appendReport(batchReport, prefix);
  537. this._avgPacketLossTotal.appendReport(batchReport, prefix);
  538. this._avgRemoteFPS.appendReport(batchReport, prefix);
  539. if (!isNaN(this._avgRemoteScreenFPS.calculate())) {
  540. this._avgRemoteScreenFPS.appendReport(batchReport, prefix);
  541. }
  542. this._avgLocalFPS.appendReport(batchReport, prefix);
  543. if (!isNaN(this._avgLocalScreenFPS.calculate())) {
  544. this._avgLocalScreenFPS.appendReport(batchReport, prefix);
  545. }
  546. this._avgCQ.appendReport(batchReport, prefix);
  547. Statistics.analytics.sendEvent(AVG_RTP_STATS_EVENT, batchReport);
  548. this._resetAvgStats();
  549. }
  550. }
  551. /**
  552. * Calculates average FPS for the report
  553. * @param {go figure} frameRate
  554. * @param {boolean} isLocal if the average is to be calculated for the local
  555. * video or <tt>false</tt> if for remote videos.
  556. * @param {VideoType} videoType
  557. * @return {number|NaN} average FPS or <tt>NaN</tt> if there are no samples.
  558. * @private
  559. */
  560. _calculateAvgVideoFps(frameRate, isLocal, videoType) {
  561. let peerFpsSum = 0;
  562. let peerCount = 0;
  563. const myID = this._conference.myUserId();
  564. for (const peerID of Object.keys(frameRate)) {
  565. if (isLocal ? peerID === myID : peerID !== myID) {
  566. const participant
  567. = isLocal
  568. ? null : this._conference.getParticipantById(peerID);
  569. const videosFps = frameRate[peerID];
  570. // Do not continue without participant for non local peerID
  571. if ((isLocal || participant) && videosFps) {
  572. const peerAvgFPS
  573. = this._calculatePeerAvgVideoFps(
  574. videosFps, participant, videoType);
  575. if (!isNaN(peerAvgFPS)) {
  576. peerFpsSum += peerAvgFPS;
  577. peerCount += 1;
  578. }
  579. }
  580. }
  581. }
  582. return peerFpsSum / peerCount;
  583. }
  584. /**
  585. * Calculate average FPS for either remote or local participant
  586. * @param {object} videos maps FPS per video SSRC
  587. * @param {JitsiParticipant|null} participant remote participant or
  588. * <tt>null</tt> for local FPS calculation.
  589. * @param {VideoType} videoType the type of the video for which an average
  590. * will be calculated.
  591. * @return {number|NaN} average FPS of all participant's videos or
  592. * <tt>NaN</tt> if currently not available
  593. * @private
  594. */
  595. _calculatePeerAvgVideoFps(videos, participant, videoType) {
  596. let ssrcs = Object.keys(videos).map(ssrc => Number(ssrc));
  597. let videoTracks = null;
  598. // NOTE that this method is supposed to be called for the stats
  599. // received from the current peerconnection.
  600. const tpc = this._conference.getActivePeerConnection();
  601. if (participant) {
  602. videoTracks = participant.getTracksByMediaType(MediaType.VIDEO);
  603. if (videoTracks) {
  604. ssrcs
  605. = ssrcs.filter(
  606. ssrc => videoTracks.find(
  607. track => !track.isMuted()
  608. && track.getSSRC() === ssrc
  609. && track.videoType === videoType));
  610. }
  611. } else {
  612. videoTracks = this._conference.getLocalTracks(MediaType.VIDEO);
  613. ssrcs
  614. = ssrcs.filter(
  615. ssrc => videoTracks.find(
  616. track => !track.isMuted()
  617. && tpc.getLocalSSRC(track) === ssrc
  618. && track.videoType === videoType));
  619. }
  620. let peerFpsSum = 0;
  621. let peerSsrcCount = 0;
  622. for (const ssrc of ssrcs) {
  623. const peerSsrcFps = Number(videos[ssrc]);
  624. // FPS is reported as 0 for users with no video
  625. if (!isNaN(peerSsrcFps) && peerSsrcFps > 0) {
  626. peerFpsSum += peerSsrcFps;
  627. peerSsrcCount += 1;
  628. }
  629. }
  630. return peerFpsSum / peerSsrcCount;
  631. }
  632. /**
  633. * Reset cache of all averages and {@link _sampleIdx}.
  634. * @private
  635. */
  636. _resetAvgStats() {
  637. this._avgAudioBitrateUp.reset();
  638. this._avgAudioBitrateDown.reset();
  639. this._avgVideoBitrateUp.reset();
  640. this._avgVideoBitrateDown.reset();
  641. this._avgBandwidthUp.reset();
  642. this._avgBandwidthDown.reset();
  643. this._avgPacketLossUp.reset();
  644. this._avgPacketLossDown.reset();
  645. this._avgPacketLossTotal.reset();
  646. this._avgRemoteFPS.reset();
  647. this._avgRemoteScreenFPS.reset();
  648. this._avgLocalFPS.reset();
  649. this._avgLocalScreenFPS.reset();
  650. this._avgCQ.reset();
  651. this._sampleIdx = 0;
  652. }
  653. /**
  654. * Unregisters all event listeners and stops working.
  655. */
  656. dispose() {
  657. this._conference.off(
  658. ConferenceEvents.P2P_STATUS,
  659. this._onP2PStatusChanged);
  660. this._conference.off(
  661. ConnectionQualityEvents.LOCAL_STATS_UPDATED,
  662. this._onLocalStatsUpdated);
  663. this.jvbStatsMonitor.dispose();
  664. this.p2pStatsMonitor.dispose();
  665. }
  666. }