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.9KB

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