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.

statsEmitter.ts 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { union } from 'lodash-es';
  2. import { IJitsiConference } from '../base/conference/reducer';
  3. import {
  4. JitsiConnectionQualityEvents
  5. } from '../base/lib-jitsi-meet';
  6. import { trackCodecChanged } from '../base/tracks/actions.any';
  7. import { getLocalTracks } from '../base/tracks/functions.any';
  8. /**
  9. * Contains all the callbacks to be notified when stats are updated.
  10. *
  11. * ```
  12. * {
  13. * userId: Function[]
  14. * }
  15. * ```
  16. */
  17. const subscribers: any = {};
  18. interface IStats {
  19. codec?: Object;
  20. framerate?: Object;
  21. resolution?: Object;
  22. }
  23. /**
  24. * A singleton that acts as a pub/sub service for connection stat updates.
  25. */
  26. const statsEmitter = {
  27. /**
  28. * Have {@code statsEmitter} subscribe to stat updates from a given
  29. * conference.
  30. *
  31. * @param {JitsiConference} conference - The conference for which
  32. * {@code statsEmitter} should subscribe for stat updates.
  33. * @returns {void}
  34. */
  35. startListeningForStats(conference: IJitsiConference) {
  36. conference.on(JitsiConnectionQualityEvents.LOCAL_STATS_UPDATED,
  37. (stats: IStats) => this._onStatsUpdated(conference.myUserId(), stats));
  38. conference.on(JitsiConnectionQualityEvents.REMOTE_STATS_UPDATED,
  39. (id: string, stats: IStats) => this._emitStatsUpdate(id, stats));
  40. },
  41. /**
  42. * Add a subscriber to be notified when stats are updated for a specified
  43. * user id.
  44. *
  45. * @param {string} id - The user id whose stats updates are of interest.
  46. * @param {Function} callback - The function to invoke when stats for the
  47. * user have been updated.
  48. * @returns {void}
  49. */
  50. subscribeToClientStats(id: string | undefined, callback: Function) {
  51. if (!id) {
  52. return;
  53. }
  54. if (!subscribers[id]) {
  55. subscribers[id] = [];
  56. }
  57. subscribers[id].push(callback);
  58. },
  59. /**
  60. * Remove a subscriber that is listening for stats updates for a specified
  61. * user id.
  62. *
  63. * @param {string} id - The user id whose stats updates are no longer of
  64. * interest.
  65. * @param {Function} callback - The function that is currently subscribed to
  66. * stat updates for the specified user id.
  67. * @returns {void}
  68. */
  69. unsubscribeToClientStats(id: string, callback: Function) {
  70. if (!subscribers[id]) {
  71. return;
  72. }
  73. const filteredSubscribers = subscribers[id].filter(
  74. (subscriber: Function) => subscriber !== callback);
  75. if (filteredSubscribers.length) {
  76. subscribers[id] = filteredSubscribers;
  77. } else {
  78. delete subscribers[id];
  79. }
  80. },
  81. /**
  82. * Emit a stat update to all those listening for a specific user's
  83. * connection stats.
  84. *
  85. * @param {string} id - The user id the stats are associated with.
  86. * @param {Object} stats - New connection stats for the user.
  87. * @returns {void}
  88. */
  89. _emitStatsUpdate(id: string, stats: IStats = {}) {
  90. const callbacks = subscribers[id] || [];
  91. callbacks.forEach((callback: Function) => {
  92. callback(stats);
  93. });
  94. },
  95. /**
  96. * Emit a stat update to all those listening for local stat updates. Will
  97. * also update listeners of remote user stats of changes related to their
  98. * stats.
  99. *
  100. * @param {string} localUserId - The user id for the local user.
  101. * @param {Object} stats - Connection stats for the local user as provided
  102. * by the library.
  103. * @returns {void}
  104. */
  105. _onStatsUpdated(localUserId: string, stats: IStats) {
  106. const allUserFramerates = stats.framerate || {};
  107. const allUserResolutions = stats.resolution || {};
  108. const allUserCodecs = stats.codec || {};
  109. // FIXME resolution and framerate are maps keyed off of user ids with
  110. // stat values. Receivers of stats expect resolution and framerate to
  111. // be primitives, not maps, so here we override the 'lib-jitsi-meet'
  112. // stats objects.
  113. const modifiedLocalStats = Object.assign({}, stats, {
  114. framerate: allUserFramerates[localUserId as keyof typeof allUserFramerates],
  115. resolution: allUserResolutions[localUserId as keyof typeof allUserResolutions],
  116. codec: allUserCodecs[localUserId as keyof typeof allUserCodecs]
  117. });
  118. modifiedLocalStats.codec
  119. && Object.keys(modifiedLocalStats.codec).length
  120. && this._updateLocalCodecs(modifiedLocalStats.codec);
  121. this._emitStatsUpdate(localUserId, modifiedLocalStats);
  122. // Get all the unique user ids from the framerate and resolution stats
  123. // and update remote user stats as needed.
  124. const framerateUserIds = Object.keys(allUserFramerates);
  125. const resolutionUserIds = Object.keys(allUserResolutions);
  126. const codecUserIds = Object.keys(allUserCodecs);
  127. union(framerateUserIds, resolutionUserIds, codecUserIds)
  128. .filter(id => id !== localUserId)
  129. .forEach(id => {
  130. const remoteUserStats: IStats = {};
  131. const framerate = allUserFramerates[id as keyof typeof allUserFramerates];
  132. if (framerate) {
  133. remoteUserStats.framerate = framerate;
  134. }
  135. const resolution = allUserResolutions[id as keyof typeof allUserResolutions];
  136. if (resolution) {
  137. remoteUserStats.resolution = resolution;
  138. }
  139. const codec = allUserCodecs[id as keyof typeof allUserCodecs];
  140. if (codec) {
  141. remoteUserStats.codec = codec;
  142. }
  143. this._emitStatsUpdate(id, remoteUserStats);
  144. });
  145. },
  146. /**
  147. * Updates the codec associated with the local tracks.
  148. * This is currently used for torture tests.
  149. *
  150. * @param {any} codecs - Codec information per local SSRC.
  151. * @returns {void}
  152. */
  153. _updateLocalCodecs(codecs: any) {
  154. if (typeof APP !== 'undefined') {
  155. const tracks = APP.store.getState()['features/base/tracks'];
  156. const localTracks = getLocalTracks(tracks);
  157. for (const track of localTracks) {
  158. const ssrc = track.jitsiTrack?.getSsrc();
  159. if (ssrc && Object.keys(codecs).find(key => Number(key) === ssrc)) {
  160. const codecsPerSsrc = codecs[ssrc];
  161. const codec = codecsPerSsrc.audio ?? codecsPerSsrc.video;
  162. if (track.codec !== codec) {
  163. APP.store.dispatch(trackCodecChanged(track.jitsiTrack, codec));
  164. }
  165. }
  166. }
  167. }
  168. }
  169. };
  170. export default statsEmitter;