Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

RTCStats.ts 8.7KB

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