Просмотр исходного кода

fix(stats): Use promise-based getStats on all browsers.

Get rid of the browser specific keys and use the standard spec-compliant fields for stats.
Get the resolution/fps for remote streams from 'inbound-rtp' stats. Use the 'track' stats for the local resolution/fps since these take the active simulcast streams into account.
dev1
Jaya Allamsetty 4 лет назад
Родитель
Сommit
ca325f5ef9
2 измененных файлов: 108 добавлений и 744 удалений
  1. 27
    22
      modules/RTC/TraceablePeerConnection.js
  2. 81
    722
      modules/statistics/RTPStatsCollector.js

+ 27
- 22
modules/RTC/TraceablePeerConnection.js Просмотреть файл

@@ -2551,7 +2551,7 @@ TraceablePeerConnection.prototype.setSenderVideoConstraint = function(frameHeigh
2551 2551
 
2552 2552
     if (this.isSimulcastOn()) {
2553 2553
         // Determine the encodings that need to stay enabled based on the new frameHeight provided.
2554
-        const encodingsEnabledState = this.tpcUtils.getLocalStreamHeightConstraints(localVideoTrack.track)
2554
+        this.encodingsEnabledState = this.tpcUtils.getLocalStreamHeightConstraints(localVideoTrack.track)
2555 2555
             .map(height => height <= newHeight);
2556 2556
 
2557 2557
         // Always keep the LD stream enabled, specifically when the LD stream's resolution is higher than of the
@@ -2562,11 +2562,11 @@ TraceablePeerConnection.prototype.setSenderVideoConstraint = function(frameHeigh
2562 2562
             .findIndex(layer => layer.scaleResolutionDownBy === 4.0);
2563 2563
 
2564 2564
         if (newHeight > 0 && ldStreamIndex !== -1) {
2565
-            encodingsEnabledState[ldStreamIndex] = true;
2565
+            this.encodingsEnabledState[ldStreamIndex] = true;
2566 2566
         }
2567 2567
         for (const encoding in parameters.encodings) {
2568 2568
             if (parameters.encodings.hasOwnProperty(encoding)) {
2569
-                parameters.encodings[encoding].active = encodingsEnabledState[encoding];
2569
+                parameters.encodings[encoding].active = this.encodingsEnabledState[encoding];
2570 2570
             }
2571 2571
         }
2572 2572
         this.tpcUtils.updateEncodingsResolution(parameters);
@@ -2940,31 +2940,36 @@ TraceablePeerConnection.prototype.addIceCandidate = function(candidate) {
2940 2940
     return this.peerconnection.addIceCandidate(candidate);
2941 2941
 };
2942 2942
 
2943
+/**
2944
+ * Returns the number of simulcast streams that are currently enabled on the peerconnection.
2945
+ *
2946
+ * @returns {number} The number of simulcast streams currently enabled or 1 when simulcast is disabled.
2947
+ */
2948
+TraceablePeerConnection.prototype.getActiveSimulcastStreams = function() {
2949
+    let activeStreams = 1;
2950
+
2951
+    if (this.isSimulcastOn() && this.encodingsEnabledState) {
2952
+        activeStreams = this.encodingsEnabledState.filter(stream => Boolean(stream))?.length;
2953
+    } else if (this.isSimulcastOn()) {
2954
+        activeStreams = SIM_LAYER_RIDS.length;
2955
+    }
2956
+
2957
+    return activeStreams;
2958
+};
2959
+
2943 2960
 /**
2944 2961
  * Obtains call-related stats from the peer connection.
2945 2962
  *
2946
- * @param {Function} callback - The function to invoke after successfully
2947
- * obtaining stats.
2948
- * @param {Function} errback - The function to invoke after failing to obtain
2949
- * stats.
2963
+ * @param {Function} callback - The function to invoke after successfully obtaining stats.
2964
+ * @param {Function} errback - The function to invoke after failing to obtain stats.
2950 2965
  * @returns {void}
2951 2966
  */
2952 2967
 TraceablePeerConnection.prototype.getStats = function(callback, errback) {
2953
-    // TODO (brian): After moving all browsers to adapter, check if adapter is
2954
-    // accounting for different getStats apis, making the browser-checking-if
2955
-    // unnecessary.
2956
-    if (browser.isWebKitBased() || browser.isFirefox() || browser.isReactNative()) {
2957
-        // uses the new Promise based getStats
2958
-        this.peerconnection.getStats()
2959
-            .then(callback)
2960
-            .catch(errback || (() => {
2961
-
2962
-                // Making sure that getStats won't fail if error callback is
2963
-                // not passed.
2964
-            }));
2965
-    } else {
2966
-        this.peerconnection.getStats(callback);
2967
-    }
2968
+    this.peerconnection.getStats()
2969
+        .then(callback)
2970
+        .catch(errback || (() => {
2971
+            // Making sure that getStats won't fail if error callback is not passed.
2972
+        }));
2968 2973
 };
2969 2974
 
2970 2975
 /**

+ 81
- 722
modules/statistics/RTPStatsCollector.js Просмотреть файл

@@ -1,4 +1,3 @@
1
-import { browsers } from '@jitsi/js-utils';
2 1
 import { getLogger } from 'jitsi-meet-logger';
3 2
 
4 3
 import * as MediaType from '../../service/RTC/MediaType';
@@ -9,74 +8,6 @@ const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
9 8
 
10 9
 const logger = getLogger(__filename);
11 10
 
12
-/**
13
- * The lib-jitsi-meet browser-agnostic names of the browser-specific keys
14
- * reported by RTCPeerConnection#getStats mapped by browser.
15
- */
16
-const KEYS_BY_BROWSER_TYPE = {};
17
-
18
-KEYS_BY_BROWSER_TYPE[browsers.FIREFOX] = {
19
-    'ssrc': 'ssrc',
20
-    'packetsReceived': 'packetsReceived',
21
-    'packetsLost': 'packetsLost',
22
-    'packetsSent': 'packetsSent',
23
-    'bytesReceived': 'bytesReceived',
24
-    'bytesSent': 'bytesSent',
25
-    'framerateMean': 'framerateMean',
26
-    'ip': 'address',
27
-    'port': 'port',
28
-    'protocol': 'protocol'
29
-};
30
-KEYS_BY_BROWSER_TYPE[browsers.CHROME] = {
31
-    'receiveBandwidth': 'googAvailableReceiveBandwidth',
32
-    'sendBandwidth': 'googAvailableSendBandwidth',
33
-    'remoteAddress': 'googRemoteAddress',
34
-    'transportType': 'googTransportType',
35
-    'localAddress': 'googLocalAddress',
36
-    'activeConnection': 'googActiveConnection',
37
-    'ssrc': 'ssrc',
38
-    'packetsReceived': 'packetsReceived',
39
-    'packetsSent': 'packetsSent',
40
-    'packetsLost': 'packetsLost',
41
-    'bytesReceived': 'bytesReceived',
42
-    'bytesSent': 'bytesSent',
43
-    'googCodecName': 'googCodecName',
44
-    'googFrameHeightReceived': 'googFrameHeightReceived',
45
-    'googFrameWidthReceived': 'googFrameWidthReceived',
46
-    'googFrameHeightSent': 'googFrameHeightSent',
47
-    'googFrameWidthSent': 'googFrameWidthSent',
48
-    'googFrameRateReceived': 'googFrameRateReceived',
49
-    'googFrameRateSent': 'googFrameRateSent',
50
-    'audioInputLevel': 'audioInputLevel',
51
-    'audioOutputLevel': 'audioOutputLevel',
52
-    'currentRoundTripTime': 'googRtt',
53
-    'remoteCandidateType': 'googRemoteCandidateType',
54
-    'localCandidateType': 'googLocalCandidateType',
55
-    'ip': 'ip',
56
-    'port': 'port',
57
-    'protocol': 'protocol'
58
-};
59
-KEYS_BY_BROWSER_TYPE[browsers.REACT_NATIVE] = {
60
-    'packetsReceived': 'packetsReceived',
61
-    'packetsSent': 'packetsSent',
62
-    'bytesReceived': 'bytesReceived',
63
-    'bytesSent': 'bytesSent',
64
-    'frameWidth': 'frameWidth',
65
-    'frameHeight': 'frameHeight',
66
-    'framesPerSecond': 'framesPerSecond',
67
-    'ip': 'ip',
68
-    'port': 'port',
69
-    'protocol': 'protocol'
70
-};
71
-KEYS_BY_BROWSER_TYPE[browsers.OPERA]
72
-    = KEYS_BY_BROWSER_TYPE[browsers.CHROME];
73
-KEYS_BY_BROWSER_TYPE[browsers.NWJS]
74
-    = KEYS_BY_BROWSER_TYPE[browsers.CHROME];
75
-KEYS_BY_BROWSER_TYPE[browsers.ELECTRON]
76
-    = KEYS_BY_BROWSER_TYPE[browsers.CHROME];
77
-KEYS_BY_BROWSER_TYPE[browsers.SAFARI]
78
-    = KEYS_BY_BROWSER_TYPE[browsers.CHROME];
79
-
80 11
 /**
81 12
  * Calculates packet lost percent using the number of lost packets and the
82 13
  * number of all packet.
@@ -200,53 +131,7 @@ function ConferenceStats() {
200 131
  * @param eventEmitter
201 132
  * @constructor
202 133
  */
203
-export default function StatsCollector(
204
-        peerconnection,
205
-        audioLevelsInterval,
206
-        statsInterval,
207
-        eventEmitter) {
208
-    // StatsCollector depends entirely on the format of the reports returned by
209
-    // RTCPeerConnection#getStats. Given that the value of
210
-    // browser#getName() is very unlikely to change at runtime, it
211
-    // makes sense to discover whether StatsCollector supports the executing
212
-    // browser as soon as possible. Otherwise, (1) getStatValue would have to
213
-    // needlessly check a "static" condition multiple times very very often and
214
-    // (2) the lack of support for the executing browser would be discovered and
215
-    // reported multiple times very very often too late in the execution in some
216
-    // totally unrelated callback.
217
-    /**
218
-     * The browser type supported by this StatsCollector. In other words, the
219
-     * type of the browser which initialized this StatsCollector
220
-     * instance.
221
-     * @private
222
-     */
223
-    this._browserType = browser.getName();
224
-    const keys = KEYS_BY_BROWSER_TYPE[this._browserType];
225
-
226
-    if (!keys) {
227
-        throw new Error(`The browser type '${this._browserType}' isn't supported!`);
228
-    }
229
-
230
-    /**
231
-     * Whether to use the Promise-based getStats API or not.
232
-     * @type {boolean}
233
-     */
234
-    this._usesPromiseGetStats
235
-        = browser.isWebKitBased() || browser.isFirefox() || browser.isReactNative();
236
-
237
-    /**
238
-     * The function which is to be used to retrieve the value associated in a
239
-     * report returned by RTCPeerConnection#getStats with a lib-jitsi-meet
240
-     * browser-agnostic name/key.
241
-     *
242
-     * @function
243
-     * @private
244
-     */
245
-    this._getStatValue
246
-        = this._usesPromiseGetStats
247
-            ? this._defineNewGetStatValueMethod(keys)
248
-            : this._defineGetStatValueMethod(keys);
249
-
134
+export default function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter) {
250 135
     this.peerconnection = peerconnection;
251 136
     this.baselineAudioLevelsReport = null;
252 137
     this.currentAudioLevelsReport = null;
@@ -346,14 +231,8 @@ StatsCollector.prototype.start = function(startAudioLevelStats) {
346 231
                                 results = report.result();
347 232
                             }
348 233
                             this.currentAudioLevelsReport = results;
349
-                            if (this._usesPromiseGetStats) {
350
-                                this.processNewAudioLevelReport();
351
-                            } else {
352
-                                this.processAudioLevelReport();
353
-                            }
354
-
355
-                            this.baselineAudioLevelsReport
356
-                                = this.currentAudioLevelsReport;
234
+                            this.processAudioLevelReport();
235
+                            this.baselineAudioLevelsReport = this.currentAudioLevelsReport;
357 236
                         },
358 237
                         error => this.errorCallback(error)
359 238
                     );
@@ -380,16 +259,11 @@ StatsCollector.prototype.start = function(startAudioLevelStats) {
380 259
 
381 260
                 this.currentStatsReport = results;
382 261
                 try {
383
-                    if (this._usesPromiseGetStats) {
384
-                        this.processNewStatsReport();
385
-                    } else {
386
-                        this.processStatsReport();
387
-                    }
388
-                } catch (e) {
389
-                    GlobalOnErrorHandler.callErrorHandler(e);
390
-                    logger.error(`Unsupported key:${e}`, e);
262
+                    this.processStatsReport();
263
+                } catch (error) {
264
+                    GlobalOnErrorHandler.callErrorHandler(error);
265
+                    logger.error('Processing of RTP stats failed:', error);
391 266
                 }
392
-
393 267
                 this.previousStatsReport = this.currentStatsReport;
394 268
             },
395 269
             error => this.errorCallback(error)
@@ -400,341 +274,6 @@ StatsCollector.prototype.start = function(startAudioLevelStats) {
400 274
     this.statsIntervalId = setInterval(processStats, this.statsIntervalMilis);
401 275
 };
402 276
 
403
-/**
404
- * Defines a function which (1) is to be used as a StatsCollector method and (2)
405
- * gets the value from a specific report returned by RTCPeerConnection#getStats
406
- * associated with a lib-jitsi-meet browser-agnostic name.
407
- *
408
- * @param {Object.<string,string>} keys the map of LibJitsi browser-agnostic
409
- * names to RTCPeerConnection#getStats browser-specific keys
410
- */
411
-StatsCollector.prototype._defineGetStatValueMethod = function(keys) {
412
-    // Define the function which converts a lib-jitsi-meet browser-asnostic name
413
-    // to a browser-specific key of a report returned by
414
-    // RTCPeerConnection#getStats.
415
-    const keyFromName = function(name) {
416
-        const key = keys[name];
417
-
418
-        if (key) {
419
-            return key;
420
-        }
421
-
422
-        throw new Error(`The property '${name}' isn't supported!`);
423
-    };
424
-
425
-    // Define the function which retrieves the value from a specific report
426
-    // returned by RTCPeerConnection#getStats associated with a given
427
-    // browser-specific key.
428
-    let itemStatByKey;
429
-
430
-    switch (this._browserType) {
431
-    case browsers.CHROME:
432
-    case browsers.OPERA:
433
-    case browsers.NWJS:
434
-    case browsers.ELECTRON:
435
-        // TODO What about other types of browser which are based on Chrome such
436
-        // as NW.js? Every time we want to support a new type browser we have to
437
-        // go and add more conditions (here and in multiple other places).
438
-        // Cannot we do a feature detection instead of a browser type check? For
439
-        // example, if item has a stat property of type function, then it's very
440
-        // likely that whoever defined it wanted you to call it in order to
441
-        // retrieve the value associated with a specific key.
442
-        itemStatByKey = (item, key) => item.stat(key);
443
-        break;
444
-    default:
445
-        itemStatByKey = (item, key) => item[key];
446
-    }
447
-
448
-    // Compose the 2 functions defined above to get a function which retrieves
449
-    // the value from a specific report returned by RTCPeerConnection#getStats
450
-    // associated with a specific lib-jitsi-meet browser-agnostic name.
451
-    return (item, name) => itemStatByKey(item, keyFromName(name));
452
-};
453
-
454
-/**
455
- * Obtains a stat value from given stat and converts it to a non-negative
456
- * number. If the value is either invalid or negative then 0 will be returned.
457
- * @param report
458
- * @param {string} name
459
- * @return {number}
460
- * @private
461
- */
462
-StatsCollector.prototype.getNonNegativeStat = function(report, name) {
463
-    let value = this._getStatValue(report, name);
464
-
465
-    if (typeof value !== 'number') {
466
-        value = Number(value);
467
-    }
468
-
469
-    if (isNaN(value)) {
470
-        return 0;
471
-    }
472
-
473
-    return Math.max(0, value);
474
-};
475
-
476
-/* eslint-disable no-continue */
477
-
478
-/**
479
- * Stats processing logic.
480
- */
481
-StatsCollector.prototype.processStatsReport = function() {
482
-    if (!this.previousStatsReport) {
483
-        return;
484
-    }
485
-
486
-    const getStatValue = this._getStatValue;
487
-    const byteSentStats = {};
488
-
489
-    for (const idx in this.currentStatsReport) {
490
-        if (!this.currentStatsReport.hasOwnProperty(idx)) {
491
-            continue;
492
-        }
493
-        const now = this.currentStatsReport[idx];
494
-
495
-        // The browser API may return "undefined" values in the array
496
-        if (!now) {
497
-            continue;
498
-        }
499
-
500
-        try {
501
-            const receiveBandwidth = getStatValue(now, 'receiveBandwidth');
502
-            const sendBandwidth = getStatValue(now, 'sendBandwidth');
503
-
504
-            if (receiveBandwidth || sendBandwidth) {
505
-                this.conferenceStats.bandwidth = {
506
-                    'download': Math.round(receiveBandwidth / 1000),
507
-                    'upload': Math.round(sendBandwidth / 1000)
508
-                };
509
-            }
510
-        } catch (e) { /* not supported*/ }
511
-
512
-        if (now.type === 'googCandidatePair') {
513
-            let active, ip, localCandidateType, localip,
514
-                remoteCandidateType, rtt, type;
515
-
516
-            try {
517
-                active = getStatValue(now, 'activeConnection');
518
-                if (!active) {
519
-                    continue;
520
-                }
521
-
522
-                ip = getStatValue(now, 'remoteAddress');
523
-                type = getStatValue(now, 'transportType');
524
-                localip = getStatValue(now, 'localAddress');
525
-                localCandidateType = getStatValue(now, 'localCandidateType');
526
-                remoteCandidateType = getStatValue(now, 'remoteCandidateType');
527
-                rtt = this.getNonNegativeStat(now, 'currentRoundTripTime');
528
-            } catch (e) { /* not supported*/ }
529
-            if (!ip || !type || !localip || active !== 'true') {
530
-                continue;
531
-            }
532
-
533
-            // Save the address unless it has been saved already.
534
-            const conferenceStatsTransport = this.conferenceStats.transport;
535
-
536
-            if (!conferenceStatsTransport.some(
537
-                    t =>
538
-                        t.ip === ip
539
-                            && t.type === type
540
-                            && t.localip === localip)) {
541
-                conferenceStatsTransport.push({
542
-                    ip,
543
-                    type,
544
-                    localip,
545
-                    p2p: this.peerconnection.isP2P,
546
-                    localCandidateType,
547
-                    remoteCandidateType,
548
-                    rtt
549
-                });
550
-            }
551
-            continue;
552
-        }
553
-
554
-        if (now.type === 'candidatepair') {
555
-            // we need succeeded and selected pairs only
556
-            if (now.state !== 'succeeded' || !now.selected) {
557
-                continue;
558
-            }
559
-
560
-            const local = this.currentStatsReport[now.localCandidateId];
561
-            const remote = this.currentStatsReport[now.remoteCandidateId];
562
-
563
-            this.conferenceStats.transport.push({
564
-                ip: `${remote.ipAddress}:${remote.portNumber}`,
565
-                type: local.transport,
566
-                localip: `${local.ipAddress}:${local.portNumber}`,
567
-                p2p: this.peerconnection.isP2P,
568
-                localCandidateType: local.candidateType,
569
-                remoteCandidateType: remote.candidateType
570
-            });
571
-        }
572
-
573
-        if (now.type !== 'ssrc' && now.type !== 'outboundrtp'
574
-            && now.type !== 'inboundrtp' && now.type !== 'track') {
575
-            continue;
576
-        }
577
-
578
-        const before = this.previousStatsReport[idx];
579
-        let ssrc = this.getNonNegativeStat(now, 'ssrc');
580
-
581
-        // If type="track", take the first SSRC from ssrcIds.
582
-        if (now.type === 'track' && Array.isArray(now.ssrcIds)) {
583
-            ssrc = Number(now.ssrcIds[0]);
584
-        }
585
-
586
-        if (!before || !ssrc) {
587
-            continue;
588
-        }
589
-
590
-        // isRemote is available only in FF and is ignored in case of chrome
591
-        // according to the spec
592
-        // https://www.w3.org/TR/webrtc-stats/#dom-rtcrtpstreamstats-isremote
593
-        // when isRemote is true indicates that the measurements were done at
594
-        // the remote endpoint and reported in an RTCP RR/XR.
595
-        // Fixes a problem where we are calculating local stats wrong adding
596
-        // the sent bytes to the local download bitrate.
597
-        // In new W3 stats spec, type="track" has a remoteSource boolean
598
-        // property.
599
-        if (now.isRemote === true || now.remoteSource === true) {
600
-            continue;
601
-        }
602
-
603
-        let ssrcStats = this.ssrc2stats.get(ssrc);
604
-
605
-        if (!ssrcStats) {
606
-            ssrcStats = new SsrcStats();
607
-            this.ssrc2stats.set(ssrc, ssrcStats);
608
-        }
609
-
610
-        let isDownloadStream = true;
611
-        let key = 'packetsReceived';
612
-        let packetsNow = getStatValue(now, key);
613
-
614
-        if (typeof packetsNow === 'undefined'
615
-            || packetsNow === null || packetsNow === '') {
616
-            isDownloadStream = false;
617
-            key = 'packetsSent';
618
-            packetsNow = getStatValue(now, key);
619
-            if (typeof packetsNow === 'undefined' || packetsNow === null) {
620
-                logger.warn('No packetsReceived nor packetsSent stat found');
621
-            }
622
-        }
623
-        if (!packetsNow || packetsNow < 0) {
624
-            packetsNow = 0;
625
-        }
626
-
627
-        const packetsBefore = this.getNonNegativeStat(before, key);
628
-        const packetsDiff = Math.max(0, packetsNow - packetsBefore);
629
-
630
-        const packetsLostNow
631
-            = this.getNonNegativeStat(now, 'packetsLost');
632
-        const packetsLostBefore
633
-            = this.getNonNegativeStat(before, 'packetsLost');
634
-        const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
635
-
636
-        ssrcStats.setLoss({
637
-            packetsTotal: packetsDiff + packetsLostDiff,
638
-            packetsLost: packetsLostDiff,
639
-            isDownloadStream
640
-        });
641
-
642
-        const bytesReceivedNow
643
-            = this.getNonNegativeStat(now, 'bytesReceived');
644
-        const bytesReceivedBefore
645
-            = this.getNonNegativeStat(before, 'bytesReceived');
646
-        const bytesReceived
647
-            = Math.max(0, bytesReceivedNow - bytesReceivedBefore);
648
-
649
-        let bytesSent = 0;
650
-
651
-        // TODO: clean this mess up!
652
-        let nowBytesTransmitted = getStatValue(now, 'bytesSent');
653
-
654
-        if (typeof nowBytesTransmitted === 'number'
655
-            || typeof nowBytesTransmitted === 'string') {
656
-            nowBytesTransmitted = Number(nowBytesTransmitted);
657
-            if (!isNaN(nowBytesTransmitted)) {
658
-                byteSentStats[ssrc] = nowBytesTransmitted;
659
-                if (nowBytesTransmitted > 0) {
660
-                    bytesSent = nowBytesTransmitted
661
-                        - getStatValue(before, 'bytesSent');
662
-                }
663
-            }
664
-        }
665
-        bytesSent = Math.max(0, bytesSent);
666
-
667
-        const timeMs = now.timestamp - before.timestamp;
668
-        let bitrateReceivedKbps = 0, bitrateSentKbps = 0;
669
-
670
-        if (timeMs > 0) {
671
-            // TODO is there any reason to round here?
672
-            bitrateReceivedKbps = Math.round((bytesReceived * 8) / timeMs);
673
-            bitrateSentKbps = Math.round((bytesSent * 8) / timeMs);
674
-        }
675
-
676
-        ssrcStats.addBitrate({
677
-            'download': bitrateReceivedKbps,
678
-            'upload': bitrateSentKbps
679
-        });
680
-
681
-        const resolution = {
682
-            height: null,
683
-            width: null
684
-        };
685
-
686
-        try {
687
-            let height, width;
688
-
689
-            if ((height = getStatValue(now, 'googFrameHeightReceived'))
690
-                && (width = getStatValue(now, 'googFrameWidthReceived'))) {
691
-                resolution.height = height;
692
-                resolution.width = width;
693
-            } else if ((height = getStatValue(now, 'googFrameHeightSent'))
694
-                && (width = getStatValue(now, 'googFrameWidthSent'))) {
695
-                resolution.height = height;
696
-                resolution.width = width;
697
-            }
698
-        } catch (e) { /* not supported*/ }
699
-
700
-        // Tries to get frame rate
701
-        let frameRate;
702
-
703
-        try {
704
-            frameRate = getStatValue(now, 'googFrameRateReceived')
705
-                || getStatValue(now, 'googFrameRateSent') || 0;
706
-        } catch (e) {
707
-            // if it fails with previous properties(chrome),
708
-            // let's try with another one (FF)
709
-            try {
710
-                frameRate = this.getNonNegativeStat(now, 'framerateMean');
711
-            } catch (err) { /* not supported*/ }
712
-        }
713
-        ssrcStats.setFramerate(Math.round(frameRate || 0));
714
-
715
-        if (resolution.height && resolution.width) {
716
-            ssrcStats.setResolution(resolution);
717
-        } else {
718
-            ssrcStats.setResolution(null);
719
-        }
720
-
721
-        let codec;
722
-
723
-        // Try to get the codec for later reporting.
724
-        try {
725
-            codec = getStatValue(now, 'googCodecName') || '';
726
-        } catch (e) { /* not supported*/ }
727
-
728
-        ssrcStats.setCodec(codec);
729
-    }
730
-
731
-
732
-    this.eventEmitter.emit(
733
-        StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);
734
-
735
-    this._processAndEmitReport();
736
-};
737
-
738 277
 /**
739 278
  *
740 279
  */
@@ -891,136 +430,6 @@ StatsCollector.prototype._processAndEmitReport = function() {
891 430
     this.conferenceStats.transport = [];
892 431
 };
893 432
 
894
-/**
895
- * Stats processing logic.
896
- */
897
-StatsCollector.prototype.processAudioLevelReport = function() {
898
-    if (!this.baselineAudioLevelsReport) {
899
-        return;
900
-    }
901
-
902
-    const getStatValue = this._getStatValue;
903
-
904
-    for (const idx in this.currentAudioLevelsReport) {
905
-        if (!this.currentAudioLevelsReport.hasOwnProperty(idx)) {
906
-            continue;
907
-        }
908
-
909
-        const now = this.currentAudioLevelsReport[idx];
910
-
911
-        if (now.type !== 'ssrc' && now.type !== 'track') {
912
-            continue;
913
-        }
914
-
915
-        const before = this.baselineAudioLevelsReport[idx];
916
-        let ssrc = this.getNonNegativeStat(now, 'ssrc');
917
-
918
-        if (!ssrc && Array.isArray(now.ssrcIds)) {
919
-            ssrc = Number(now.ssrcIds[0]);
920
-        }
921
-
922
-        if (!before) {
923
-            logger.warn(`${ssrc} not enough data`);
924
-            continue;
925
-        }
926
-
927
-        if (!ssrc) {
928
-            if ((Date.now() - now.timestamp) < 3000) {
929
-                logger.warn('No ssrc: ');
930
-            }
931
-            continue;
932
-        }
933
-
934
-        // Audio level
935
-        let audioLevel;
936
-
937
-        try {
938
-            audioLevel
939
-                = getStatValue(now, 'audioInputLevel')
940
-                    || getStatValue(now, 'audioOutputLevel');
941
-        } catch (e) { /* not supported*/
942
-            logger.warn('Audio Levels are not available in the statistics.');
943
-            clearInterval(this.audioLevelsIntervalId);
944
-
945
-            return;
946
-        }
947
-
948
-        if (audioLevel) {
949
-            let isLocal;
950
-
951
-            // If type="ssrc" (legacy) check whether they are received packets.
952
-            if (now.type === 'ssrc') {
953
-                isLocal = !getStatValue(now, 'packetsReceived');
954
-
955
-            // If type="track", check remoteSource boolean property.
956
-            } else {
957
-                isLocal = !now.remoteSource;
958
-            }
959
-
960
-            // According to the W3C WebRTC Stats spec, audioLevel should be in
961
-            // 0..1 range (0 == silence). However browsers don't behave that
962
-            // way so we must convert it to 0..1.
963
-            // TODO: Can't find specs about what this value really is, but it
964
-            // seems to vary between 0 and around 32k.
965
-            audioLevel = audioLevel / 32767;
966
-
967
-            if (!(ssrc in this.audioLevelReportHistory)) {
968
-                this.audioLevelReportHistory[ssrc] = {
969
-                    isLocal,
970
-                    data: []
971
-                };
972
-            }
973
-            this.audioLevelReportHistory[ssrc].data.push(audioLevel);
974
-
975
-            this.eventEmitter.emit(
976
-                StatisticsEvents.AUDIO_LEVEL,
977
-                this.peerconnection,
978
-                ssrc,
979
-                audioLevel,
980
-                isLocal);
981
-        }
982
-    }
983
-};
984
-
985
-/* eslint-enable no-continue */
986
-
987
-/**
988
- * New promised based getStats report processing.
989
- * Tested with chrome, firefox and safari. Not switching it on for chrome as
990
- * frameRate stat is missing and calculating it using framesSent,
991
- * gives values double the values seen in webrtc-internals.
992
- * https://w3c.github.io/webrtc-stats/
993
- */
994
-
995
-/**
996
- * Defines a function which (1) is to be used as a StatsCollector method and (2)
997
- * gets the value from a specific report returned by RTCPeerConnection#getStats
998
- * associated with a lib-jitsi-meet browser-agnostic name in case of using
999
- * Promised based getStats.
1000
- *
1001
- * @param {Object.<string,string>} keys the map of LibJitsi browser-agnostic
1002
- * names to RTCPeerConnection#getStats browser-specific keys
1003
- */
1004
-StatsCollector.prototype._defineNewGetStatValueMethod = function(keys) {
1005
-    // Define the function which converts a lib-jitsi-meet browser-asnostic name
1006
-    // to a browser-specific key of a report returned by
1007
-    // RTCPeerConnection#getStats.
1008
-    const keyFromName = function(name) {
1009
-        const key = keys[name];
1010
-
1011
-        if (key) {
1012
-            return key;
1013
-        }
1014
-
1015
-        throw new Error(`The property '${name}' isn't supported!`);
1016
-    };
1017
-
1018
-    // Compose the 2 functions defined above to get a function which retrieves
1019
-    // the value from a specific report returned by RTCPeerConnection#getStats
1020
-    // associated with a specific lib-jitsi-meet browser-agnostic name.
1021
-    return (item, name) => item[keyFromName(name)];
1022
-};
1023
-
1024 433
 /**
1025 434
  * Converts the value to a non-negative number.
1026 435
  * If the value is either invalid or negative then 0 will be returned.
@@ -1069,24 +478,17 @@ StatsCollector.prototype._calculateBitrate = function(now, before, fieldName) {
1069 478
 };
1070 479
 
1071 480
 /**
1072
- * Stats processing new getStats logic.
481
+ * Stats processing for spec-compliant RTCPeerConnection#getStats.
1073 482
  */
1074
-StatsCollector.prototype.processNewStatsReport = function() {
483
+StatsCollector.prototype.processStatsReport = function() {
1075 484
     if (!this.previousStatsReport) {
1076 485
         return;
1077 486
     }
1078
-
1079
-    const getStatValue = this._getStatValue;
1080 487
     const byteSentStats = {};
1081 488
 
1082 489
     this.currentStatsReport.forEach(now => {
1083
-
1084
-        // RTCIceCandidatePairStats
1085
-        // https://w3c.github.io/webrtc-stats/#candidatepair-dict*
1086
-        if (now.type === 'candidate-pair'
1087
-            && now.nominated
1088
-            && now.state === 'succeeded') {
1089
-
490
+        // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict*
491
+        if (now.type === 'candidate-pair' && now.nominated && now.state === 'succeeded') {
1090 492
             const availableIncomingBitrate = now.availableIncomingBitrate;
1091 493
             const availableOutgoingBitrate = now.availableOutgoingBitrate;
1092 494
 
@@ -1097,37 +499,36 @@ StatsCollector.prototype.processNewStatsReport = function() {
1097 499
                 };
1098 500
             }
1099 501
 
1100
-            const remoteUsedCandidate
1101
-                = this.currentStatsReport.get(now.remoteCandidateId);
1102
-            const localUsedCandidate
1103
-                = this.currentStatsReport.get(now.localCandidateId);
502
+            const remoteUsedCandidate = this.currentStatsReport.get(now.remoteCandidateId);
503
+            const localUsedCandidate = this.currentStatsReport.get(now.localCandidateId);
1104 504
 
1105 505
             // RTCIceCandidateStats
1106 506
             // https://w3c.github.io/webrtc-stats/#icecandidate-dict*
1107
-            // safari currently does not provide ice candidates in stats
1108 507
             if (remoteUsedCandidate && localUsedCandidate) {
1109
-                const remoteIpAddress = getStatValue(remoteUsedCandidate, 'ip');
1110
-                const remotePort = getStatValue(remoteUsedCandidate, 'port');
508
+                const remoteIpAddress = browser.isChromiumBased()
509
+                    ? remoteUsedCandidate.ip
510
+                    : remoteUsedCandidate.address;
511
+                const remotePort = remoteUsedCandidate.port;
1111 512
                 const ip = `${remoteIpAddress}:${remotePort}`;
1112 513
 
1113
-                const localIpAddress = getStatValue(localUsedCandidate, 'ip');
1114
-                const localPort = getStatValue(localUsedCandidate, 'port');
1115
-
1116
-                const localIp = `${localIpAddress}:${localPort}`;
1117
-                const type = getStatValue(remoteUsedCandidate, 'protocol');
514
+                const localIpAddress = browser.isChromiumBased()
515
+                    ? localUsedCandidate.ip
516
+                    : localUsedCandidate.address;
517
+                const localPort = localUsedCandidate.port;
518
+                const localip = `${localIpAddress}:${localPort}`;
519
+                const type = remoteUsedCandidate.protocol;
1118 520
 
1119 521
                 // Save the address unless it has been saved already.
1120 522
                 const conferenceStatsTransport = this.conferenceStats.transport;
1121 523
 
1122
-                if (!conferenceStatsTransport.some(
1123
-                        t =>
1124
-                            t.ip === ip
1125
-                            && t.type === type
1126
-                            && t.localip === localIp)) {
524
+                if (!conferenceStatsTransport.some(t =>
525
+                    t.ip === ip
526
+                    && t.type === type
527
+                    && t.localip === localip)) {
1127 528
                     conferenceStatsTransport.push({
1128 529
                         ip,
1129 530
                         type,
1130
-                        localIp,
531
+                        localip,
1131 532
                         p2p: this.peerconnection.isP2P,
1132 533
                         localCandidateType: localUsedCandidate.candidateType,
1133 534
                         remoteCandidateType: remoteUsedCandidate.candidateType,
@@ -1173,12 +574,9 @@ StatsCollector.prototype.processNewStatsReport = function() {
1173 574
             const packetsBefore = this.getNonNegativeValue(before[key]);
1174 575
             const packetsDiff = Math.max(0, packetsNow - packetsBefore);
1175 576
 
1176
-            const packetsLostNow
1177
-                = this.getNonNegativeValue(now.packetsLost);
1178
-            const packetsLostBefore
1179
-                = this.getNonNegativeValue(before.packetsLost);
1180
-            const packetsLostDiff
1181
-                = Math.max(0, packetsLostNow - packetsLostBefore);
577
+            const packetsLostNow = this.getNonNegativeValue(now.packetsLost);
578
+            const packetsLostBefore = this.getNonNegativeValue(before.packetsLost);
579
+            const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
1182 580
 
1183 581
             ssrcStats.setLoss({
1184 582
                 packetsTotal: packetsDiff + packetsLostDiff,
@@ -1186,71 +584,36 @@ StatsCollector.prototype.processNewStatsReport = function() {
1186 584
                 isDownloadStream
1187 585
             });
1188 586
 
1189
-            const resolution = {
1190
-                height: null,
1191
-                width: null
1192
-            };
1193
-
1194
-            try {
1195
-                resolution.height = getStatValue(now, 'frameHeight');
1196
-                resolution.width = getStatValue(now, 'frameWidth');
1197
-            } catch (e) { /* not supported*/ }
1198
-
1199
-            // Tries to get frame rate
1200
-            let frameRate;
1201
-
1202
-            try {
1203
-                frameRate = getStatValue(now, 'framesPerSecond');
1204
-            } catch (err) { /* not supported*/ }
1205
-
1206
-            ssrcStats.setFramerate(Math.round(frameRate || 0));
1207
-
1208
-            if (resolution.height && resolution.width) {
1209
-                ssrcStats.setResolution(resolution);
1210
-            } else {
1211
-                ssrcStats.setResolution(null);
1212
-            }
1213
-
587
+            // Get the resolution and framerate for only remote video sources here. For the local video sources,
588
+            // 'track' stats will be used since they have the updated resolution based on the simulcast streams
589
+            // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be
590
+            // more calculations needed to determine what is the highest resolution stream sent by the client if the
591
+            // 'outbound-rtp' stats are used.
1214 592
             if (now.type === 'inbound-rtp') {
593
+                const resolution = {
594
+                    height: now.frameHeight,
595
+                    width: now.frameWidth
596
+                };
597
+                const frameRate = now.framesPerSecond;
598
+
599
+                if (resolution.height && resolution.width) {
600
+                    ssrcStats.setResolution(resolution);
601
+                }
602
+                ssrcStats.setFramerate(Math.round(frameRate || 0));
1215 603
 
1216 604
                 ssrcStats.addBitrate({
1217
-                    'download': this._calculateBitrate(
1218
-                                    now, before, 'bytesReceived'),
605
+                    'download': this._calculateBitrate(now, before, 'bytesReceived'),
1219 606
                     'upload': 0
1220 607
                 });
1221
-
1222
-                // RTCInboundRtpStreamStats
1223
-                // https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict*
1224
-                // TODO: can we use framesDecoded for frame rate, available
1225
-                // in chrome
1226 608
             } else {
1227 609
                 byteSentStats[ssrc] = this.getNonNegativeValue(now.bytesSent);
1228 610
                 ssrcStats.addBitrate({
1229 611
                     'download': 0,
1230
-                    'upload': this._calculateBitrate(
1231
-                                now, before, 'bytesSent')
612
+                    'upload': this._calculateBitrate(now, before, 'bytesSent')
1232 613
                 });
1233
-
1234
-                // RTCOutboundRtpStreamStats
1235
-                // https://w3c.github.io/webrtc-stats/#outboundrtpstats-dict*
1236
-                // TODO: can we use framesEncoded for frame rate, available
1237
-                // in chrome
1238
-            }
1239
-
1240
-            // FF has framerateMean out of spec
1241
-            const framerateMean = now.framerateMean;
1242
-
1243
-            if (framerateMean) {
1244
-                ssrcStats.setFramerate(Math.round(framerateMean || 0));
1245 614
             }
1246 615
 
1247
-
1248
-            let codec;
1249
-
1250
-            // Try to get the codec for later reporting.
1251
-            try {
1252
-                codec = this.currentStatsReport.get(now.codecId);
1253
-            } catch (e) { /* not supported*/ }
616
+            const codec = this.currentStatsReport.get(now.codecId);
1254 617
 
1255 618
             if (codec) {
1256 619
                 /**
@@ -1263,31 +626,47 @@ StatsCollector.prototype.processNewStatsReport = function() {
1263 626
                 codecShortType && ssrcStats.setCodec(codecShortType);
1264 627
             }
1265 628
 
1266
-        // track for resolution
1267
-        // RTCVideoHandlerStats
1268
-        // https://w3c.github.io/webrtc-stats/#vststats-dict*
1269
-        // RTCMediaHandlerStats
1270
-        // https://w3c.github.io/webrtc-stats/#mststats-dict*
1271
-        } else if (now.type === 'track') {
1272
-
629
+        // Use track stats for resolution and framerate of the local video source.
630
+        // RTCVideoHandlerStats - https://w3c.github.io/webrtc-stats/#vststats-dict*
631
+        // RTCMediaHandlerStats - https://w3c.github.io/webrtc-stats/#mststats-dict*
632
+        } else if (now.type === 'track' && now.kind === MediaType.VIDEO && !now.remoteSource) {
1273 633
             const resolution = {
1274 634
                 height: now.frameHeight,
1275 635
                 width: now.frameWidth
1276 636
             };
637
+            const localVideoTracks = this.peerconnection.getLocalTracks(MediaType.VIDEO);
638
+
639
+            if (!localVideoTracks?.length) {
640
+                return;
641
+            }
1277 642
 
1278
-            // Tries to get frame rate
643
+            const ssrc = this.peerconnection.getLocalSSRC(localVideoTracks[0]);
644
+
645
+            if (!ssrc) {
646
+                return;
647
+            }
648
+            let ssrcStats = this.ssrc2stats.get(ssrc);
649
+
650
+            if (!ssrcStats) {
651
+                ssrcStats = new SsrcStats();
652
+                this.ssrc2stats.set(ssrc, ssrcStats);
653
+            }
654
+            if (resolution.height && resolution.width) {
655
+                ssrcStats.setResolution(resolution);
656
+            }
657
+
658
+            // Calculate the frame rate. 'framesSent' is the total aggregate value for all the simulcast streams.
659
+            // Therefore, it needs to be divided by the total number of active simulcast streams.
1279 660
             let frameRate = now.framesPerSecond;
1280 661
 
1281 662
             if (!frameRate) {
1282
-                // we need to calculate it
1283 663
                 const before = this.previousStatsReport.get(now.id);
1284 664
 
1285 665
                 if (before) {
1286 666
                     const timeMs = now.timestamp - before.timestamp;
1287 667
 
1288 668
                     if (timeMs > 0 && now.framesSent) {
1289
-                        const numberOfFramesSinceBefore
1290
-                            = now.framesSent - before.framesSent;
669
+                        const numberOfFramesSinceBefore = now.framesSent - before.framesSent;
1291 670
 
1292 671
                         frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;
1293 672
                     }
@@ -1298,38 +677,21 @@ StatsCollector.prototype.processNewStatsReport = function() {
1298 677
                 }
1299 678
             }
1300 679
 
1301
-            const trackIdentifier = now.trackIdentifier;
1302
-            const ssrc = this.peerconnection.getSsrcByTrackId(trackIdentifier);
680
+            // Get the number of simulcast streams currently enabled from TPC.
681
+            const numberOfActiveStreams = this.peerconnection.getActiveSimulcastStreams();
1303 682
 
1304
-            if (!ssrc) {
1305
-                return;
1306
-            }
1307
-            let ssrcStats = this.ssrc2stats.get(ssrc);
1308
-
1309
-            if (!ssrcStats) {
1310
-                ssrcStats = new SsrcStats();
1311
-                this.ssrc2stats.set(ssrc, ssrcStats);
1312
-            }
1313
-            ssrcStats.setFramerate(Math.round(frameRate || 0));
1314
-
1315
-            if (resolution.height && resolution.width) {
1316
-                ssrcStats.setResolution(resolution);
1317
-            } else {
1318
-                ssrcStats.setResolution(null);
1319
-            }
683
+            ssrcStats.setFramerate(Math.round((frameRate / numberOfActiveStreams) || 0));
1320 684
         }
1321 685
     });
1322 686
 
1323
-    this.eventEmitter.emit(
1324
-        StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);
1325
-
687
+    this.eventEmitter.emit(StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);
1326 688
     this._processAndEmitReport();
1327 689
 };
1328 690
 
1329 691
 /**
1330 692
  * Stats processing logic.
1331 693
  */
1332
-StatsCollector.prototype.processNewAudioLevelReport = function() {
694
+StatsCollector.prototype.processAudioLevelReport = function() {
1333 695
     if (!this.baselineAudioLevelsReport) {
1334 696
         return;
1335 697
     }
@@ -1364,6 +726,3 @@ StatsCollector.prototype.processNewAudioLevelReport = function() {
1364 726
     });
1365 727
 };
1366 728
 
1367
-/**
1368
- * End new promised based getStats processing methods.
1369
- */

Загрузка…
Отмена
Сохранить