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

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,6 +5,7 @@ import { Strophe } from 'strophe.js';
5 5
 
6 6
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
7 7
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
8
+import { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';
8 9
 import Statistics from './modules/statistics/statistics';
9 10
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
10 11
 import * as MediaType from './service/RTC/MediaType';
@@ -505,6 +506,20 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function() {
505 506
                 conference.eventEmitter.emit(
506 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 523
                 if (conference.statistics && conference.myUserId() === dominant) {
509 524
                     // We are the new dominant speaker.
510 525
                     conference.statistics.sendDominantSpeakerEvent(conference.room.roomjid);

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

@@ -468,6 +468,32 @@ TraceablePeerConnection.prototype._getDesiredMediaDirection = function(
468 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 498
  * Tells whether or not this TPC instance is using Simulcast.
473 499
  * @return {boolean} <tt>true</tt> if simulcast is enabled and active or
@@ -526,16 +552,17 @@ TraceablePeerConnection.prototype._peerMutedChanged = function(
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 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 567
     audioReceivers.forEach(remote => {
541 568
         const ssrc = remote.getSynchronizationSources();

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

@@ -182,7 +182,11 @@ export default class BrowserCapabilities extends BrowserDetection {
182 182
      */
183 183
     supportsReceiverStats() {
184 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,6 +260,7 @@ export default function StatsCollector(
260 260
     // Updates stats interval
261 261
     this.audioLevelsIntervalMilis = audioLevelsInterval;
262 262
 
263
+    this.speakerList = [];
263 264
     this.statsIntervalId = null;
264 265
     this.statsIntervalMilis = statsInterval;
265 266
 
@@ -270,7 +271,15 @@ export default function StatsCollector(
270 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 285
  * Stops stats updates.
@@ -308,7 +317,7 @@ StatsCollector.prototype.start = function(startAudioLevelStats) {
308 317
         this.audioLevelsIntervalId = setInterval(
309 318
             () => {
310 319
                 if (browser.supportsReceiverStats()) {
311
-                    const audioLevels = this.peerconnection.getAudioLevels();
320
+                    const audioLevels = this.peerconnection.getAudioLevels(this.speakerList);
312 321
 
313 322
                     for (const ssrc in audioLevels) {
314 323
                         if (audioLevels.hasOwnProperty(ssrc)) {

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

@@ -1 +1,8 @@
1 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,6 +345,20 @@ Statistics.prototype.removeLongTasksStatsListener = function(listener) {
345 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 362
 Statistics.prototype.dispose = function() {
349 363
     try {
350 364
         // NOTE Before reading this please see the comment in stopCallStats...

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