Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

RTCStats.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. options?: InitOptions;
  45. isPeerConnectionWrapped = false;
  46. connStateEvents: any = [];
  47. /**
  48. * Initialize the rtcstats components, if the options have changed.
  49. *
  50. * @param {Oject} newOptions -.
  51. * @returns {void}
  52. */
  53. maybeInit(newOptions: InitOptions) {
  54. const oldOptions = this.options;
  55. const changed = !oldOptions || (oldOptions.endpoint !== newOptions.endpoint
  56. || oldOptions.meetingFqn !== newOptions.meetingFqn
  57. || oldOptions.pollInterval !== newOptions.pollInterval
  58. || oldOptions.sendSdp !== newOptions.sendSdp
  59. || oldOptions.useLegacy !== newOptions.useLegacy);
  60. if (changed) {
  61. this.reset();
  62. if (newOptions.meetingFqn && newOptions.endpoint) {
  63. this.init(newOptions);
  64. } else {
  65. logger.warn('RTCStats is enabled but it has not been configured.');
  66. }
  67. }
  68. }
  69. /**
  70. * Wrapper method for the underlying trace object to be used as a static reference from inside the wrapped
  71. * PeerConnection.
  72. *
  73. * @param {any[]} data - The stats entry to send to the rtcstats endpoint.
  74. * @returns {void}
  75. */
  76. statsEntry(...data: any[]) {
  77. this.trace?.statsEntry(data);
  78. }
  79. /**
  80. * Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket
  81. * that does the actual communication with the server. Secondly, the rtcstats component is initialized,
  82. * it overwrites GUM and PeerConnection global functions and adds a proxy over them used to capture stats.
  83. * Note, lib-jitsi-meet takes references to these methods before initializing so the init method needs to be
  84. * loaded before it does.
  85. *
  86. * @param {Object} options -.
  87. * @param {string} options.endpoint - The Amplitude app key required.
  88. * @param {string} options.meetingFqn - The meeting fqn.
  89. * @param {boolean} options.useLegacy - Switch to legacy chrome webrtc statistics. Parameter will only have
  90. * an effect on chrome based applications.
  91. * @param {number} options.pollInterval - The getstats poll interval in ms.
  92. * @param {boolean} options.sendSdp - Determines if the client sends SDP to the rtcstats server.
  93. * @returns {void}
  94. */
  95. init(options: InitOptions) {
  96. const { endpoint, meetingFqn, useLegacy, pollInterval, sendSdp } = options;
  97. const traceOptions = {
  98. endpoint,
  99. meetingFqn,
  100. onCloseCallback: this.handleTraceWSClose.bind(this),
  101. useLegacy
  102. };
  103. this.trace = traceInit(traceOptions);
  104. if (!this.isPeerConnectionWrapped) {
  105. const rtcstatsOptions = {
  106. connectionFilter,
  107. pollInterval,
  108. useLegacy,
  109. sendSdp,
  110. eventCallback: this.handleRtcstatsEvent.bind(this)
  111. };
  112. const statsEntryCallback = { statsEntry: this.statsEntry.bind(this) };
  113. rtcstatsInit(statsEntryCallback, rtcstatsOptions);
  114. this.isPeerConnectionWrapped = true;
  115. }
  116. this.options = options;
  117. }
  118. /**
  119. * Check whether or not the RTCStats is initialized.
  120. *
  121. * @returns {boolean}
  122. */
  123. isInitialized() {
  124. return this.options !== undefined;
  125. }
  126. /**
  127. * Resets the rtcstats.
  128. *
  129. * @returns {void}
  130. */
  131. reset() {
  132. delete this.options;
  133. if (this.trace) {
  134. this.trace.close();
  135. delete this.trace;
  136. }
  137. }
  138. /**
  139. * Send identity data to rtcstats server, this will be reflected in the identity section of the stats dump.
  140. * It can be generally used to send additional metadata that might be relevant such as amplitude user data
  141. * or deployment specific information.
  142. *
  143. * @param {any} identityData - Metadata object to send as identity.
  144. * @returns {void}
  145. */
  146. sendIdentityData(identityData: any) {
  147. this.trace?.identity('identity', null, identityData);
  148. }
  149. /**
  150. * Send console logs to rtcstats server.
  151. *
  152. * @param {Array<string|any>} logEntries - The log entries to send to the rtcstats server.
  153. * @returns {void}
  154. */
  155. sendLogs(logEntries: Array<string | any>) {
  156. this.trace?.statsEntry('logs', null, logEntries);
  157. }
  158. /**
  159. * Send dominant speaker data, the data will be processed by rtcstats-server and saved in the dump file.
  160. *
  161. * @param {Object} dominantSpeakerData - Dominant speaker data to be saved in the rtcstats dump.
  162. * @returns {void}
  163. */
  164. sendDominantSpeakerData(dominantSpeakerData: DominantSpeakerData) {
  165. this.trace?.statsEntry('dominantSpeaker', null, dominantSpeakerData);
  166. }
  167. /**
  168. * Send e2e rtt data, the data will be processed by rtcstats-server and saved in the dump file.
  169. *
  170. * @param {Object} e2eRttData - The object that holds the e2e data.
  171. * @returns {void}
  172. */
  173. sendE2eRttData(e2eRttData: E2ERTTData) {
  174. this.trace?.statsEntry('e2eRtt', null, e2eRttData);
  175. }
  176. /**
  177. * Send the timestamp of the start of the conference, the data will be processed by the rtcstats-server
  178. * and saved in the dump file.
  179. *
  180. * @param {Object} timestamp - The object which contains the timestamp.
  181. * @returns {void}
  182. */
  183. sendConferenceTimestamp(timestamp: number) {
  184. this.trace?.statsEntry('conferenceStartTimestamp', null, timestamp);
  185. }
  186. /**
  187. * Send videoType data, the data will be processed by rtcstats-server and saved in the dump file.
  188. *
  189. * @param {Object} videoTypeData - The object that holds the videoType data.
  190. * @returns {void}
  191. */
  192. sendVideoTypeData(videoTypeData: VideoTypeData) {
  193. this.trace?.statsEntry('setVideoType', null, videoTypeData);
  194. }
  195. /**
  196. * Send face landmarks data, the data will be processed by rtcstats-server and saved in the dump file.
  197. *
  198. * @param {Object} faceLandmarksData - Face landmarks data to be saved in the rtcstats dump.
  199. * @returns {void}
  200. */
  201. sendFaceLandmarksData(faceLandmarksData: FaceLandmarksData) {
  202. this.trace?.statsEntry('faceLandmarks', null, faceLandmarksData);
  203. }
  204. /**
  205. * Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
  206. * connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
  207. * connected and sent once it is established.
  208. *
  209. * @param {boolean} isBreakoutRoom - Flag indicating if the user is in a breakout room.
  210. * @returns {void}
  211. */
  212. connect(isBreakoutRoom: boolean) {
  213. this.trace?.connect(isBreakoutRoom);
  214. }
  215. /**
  216. * Self explanatory; closes the web socked connection.
  217. * Note, at the point of writing this documentation there was no method to reset the function overwrites,
  218. * thus even if the websocket is closed the global function proxies are still active but send no data,
  219. * this shouldn't influence the normal flow of the application.
  220. *
  221. * @returns {void}
  222. */
  223. close() {
  224. this.trace?.close();
  225. }
  226. /**
  227. * RTCStats client can notify the APP of any PeerConnection related event that occurs.
  228. *
  229. * @param {Object} event - Rtcstats event.
  230. * @returns {void}
  231. */
  232. handleRtcstatsEvent(event: any) {
  233. switch (event.type) {
  234. case PC_CON_STATE_CHANGE: {
  235. const { body: { isP2P, state } = { state: null,
  236. isP2P: null } } = event;
  237. this.connStateEvents.push(event.body);
  238. // We only report PC related connection issues. If the rtcstats websocket is not connected at this point
  239. // it usually means that none of our services can be reached i.e. there's problem with the internet
  240. // connection and not necessarily with reaching the JVB (due to a firewall or other reasons).
  241. if (state === PC_STATE_FAILED && this.trace.isConnected()) {
  242. const connectionType = isP2P ? 'P2P' : 'JVB';
  243. const wasConnected = this.connStateEvents.some((connectionEvent: { isP2P: any; state: string; }) =>
  244. (connectionEvent.isP2P === isP2P) && (connectionEvent.state === PC_STATE_CONNECTED));
  245. logger.info(`${connectionType} PeerConnection failed, previously connected: ${wasConnected}`);
  246. if (typeof APP !== 'undefined') {
  247. APP.API.notifyPeerConnectionFailure(isP2P, wasConnected);
  248. }
  249. }
  250. break;
  251. }
  252. }
  253. }
  254. /**
  255. * The way rtcstats is currently designed the ws wouldn't normally be closed by the application logic but rather
  256. * by the page being closed/reloaded. Using this assumption any onclose event is most likely something abnormal
  257. * that happened on the ws. We then track this in order to determine how many rtcstats connection were closed
  258. * prematurely.
  259. *
  260. * @param {Object} closeEvent - Event sent by ws onclose.
  261. * @returns {void}
  262. */
  263. handleTraceWSClose(closeEvent: any) {
  264. logger.info('RTCStats trace ws closed', closeEvent);
  265. sendAnalytics(createRTCStatsTraceCloseEvent(closeEvent));
  266. }
  267. }
  268. export default new RTCStats();