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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { getLogger } from '@jitsi/logger';
  2. import rtcstatsInit from '@jitsi/rtcstats/rtcstats';
  3. import traceInit from '@jitsi/rtcstats/trace-ws';
  4. import EventEmitter from 'events';
  5. import {
  6. CONFERENCE_JOINED,
  7. CONFERENCE_LEFT,
  8. CONFERENCE_UNIQUE_ID_SET
  9. } from '../../JitsiConferenceEvents';
  10. import JitsiConference from '../../JitsiConference';
  11. import { IRTCStatsConfiguration } from './interfaces';
  12. import { RTC_STATS_PC_EVENT, RTC_STATS_WC_DISCONNECTED } from './RTCStatsEvents';
  13. const logger = getLogger(__filename);
  14. /**
  15. * RTCStats Singleton that is initialized only once for the lifetime of the app, subsequent calls to init will be ignored.
  16. * Config and conference changes are handled by the start method.
  17. */
  18. class RTCStats {
  19. private _initialized: boolean = false;
  20. private _trace: any = null;
  21. public events: EventEmitter = new EventEmitter();
  22. /**
  23. * RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global objects.
  24. * The proxies will then send data to the rtcstats server via the trace object.
  25. * The initialization procedure must be called once when lib-jitsi-meet is loaded.
  26. *
  27. * @param {IRTCStatsConfiguration} initConfig initial config for rtcstats.
  28. * @returns {void}
  29. */
  30. init(initConfig: IRTCStatsConfiguration) {
  31. const {
  32. analytics: {
  33. rtcstatsUseLegacy: useLegacy = false,
  34. rtcstatsPollInterval: pollInterval= 10000,
  35. rtcstatsSendSdp: sendSdp = false,
  36. rtcstatsEnabled = false
  37. } = {}
  38. } = initConfig;
  39. // If rtcstats is not enabled or already initialized, do nothing.
  40. // Calling rtcsatsInit multiple times will cause the global objects to be rewritten multiple times,
  41. // with unforeseen consequences.
  42. if (!rtcstatsEnabled || this._initialized) return;
  43. rtcstatsInit(
  44. { statsEntry: this.sendStatsEntry.bind(this) },
  45. { pollInterval,
  46. useLegacy,
  47. sendSdp,
  48. eventCallback: (event) => this.events.emit(RTC_STATS_PC_EVENT, event)}
  49. );
  50. this._initialized = true;
  51. }
  52. /**
  53. * When a conference is about to start, we need to reset the trace module, and initialize it with the
  54. * new conference's config. On a normal conference flow this wouldn't be necessary, as the whole page is
  55. * reloaded, but in the case of breakout rooms or react native the js context doesn't reload, hence the
  56. * RTCStats singleton and its config persists between conferences.
  57. *
  58. * @param conference - JitsiConference instance that's about to start.
  59. * @returns {void}
  60. */
  61. start(conference: JitsiConference) {
  62. const {
  63. options: {
  64. config : confConfig = {},
  65. name: confName = ''
  66. } = {},
  67. _statsCurrentId : displayName = ''
  68. } = conference;
  69. const {
  70. analytics: {
  71. rtcstatsEnabled = false,
  72. rtcstatsEndpoint: endpoint = '',
  73. rtcstatsUseLegacy: useLegacy = false
  74. } = {}
  75. } = confConfig;
  76. // Reset the trace module in case it wasn't during the previous conference.
  77. // Closing the underlying websocket connection and deleting the trace obj.
  78. this.reset();
  79. // The new conference config might have rtcstats disabled, so we need to check again.
  80. if (!rtcstatsEnabled) return;
  81. // If rtcstats proxy module is not initialized, do nothing.
  82. if (!this._initialized) {
  83. logger.error('Calling start before RTCStats proxy module is initialized.');
  84. return;
  85. }
  86. // When the conference is joined, we need to initialize the trace module with the new conference's config.
  87. // The trace module will then connect to the rtcstats server and send the identity data.
  88. conference.once(CONFERENCE_JOINED, () => {
  89. const traceOptions = {
  90. endpoint,
  91. meetingFqn: confName,
  92. onCloseCallback: (event) => this.events.emit(RTC_STATS_WC_DISCONNECTED, event),
  93. useLegacy
  94. };
  95. const isBreakoutRoom = Boolean(conference.getBreakoutRooms()?.isBreakoutRoom());
  96. const endpointId = conference.myUserId();
  97. const meetingUniqueId = conference.getMeetingUniqueId();
  98. this._trace = traceInit(traceOptions);
  99. // Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
  100. // connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
  101. // connected and sent once it is established.
  102. this._trace.connect(isBreakoutRoom);
  103. const identityData = {
  104. ...confConfig,
  105. endpointId,
  106. confName,
  107. displayName,
  108. meetingUniqueId,
  109. isBreakoutRoom
  110. }
  111. this.sendIdentity(identityData);
  112. });
  113. // Note, this will only be called for normal rooms, not breakout rooms.
  114. conference.once(CONFERENCE_UNIQUE_ID_SET, (meetingUniqueId) => {
  115. this.sendIdentity({meetingUniqueId});
  116. });
  117. conference.once(CONFERENCE_LEFT, () => {
  118. this.reset();
  119. });
  120. }
  121. /**
  122. * Sends the identity data to the rtcstats server.
  123. *
  124. * @param identityData - Identity data to send.
  125. * @returns {void}
  126. */
  127. sendIdentity(identityData) {
  128. this._trace?.identity('identity', null, identityData);
  129. }
  130. /**
  131. * Resets the trace module by closing the websocket and deleting the object.
  132. * After reset, the rtcstats proxy module that tries to send data via `sendStatsEntry`, will no longer
  133. * send any data, until the trace module is initialized again. This comes in handy on react-native
  134. * where ljm doesn't get reloaded, so we need to switch the trace module between conferences.
  135. *
  136. * @returns {void}
  137. */
  138. reset() {
  139. this._trace?.close();
  140. this._trace = null;
  141. }
  142. /**
  143. * Sends a stats entry to the rtcstats server. This is called by the rtcstats proxy module,
  144. * or any other app that wants to send custom stats.
  145. *
  146. * @param entry - Stats entry to send.
  147. * @returns {void}
  148. */
  149. sendStatsEntry(statsType, pcId, data) {
  150. this._trace?.statsEntry(statsType, pcId, data);
  151. }
  152. }
  153. export default new RTCStats();