Преглед на файлове

feat(stats): Get audio levels for the top 5 speakers only.

Capture the audio levels only for the top 5 speakers as RTCRtpReceiver#getSynchronizationSources can be expensive when we have too many audio receivers in the call.
Also, capture the audio levels for track that are unmuted if RTCRtpReceiver#getSynchronizationSources is not supported.
Switch Safari to using getStats since its reporting errorneous values, i.e., 0.000001 as audio level for all remote audio tracks.
dev1
Jaya Allamsetty преди 4 години
родител
ревизия
43c589f409
No account linked to committer's email address

+ 15
- 0
JitsiConferenceEventManager.js Целия файл

5
 
5
 
6
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
6
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
7
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
7
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
8
+import { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';
8
 import Statistics from './modules/statistics/statistics';
9
 import Statistics from './modules/statistics/statistics';
9
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
10
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
10
 import * as MediaType from './service/RTC/MediaType';
11
 import * as MediaType from './service/RTC/MediaType';
505
                 conference.eventEmitter.emit(
506
                 conference.eventEmitter.emit(
506
                     JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, dominant, previous);
507
                     JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, dominant, previous);
507
 
508
 
509
+                if (previous && previous.length) {
510
+                    const speakerList = previous.slice(0);
511
+
512
+                    // Add the dominant speaker to the top of the list (exclude self).
513
+                    if (conference.myUserId !== dominant) {
514
+                        speakerList.splice(0, 0, dominant);
515
+                    }
516
+
517
+                    // Trim the list to the top 5 speakers only.
518
+                    if (speakerList.length > SPEAKERS_AUDIO_LEVELS) {
519
+                        speakerList.splice(SPEAKERS_AUDIO_LEVELS, speakerList.length - SPEAKERS_AUDIO_LEVELS);
520
+                    }
521
+                    conference.statistics && conference.statistics.setSpeakerList(speakerList);
522
+                }
508
                 if (conference.statistics && conference.myUserId() === dominant) {
523
                 if (conference.statistics && conference.myUserId() === dominant) {
509
                     // We are the new dominant speaker.
524
                     // We are the new dominant speaker.
510
                     conference.statistics.sendDominantSpeakerEvent(conference.room.roomjid);
525
                     conference.statistics.sendDominantSpeakerEvent(conference.room.roomjid);

+ 35
- 8
modules/RTC/TraceablePeerConnection.js Целия файл

468
     return 'inactive';
468
     return 'inactive';
469
 };
469
 };
470
 
470
 
471
+/**
472
+ * Returns the list of RTCRtpReceivers created for the source of the given media type associated with
473
+ * the set of remote endpoints specified.
474
+ * @param {Array<string>} endpoints list of the endpoints
475
+ * @param {string} mediaType 'audio' or 'video'
476
+ * @returns {Array<RTCRtpReceiver>} list of receivers created by the peerconnection.
477
+ */
478
+TraceablePeerConnection.prototype._getReceiversByEndpointIds = function(endpoints, mediaType) {
479
+    let remoteTracks = [];
480
+    let receivers = [];
481
+
482
+    for (const endpoint of endpoints) {
483
+        remoteTracks = remoteTracks.concat(this.getRemoteTracks(endpoint, mediaType));
484
+    }
485
+
486
+    // Get the ids of the MediaStreamTracks associated with each of these remote tracks.
487
+    const remoteTrackIds = remoteTracks.map(remote => remote.track?.id);
488
+
489
+    receivers = this.peerconnection.getReceivers()
490
+        .filter(receiver => receiver.track
491
+            && receiver.track.kind === mediaType
492
+            && remoteTrackIds.find(trackId => trackId === receiver.track.id));
493
+
494
+    return receivers;
495
+};
496
+
471
 /**
497
 /**
472
  * Tells whether or not this TPC instance is using Simulcast.
498
  * Tells whether or not this TPC instance is using Simulcast.
473
  * @return {boolean} <tt>true</tt> if simulcast is enabled and active or
499
  * @return {boolean} <tt>true</tt> if simulcast is enabled and active or
526
 };
552
 };
527
 
553
 
528
 /**
554
 /**
529
- * Obtains audio levels of the remote audio tracks by getting the source
530
- * information on the RTCRtpReceivers. The information relevant to the ssrc
531
- * is updated each time a RTP packet constaining the ssrc is received.
532
- * @returns {Object} containing ssrc and audio level information as a
533
- * key-value pair.
555
+ * Obtains audio levels of the remote audio tracks by getting the source information on the RTCRtpReceivers.
556
+ * The information relevant to the ssrc is updated each time a RTP packet constaining the ssrc is received.
557
+ * @param {Array<string>} speakerList list of endpoint ids for which audio levels are to be gathered.
558
+ * @returns {Object} containing ssrc and audio level information as a key-value pair.
534
  */
559
  */
535
-TraceablePeerConnection.prototype.getAudioLevels = function() {
560
+TraceablePeerConnection.prototype.getAudioLevels = function(speakerList = []) {
536
     const audioLevels = {};
561
     const audioLevels = {};
537
-    const audioReceivers = this.peerconnection.getReceivers()
538
-        .filter(receiver => receiver.track && receiver.track.kind === MediaType.AUDIO);
562
+    const audioReceivers = speakerList.length
563
+        ? this._getReceiversByEndpointIds(speakerList, MediaType.AUDIO)
564
+        : this.peerconnection.getReceivers()
565
+            .filter(receiver => receiver.track && receiver.track.kind === MediaType.AUDIO && receiver.track.enabled);
539
 
566
 
540
     audioReceivers.forEach(remote => {
567
     audioReceivers.forEach(remote => {
541
         const ssrc = remote.getSynchronizationSources();
568
         const ssrc = remote.getSynchronizationSources();

+ 5
- 1
modules/browser/BrowserCapabilities.js Целия файл

182
      */
182
      */
183
     supportsReceiverStats() {
183
     supportsReceiverStats() {
184
         return typeof window.RTCRtpReceiver !== 'undefined'
184
         return typeof window.RTCRtpReceiver !== 'undefined'
185
-            && Object.keys(RTCRtpReceiver.prototype).indexOf('getSynchronizationSources') > -1;
185
+            && Object.keys(RTCRtpReceiver.prototype).indexOf('getSynchronizationSources') > -1
186
+
187
+            // Disable this on Safari because it is reporting 0.000001 as the audio levels for all
188
+            // remote audio tracks.
189
+            && !this.isWebKitBased();
186
     }
190
     }
187
 
191
 
188
     /**
192
     /**

+ 11
- 2
modules/statistics/RTPStatsCollector.js Целия файл

260
     // Updates stats interval
260
     // Updates stats interval
261
     this.audioLevelsIntervalMilis = audioLevelsInterval;
261
     this.audioLevelsIntervalMilis = audioLevelsInterval;
262
 
262
 
263
+    this.speakerList = [];
263
     this.statsIntervalId = null;
264
     this.statsIntervalId = null;
264
     this.statsIntervalMilis = statsInterval;
265
     this.statsIntervalMilis = statsInterval;
265
 
266
 
270
     this.ssrc2stats = new Map();
271
     this.ssrc2stats = new Map();
271
 }
272
 }
272
 
273
 
273
-/* eslint-enable max-params */
274
+/**
275
+ * Set the list of the remote speakers for which audio levels are to be calculated.
276
+ *
277
+ * @param {Array<string>} speakerList - Endpoint ids.
278
+ * @returns {void}
279
+ */
280
+StatsCollector.prototype.setSpeakerList = function(speakerList) {
281
+    this.speakerList = speakerList;
282
+};
274
 
283
 
275
 /**
284
 /**
276
  * Stops stats updates.
285
  * Stops stats updates.
308
         this.audioLevelsIntervalId = setInterval(
317
         this.audioLevelsIntervalId = setInterval(
309
             () => {
318
             () => {
310
                 if (browser.supportsReceiverStats()) {
319
                 if (browser.supportsReceiverStats()) {
311
-                    const audioLevels = this.peerconnection.getAudioLevels();
320
+                    const audioLevels = this.peerconnection.getAudioLevels(this.speakerList);
312
 
321
 
313
                     for (const ssrc in audioLevels) {
322
                     for (const ssrc in audioLevels) {
314
                         if (audioLevels.hasOwnProperty(ssrc)) {
323
                         if (audioLevels.hasOwnProperty(ssrc)) {

+ 7
- 0
modules/statistics/constants.js Целия файл

1
 export const CALLSTATS_SCRIPT_URL = 'https://api.callstats.io/static/callstats-ws.min.js';
1
 export const CALLSTATS_SCRIPT_URL = 'https://api.callstats.io/static/callstats-ws.min.js';
2
+
3
+/**
4
+ * The number of remote speakers for which the audio levels will be calculated using
5
+ * RTCRtpReceiver#getSynchronizationSources. Limit the number of endpoints to save cpu on the client as this API call
6
+ * is known to take longer to execute when there are many audio receivers.
7
+ */
8
+export const SPEAKERS_AUDIO_LEVELS = 5;

+ 14
- 0
modules/statistics/statistics.js Целия файл

345
     this.eventEmitter.removeListener(StatisticsEvents.LONG_TASKS_STATS, listener);
345
     this.eventEmitter.removeListener(StatisticsEvents.LONG_TASKS_STATS, listener);
346
 };
346
 };
347
 
347
 
348
+/**
349
+ * Updates the list of speakers for which the audio levels are to be calculated. This is needed for the jvb pc only.
350
+ *
351
+ * @param {Array<string>} speakerList The list of remote endpoint ids.
352
+ * @returns {void}
353
+ */
354
+Statistics.prototype.setSpeakerList = function(speakerList) {
355
+    for (const rtpStats of Array.from(this.rtpStatsMap.values())) {
356
+        if (!rtpStats.peerconnection.isP2P) {
357
+            rtpStats.setSpeakerList(speakerList);
358
+        }
359
+    }
360
+};
361
+
348
 Statistics.prototype.dispose = function() {
362
 Statistics.prototype.dispose = function() {
349
     try {
363
     try {
350
         // NOTE Before reading this please see the comment in stopCallStats...
364
         // NOTE Before reading this please see the comment in stopCallStats...

Loading…
Отказ
Запис