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.

RTCStats.ts 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // @ts-ignore
  2. import rtcstatsInit from '@jitsi/rtcstats/rtcstats';
  3. // eslint-disable-next-line lines-around-comment
  4. // @ts-ignore
  5. import traceInit from '@jitsi/rtcstats/trace-ws';
  6. import { createRTCStatsTraceCloseEvent } from '../analytics/AnalyticsEvents';
  7. import { sendAnalytics } from '../analytics/functions';
  8. import logger from './logger';
  9. import {
  10. DominantSpeakerData,
  11. E2ERTTData,
  12. FaceLandmarksData,
  13. InitOptions,
  14. VideoTypeData
  15. } from './types';
  16. /**
  17. * Filter out RTCPeerConnection that are created by callstats.io.
  18. *
  19. * @param {*} config - Config object sent to the PC c'tor.
  20. * @returns {boolean}
  21. */
  22. function connectionFilter(config: any) {
  23. if (config?.iceServers[0] && config.iceServers[0].urls) {
  24. for (const iceUrl of config.iceServers[0].urls) {
  25. if (iceUrl.indexOf('callstats.io') >= 0) {
  26. return true;
  27. }
  28. }
  29. }
  30. }
  31. /**
  32. * Class that controls the rtcstats flow, because it overwrites and proxies global function it should only be
  33. * initialized once.
  34. */
  35. class RTCStats {
  36. trace: any;
  37. initialized = false;
  38. /**
  39. * Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket
  40. * that does the actual communication with the server. Secondly, the rtcstats component is initialized,
  41. * it overwrites GUM and PeerConnection global functions and adds a proxy over them used to capture stats.
  42. * Note, lib-jitsi-meet takes references to these methods before initializing so the init method needs to be
  43. * loaded before it does.
  44. *
  45. * @param {Object} options -.
  46. * @param {string} options.endpoint - The Amplitude app key required.
  47. * @param {string} options.meetingFqn - The meeting fqn.
  48. * @param {boolean} options.useLegacy - Switch to legacy chrome webrtc statistics. Parameter will only have
  49. * an effect on chrome based applications.
  50. * @param {number} options.pollInterval - The getstats poll interval in ms.
  51. * @param {boolean} options.sendSdp - Determines if the client sends SDP to the rtcstats server.
  52. * @returns {void}
  53. */
  54. init(options: InitOptions) {
  55. const { endpoint, meetingFqn, useLegacy, pollInterval, sendSdp } = options;
  56. const traceOptions = {
  57. endpoint,
  58. meetingFqn,
  59. onCloseCallback: this.handleTraceWSClose.bind(this),
  60. useLegacy
  61. };
  62. const rtcstatsOptions = {
  63. connectionFilter,
  64. pollInterval,
  65. useLegacy,
  66. sendSdp
  67. };
  68. this.trace = traceInit(traceOptions);
  69. rtcstatsInit(this.trace, rtcstatsOptions);
  70. this.initialized = true;
  71. }
  72. /**
  73. * Check whether or not the RTCStats is initialized.
  74. *
  75. * @returns {boolean}
  76. */
  77. isInitialized() {
  78. return this.initialized;
  79. }
  80. /**
  81. * Send identity data to rtcstats server, this will be reflected in the identity section of the stats dump.
  82. * It can be generally used to send additional metadata that might be relevant such as amplitude user data
  83. * or deployment specific information.
  84. *
  85. * @param {any} identityData - Metadata object to send as identity.
  86. * @returns {void}
  87. */
  88. sendIdentityData(identityData: any) {
  89. this.trace?.identity('identity', null, identityData);
  90. }
  91. /**
  92. * Send console logs to rtcstats server.
  93. *
  94. * @param {Array<string|any>} logEntries - The log entries to send to the rtcstats server.
  95. * @returns {void}
  96. */
  97. sendLogs(logEntries: Array<string | any>) {
  98. this.trace?.statsEntry('logs', null, logEntries);
  99. }
  100. /**
  101. * Send dominant speaker data, the data will be processed by rtcstats-server and saved in the dump file.
  102. *
  103. * @param {Object} dominantSpeakerData - Dominant speaker data to be saved in the rtcstats dump.
  104. * @returns {void}
  105. */
  106. sendDominantSpeakerData(dominantSpeakerData: DominantSpeakerData) {
  107. this.trace?.statsEntry('dominantSpeaker', null, dominantSpeakerData);
  108. }
  109. /**
  110. * Send e2e rtt data, the data will be processed by rtcstats-server and saved in the dump file.
  111. *
  112. * @param {Object} e2eRttData - The object that holds the e2e data.
  113. * @returns {void}
  114. */
  115. sendE2eRttData(e2eRttData: E2ERTTData) {
  116. this.trace?.statsEntry('e2eRtt', null, e2eRttData);
  117. }
  118. /**
  119. * Send the timestamp of the start of the conference, the data will be processed by the rtcstats-server
  120. * and saved in the dump file.
  121. *
  122. * @param {Object} timestamp - The object which contains the timestamp.
  123. * @returns {void}
  124. */
  125. sendConferenceTimestamp(timestamp: number) {
  126. this.trace?.statsEntry('conferenceStartTimestamp', null, timestamp);
  127. }
  128. /**
  129. * Send videoType data, the data will be processed by rtcstats-server and saved in the dump file.
  130. *
  131. * @param {Object} videoTypeData - The object that holds the videoType data.
  132. * @returns {void}
  133. */
  134. sendVideoTypeData(videoTypeData: VideoTypeData) {
  135. this.trace?.statsEntry('setVideoType', null, videoTypeData);
  136. }
  137. /**
  138. * Send face landmarks data, the data will be processed by rtcstats-server and saved in the dump file.
  139. *
  140. * @param {Object} faceLandmarksData - Face landmarks data to be saved in the rtcstats dump.
  141. * @returns {void}
  142. */
  143. sendFaceLandmarksData(faceLandmarksData: FaceLandmarksData) {
  144. this.trace?.statsEntry('faceLandmarks', null, faceLandmarksData);
  145. }
  146. /**
  147. * Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
  148. * connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
  149. * connected and sent once it is established.
  150. *
  151. * @param {boolean} isBreakoutRoom - Flag indicating if the user is in a breakout room.
  152. * @returns {void}
  153. */
  154. connect(isBreakoutRoom: boolean) {
  155. this.trace?.connect(isBreakoutRoom);
  156. }
  157. /**
  158. * Self explanatory; closes the web socked connection.
  159. * Note, at the point of writing this documentation there was no method to reset the function overwrites,
  160. * thus even if the websocket is closed the global function proxies are still active but send no data,
  161. * this shouldn't influence the normal flow of the application.
  162. *
  163. * @returns {void}
  164. */
  165. close() {
  166. this.trace?.close();
  167. }
  168. /**
  169. * The way rtcstats is currently designed the ws wouldn't normally be closed by the application logic but rather
  170. * by the page being closed/reloaded. Using this assumption any onclose event is most likely something abnormal
  171. * that happened on the ws. We then track this in order to determine how many rtcstats connection were closed
  172. * prematurely.
  173. *
  174. * @param {Object} closeEvent - Event sent by ws onclose.
  175. * @returns {void}
  176. */
  177. handleTraceWSClose(closeEvent: any) {
  178. logger.info('RTCStats trace ws closed', closeEvent);
  179. sendAnalytics(createRTCStatsTraceCloseEvent(closeEvent));
  180. }
  181. }
  182. export default new RTCStats();