Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

AvgRTPStatsReporter.js 25KB

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