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.

AvgRTPStatsReporter.js 32KB

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