modified lib-jitsi-meet dev repo
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

AvgRTPStatsReporter.js 22KB

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