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.

middleware.ts 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /* eslint-disable lines-around-comment */
  2. import { AnyAction } from 'redux';
  3. import { IStore } from '../app/types';
  4. import {
  5. CONFERENCE_JOINED,
  6. CONFERENCE_TIMESTAMP_CHANGED,
  7. CONFERENCE_UNIQUE_ID_SET,
  8. CONFERENCE_WILL_LEAVE,
  9. E2E_RTT_CHANGED
  10. // @ts-ignore
  11. } from '../base/conference';
  12. import { LIB_WILL_INIT } from '../base/lib-jitsi-meet/actionTypes';
  13. import { DOMINANT_SPEAKER_CHANGED } from '../base/participants/actionTypes';
  14. import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
  15. import { TRACK_ADDED, TRACK_UPDATED } from '../base/tracks/actionTypes';
  16. import { getCurrentRoomId, isInBreakoutRoom } from '../breakout-rooms/functions';
  17. // @ts-ignore
  18. import { extractFqnFromPath } from '../dynamic-branding/functions.any';
  19. import { ADD_FACE_EXPRESSION, FACE_LANDMARK_DETECTION_STOPPED } from '../face-landmarks/actionTypes';
  20. import RTCStats from './RTCStats';
  21. import {
  22. canSendFaceLandmarksRtcstatsData,
  23. canSendRtcstatsData,
  24. connectAndSendIdentity,
  25. isRtcstatsEnabled
  26. } from './functions';
  27. import logger from './logger';
  28. /**
  29. * Middleware which intercepts lib-jitsi-meet initialization and conference join in order init the
  30. * rtcstats-client.
  31. *
  32. * @param {Store} store - The redux store.
  33. * @returns {Function}
  34. */
  35. MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => {
  36. const { getState } = store;
  37. const state = getState();
  38. const config = state['features/base/config'];
  39. const { analytics } = config;
  40. switch (action.type) {
  41. case LIB_WILL_INIT: {
  42. if (isRtcstatsEnabled(state) && !RTCStats.isInitialized()) {
  43. // RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
  44. // window functions. Because lib-jitsi-meet uses references to those functions that are taken on
  45. // init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
  46. // original non proxy versions of these functions.
  47. try {
  48. // Default poll interval is 10000ms and standard stats will be used, if not provided in the config.
  49. const pollInterval = analytics?.rtcstatsPollInterval || 10000;
  50. const useLegacy = analytics?.rtcstatsUseLegacy || false;
  51. const sendSdp = analytics?.rtcstatsSendSdp || false;
  52. // Initialize but don't connect to the rtcstats server wss, as it will start sending data for all
  53. // media calls made even before the conference started.
  54. RTCStats.init({
  55. endpoint: analytics?.rtcstatsEndpoint,
  56. meetingFqn: extractFqnFromPath(state),
  57. useLegacy,
  58. pollInterval,
  59. sendSdp
  60. });
  61. } catch (error) {
  62. logger.error('Failed to initialize RTCStats: ', error);
  63. }
  64. }
  65. break;
  66. }
  67. // Used for connecting to rtcstats server when joining a breakout room.
  68. // Breakout rooms do not have a meetingUniqueId.
  69. case CONFERENCE_JOINED: {
  70. if (isInBreakoutRoom(getState())) {
  71. connectAndSendIdentity(
  72. store,
  73. {
  74. isBreakoutRoom: true,
  75. roomId: getCurrentRoomId(getState())
  76. }
  77. );
  78. }
  79. break;
  80. }
  81. // Used for connecting to rtcstats server when joining the main room.
  82. // Using this event to be sure the meetingUniqueId can be retrieved.
  83. case CONFERENCE_UNIQUE_ID_SET: {
  84. if (!isInBreakoutRoom(getState())) {
  85. // Unique identifier for a conference session, not to be confused with meeting name
  86. // i.e. If all participants leave a meeting it will have a different value on the next join.
  87. const { conference } = action;
  88. const meetingUniqueId = conference?.getMeetingUniqueId();
  89. connectAndSendIdentity(
  90. store,
  91. {
  92. isBreakoutRoom: false,
  93. meetingUniqueId
  94. }
  95. );
  96. }
  97. break;
  98. }
  99. case TRACK_ADDED: {
  100. if (canSendRtcstatsData(state)) {
  101. const jitsiTrack = action?.track?.jitsiTrack;
  102. const { ssrc, videoType } = jitsiTrack || { };
  103. // Remote tracks store their ssrc in the jitsiTrack object. Local tracks don't. See getSsrcByTrack.
  104. if (videoType && ssrc && !jitsiTrack.isLocal() && !jitsiTrack.isAudioTrack()) {
  105. RTCStats.sendVideoTypeData({
  106. ssrc,
  107. videoType
  108. });
  109. }
  110. }
  111. break;
  112. }
  113. case TRACK_UPDATED: {
  114. if (canSendRtcstatsData(state)) {
  115. const { videoType, jitsiTrack } = action?.track || { };
  116. const { ssrc } = jitsiTrack || { };
  117. // if the videoType of the remote track has changed we expect to find it in track.videoType. grep for
  118. // trackVideoTypeChanged.
  119. if (videoType && ssrc && !jitsiTrack.isLocal() && !jitsiTrack.isAudioTrack()) {
  120. RTCStats.sendVideoTypeData({
  121. ssrc,
  122. videoType
  123. });
  124. }
  125. }
  126. break;
  127. }
  128. case DOMINANT_SPEAKER_CHANGED: {
  129. if (canSendRtcstatsData(state)) {
  130. const { id, previousSpeakers, silence } = action.participant;
  131. RTCStats.sendDominantSpeakerData({
  132. dominantSpeakerEndpoint: silence ? null : id,
  133. previousSpeakers
  134. });
  135. }
  136. break;
  137. }
  138. case E2E_RTT_CHANGED: {
  139. if (canSendRtcstatsData(state)) {
  140. const { participant, rtt } = action.e2eRtt;
  141. RTCStats.sendE2eRttData({
  142. remoteEndpointId: participant.getId(),
  143. rtt,
  144. remoteRegion: participant.getProperty('region')
  145. });
  146. }
  147. break;
  148. }
  149. case ADD_FACE_EXPRESSION:
  150. case FACE_LANDMARK_DETECTION_STOPPED: {
  151. if (canSendFaceLandmarksRtcstatsData(state)) {
  152. const { duration, faceExpression, timestamp } = action;
  153. RTCStats.sendFaceLandmarksData({
  154. duration: duration ?? 0,
  155. faceLandmarks: faceExpression ?? 'detection-off',
  156. timestamp
  157. });
  158. }
  159. break;
  160. }
  161. case CONFERENCE_TIMESTAMP_CHANGED: {
  162. if (canSendRtcstatsData(state)) {
  163. const { conferenceTimestamp } = action;
  164. RTCStats.sendConferenceTimestamp(conferenceTimestamp);
  165. }
  166. break;
  167. }
  168. case CONFERENCE_WILL_LEAVE: {
  169. if (canSendRtcstatsData(state)) {
  170. RTCStats.close();
  171. }
  172. break;
  173. }
  174. }
  175. return next(action);
  176. });