@webex/plugin-meetings 3.0.0-beta.75 → 3.0.0-beta.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/meeting/index.js +4 -1
- package/dist/meeting/index.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +16 -0
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/statsAnalyzer/index.js +32 -23
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/types/multistream/receiveSlotManager.d.ts +7 -0
- package/dist/types/statsAnalyzer/index.d.ts +6 -1
- package/package.json +18 -18
- package/src/meeting/index.ts +6 -2
- package/src/multistream/receiveSlotManager.ts +12 -0
- package/src/statsAnalyzer/index.ts +54 -23
- package/test/unit/spec/multistream/receiveSlotManager.ts +11 -3
- package/test/unit/spec/stats-analyzer/index.js +7 -2
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getVideoSenderMqa,
|
|
29
29
|
getVideoReceiverMqa,
|
|
30
30
|
} from './mqaUtil';
|
|
31
|
+
import {ReceiveSlot} from '../multistream/receiveSlot';
|
|
31
32
|
|
|
32
33
|
export const EVENTS = {
|
|
33
34
|
MEDIA_QUALITY: 'MEDIA_QUALITY',
|
|
@@ -53,6 +54,8 @@ const emptyReceiver = {
|
|
|
53
54
|
meanRoundTripTime: [],
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
type ReceiveSlotCallback = (csi: number) => ReceiveSlot | undefined;
|
|
58
|
+
|
|
56
59
|
/**
|
|
57
60
|
* Stats Analyzer class that will emit events based on detected quality
|
|
58
61
|
*
|
|
@@ -74,17 +77,20 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
74
77
|
statsInterval: NodeJS.Timeout;
|
|
75
78
|
statsResults: any;
|
|
76
79
|
statsStarted: any;
|
|
80
|
+
receiveSlotCallback: ReceiveSlotCallback;
|
|
77
81
|
|
|
78
82
|
/**
|
|
79
83
|
* Creates a new instance of StatsAnalyzer
|
|
80
84
|
* @constructor
|
|
81
85
|
* @public
|
|
82
86
|
* @param {Object} config SDK Configuration Object
|
|
87
|
+
* @param {Function} receiveSlotCallback Callback used to access receive slots.
|
|
83
88
|
* @param {Object} networkQualityMonitor class for assessing network characteristics (jitter, packetLoss, latency)
|
|
84
89
|
* @param {Object} statsResults Default properties for stats
|
|
85
90
|
*/
|
|
86
91
|
constructor(
|
|
87
92
|
config: any,
|
|
93
|
+
receiveSlotCallback: ReceiveSlotCallback = () => undefined,
|
|
88
94
|
networkQualityMonitor: object = {},
|
|
89
95
|
statsResults: object = defaultStats
|
|
90
96
|
) {
|
|
@@ -98,6 +104,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
98
104
|
this.mqaSentCount = -1;
|
|
99
105
|
this.lastMqaDataSent = {};
|
|
100
106
|
this.lastEmittedStartStopEvent = {};
|
|
107
|
+
this.receiveSlotCallback = receiveSlotCallback;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
/**
|
|
@@ -523,7 +530,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
523
530
|
currentStats.totalPacketsSent === 0
|
|
524
531
|
) {
|
|
525
532
|
LoggerProxy.logger.info(
|
|
526
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent
|
|
533
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent`,
|
|
534
|
+
currentStats.totalPacketsSent
|
|
527
535
|
);
|
|
528
536
|
} else {
|
|
529
537
|
if (
|
|
@@ -531,7 +539,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
531
539
|
currentStats.totalAudioEnergy === 0
|
|
532
540
|
) {
|
|
533
541
|
LoggerProxy.logger.info(
|
|
534
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present
|
|
542
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present`,
|
|
543
|
+
currentStats.totalAudioEnergy
|
|
535
544
|
);
|
|
536
545
|
}
|
|
537
546
|
|
|
@@ -565,14 +574,16 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
565
574
|
|
|
566
575
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
567
576
|
LoggerProxy.logger.info(
|
|
568
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received
|
|
577
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received`,
|
|
578
|
+
currentPacketsReceived
|
|
569
579
|
);
|
|
570
580
|
} else if (
|
|
571
581
|
currentSamplesReceived === previousSamplesReceived ||
|
|
572
582
|
currentSamplesReceived === 0
|
|
573
583
|
) {
|
|
574
584
|
LoggerProxy.logger.info(
|
|
575
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio samples received
|
|
585
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio samples received`,
|
|
586
|
+
currentSamplesReceived
|
|
576
587
|
);
|
|
577
588
|
}
|
|
578
589
|
|
|
@@ -589,7 +600,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
589
600
|
currentStats.totalPacketsSent === 0
|
|
590
601
|
) {
|
|
591
602
|
LoggerProxy.logger.info(
|
|
592
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent
|
|
603
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
|
|
604
|
+
currentStats.totalPacketsSent
|
|
593
605
|
);
|
|
594
606
|
} else {
|
|
595
607
|
if (
|
|
@@ -597,7 +609,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
597
609
|
currentStats.framesEncoded === 0
|
|
598
610
|
) {
|
|
599
611
|
LoggerProxy.logger.info(
|
|
600
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded
|
|
612
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
|
|
613
|
+
currentStats.framesEncoded
|
|
601
614
|
);
|
|
602
615
|
}
|
|
603
616
|
|
|
@@ -607,7 +620,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
607
620
|
this.statsResults.resolutions['video-send'].send.framesSent === 0
|
|
608
621
|
) {
|
|
609
622
|
LoggerProxy.logger.info(
|
|
610
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent
|
|
623
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
|
|
624
|
+
this.statsResults.resolutions['video-send'].send.framesSent
|
|
611
625
|
);
|
|
612
626
|
}
|
|
613
627
|
}
|
|
@@ -643,24 +657,28 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
643
657
|
|
|
644
658
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
645
659
|
LoggerProxy.logger.info(
|
|
646
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received
|
|
660
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received`,
|
|
661
|
+
currentPacketsReceived
|
|
647
662
|
);
|
|
648
663
|
} else {
|
|
649
664
|
if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
|
|
650
665
|
LoggerProxy.logger.info(
|
|
651
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video frames received
|
|
666
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video frames received`,
|
|
667
|
+
currentFramesReceived
|
|
652
668
|
);
|
|
653
669
|
}
|
|
654
670
|
|
|
655
671
|
if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
|
|
656
672
|
LoggerProxy.logger.info(
|
|
657
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video frames decoded
|
|
673
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video frames decoded`,
|
|
674
|
+
currentFramesDecoded
|
|
658
675
|
);
|
|
659
676
|
}
|
|
660
677
|
|
|
661
678
|
if (currentFramesDropped - previousFramesDropped > 10) {
|
|
662
679
|
LoggerProxy.logger.info(
|
|
663
|
-
`StatsAnalyzer:index#compareLastStatsResult --> video frames are getting dropped
|
|
680
|
+
`StatsAnalyzer:index#compareLastStatsResult --> video frames are getting dropped`,
|
|
681
|
+
currentFramesDropped - previousFramesDropped
|
|
664
682
|
);
|
|
665
683
|
}
|
|
666
684
|
}
|
|
@@ -679,7 +697,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
679
697
|
currentStats.totalPacketsSent === 0
|
|
680
698
|
) {
|
|
681
699
|
LoggerProxy.logger.info(
|
|
682
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent
|
|
700
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent`,
|
|
701
|
+
currentStats.totalPacketsSent
|
|
683
702
|
);
|
|
684
703
|
} else {
|
|
685
704
|
if (
|
|
@@ -687,7 +706,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
687
706
|
currentStats.framesEncoded === 0
|
|
688
707
|
) {
|
|
689
708
|
LoggerProxy.logger.info(
|
|
690
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded
|
|
709
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded`,
|
|
710
|
+
currentStats.framesEncoded
|
|
691
711
|
);
|
|
692
712
|
}
|
|
693
713
|
|
|
@@ -697,7 +717,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
697
717
|
this.statsResults.resolutions['video-share-send'].send.framesSent === 0
|
|
698
718
|
) {
|
|
699
719
|
LoggerProxy.logger.info(
|
|
700
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames sent
|
|
720
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames sent`,
|
|
721
|
+
this.statsResults.resolutions['video-share-send'].send.framesSent
|
|
701
722
|
);
|
|
702
723
|
}
|
|
703
724
|
}
|
|
@@ -735,24 +756,28 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
735
756
|
|
|
736
757
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
737
758
|
LoggerProxy.logger.info(
|
|
738
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets received
|
|
759
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets received`,
|
|
760
|
+
currentPacketsReceived
|
|
739
761
|
);
|
|
740
762
|
} else {
|
|
741
763
|
if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
|
|
742
764
|
LoggerProxy.logger.info(
|
|
743
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames received
|
|
765
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames received`,
|
|
766
|
+
currentFramesReceived
|
|
744
767
|
);
|
|
745
768
|
}
|
|
746
769
|
|
|
747
770
|
if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
|
|
748
771
|
LoggerProxy.logger.info(
|
|
749
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames decoded
|
|
772
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames decoded`,
|
|
773
|
+
currentFramesDecoded
|
|
750
774
|
);
|
|
751
775
|
}
|
|
752
776
|
|
|
753
777
|
if (currentFramesDropped - previousFramesDropped > 10) {
|
|
754
778
|
LoggerProxy.logger.info(
|
|
755
|
-
`StatsAnalyzer:index#compareLastStatsResult --> share frames are getting dropped
|
|
779
|
+
`StatsAnalyzer:index#compareLastStatsResult --> share frames are getting dropped`,
|
|
780
|
+
currentFramesDropped - previousFramesDropped
|
|
756
781
|
);
|
|
757
782
|
}
|
|
758
783
|
}
|
|
@@ -933,6 +958,10 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
933
958
|
|
|
934
959
|
if (result.bytesReceived) {
|
|
935
960
|
let kilobytes = 0;
|
|
961
|
+
const receiveSlot = this.receiveSlotCallback(result.ssrc);
|
|
962
|
+
const idAndCsi = receiveSlot
|
|
963
|
+
? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
|
|
964
|
+
: '';
|
|
936
965
|
|
|
937
966
|
if (result.frameWidth && result.frameHeight) {
|
|
938
967
|
this.statsResults.resolutions[mediaType][sendrecvType].width = result.frameWidth;
|
|
@@ -989,10 +1018,12 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
989
1018
|
result.packetsReceived;
|
|
990
1019
|
|
|
991
1020
|
if (this.statsResults[mediaType][sendrecvType].packetsReceived === 0) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1021
|
+
if (receiveSlot) {
|
|
1022
|
+
LoggerProxy.logger.info(
|
|
1023
|
+
`StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot ${idAndCsi}`,
|
|
1024
|
+
this.statsResults[mediaType][sendrecvType].packetsReceived
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
996
1027
|
}
|
|
997
1028
|
|
|
998
1029
|
// Check the over all packet Lost ratio
|
|
@@ -1004,7 +1035,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
1004
1035
|
: 0;
|
|
1005
1036
|
if (this.statsResults[mediaType][sendrecvType].currentPacketLossRatio > 3) {
|
|
1006
1037
|
LoggerProxy.logger.info(
|
|
1007
|
-
|
|
1038
|
+
`StatsAnalyzer:index#processInboundRTPResult --> Packets getting lost from the receiver with slot ${idAndCsi}`,
|
|
1008
1039
|
this.statsResults[mediaType][sendrecvType].currentPacketLossRatio
|
|
1009
1040
|
);
|
|
1010
1041
|
}
|
|
@@ -15,7 +15,7 @@ describe('ReceiveSlotManager', () => {
|
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
17
17
|
fakeWcmeSlot = {
|
|
18
|
-
id:
|
|
18
|
+
id: {ssrc: 1},
|
|
19
19
|
};
|
|
20
20
|
fakeReceiveSlots = [];
|
|
21
21
|
mockReceiveSlotCtor = sinon.stub(ReceiveSlotModule, 'ReceiveSlot').callsFake((mediaType) => {
|
|
@@ -23,6 +23,7 @@ describe('ReceiveSlotManager', () => {
|
|
|
23
23
|
id: `fake sdk receive slot ${fakeReceiveSlots.length + 1}`,
|
|
24
24
|
mediaType,
|
|
25
25
|
findMemberId: sinon.stub(),
|
|
26
|
+
wcmeReceiveSlot: fakeWcmeSlot,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
fakeReceiveSlots.push(fakeReceiveSlot);
|
|
@@ -168,7 +169,6 @@ describe('ReceiveSlotManager', () => {
|
|
|
168
169
|
});
|
|
169
170
|
|
|
170
171
|
describe('updateMemberIds', () => {
|
|
171
|
-
|
|
172
172
|
it('calls findMemberId() on all allocated receive slots', async () => {
|
|
173
173
|
const audioSlots: ReceiveSlot[] = [];
|
|
174
174
|
const videoSlots: ReceiveSlot[] = [];
|
|
@@ -187,9 +187,17 @@ describe('ReceiveSlotManager', () => {
|
|
|
187
187
|
|
|
188
188
|
assert.strictEqual(fakeReceiveSlots.length, audioSlots.length + videoSlots.length);
|
|
189
189
|
|
|
190
|
-
fakeReceiveSlots.forEach(slot => {
|
|
190
|
+
fakeReceiveSlots.forEach((slot) => {
|
|
191
191
|
assert.calledOnce(slot.findMemberId);
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
194
|
});
|
|
195
|
+
|
|
196
|
+
describe('findReceiveSlotBySsrc', () => {
|
|
197
|
+
it('finds a receive slot with a specific id', async () => {
|
|
198
|
+
await receiveSlotManager.allocateSlot(MediaType.VideoMain);
|
|
199
|
+
assert.exists(receiveSlotManager.findReceiveSlotBySsrc(1));
|
|
200
|
+
assert.strictEqual(receiveSlotManager.findReceiveSlotBySsrc(2), undefined);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
195
203
|
});
|
|
@@ -53,7 +53,12 @@ describe('plugin-meetings', () => {
|
|
|
53
53
|
beforeEach(() => {
|
|
54
54
|
const networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
|
|
55
55
|
|
|
56
|
-
statsAnalyzer = new StatsAnalyzer(
|
|
56
|
+
statsAnalyzer = new StatsAnalyzer(
|
|
57
|
+
initialConfig,
|
|
58
|
+
() => ({}),
|
|
59
|
+
networkQualityMonitor,
|
|
60
|
+
defaultStats
|
|
61
|
+
);
|
|
57
62
|
|
|
58
63
|
sandBoxSpy = sandbox.spy(
|
|
59
64
|
statsAnalyzer.networkQualityMonitor,
|
|
@@ -188,7 +193,7 @@ describe('plugin-meetings', () => {
|
|
|
188
193
|
|
|
189
194
|
networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
|
|
190
195
|
|
|
191
|
-
statsAnalyzer = new StatsAnalyzer(initialConfig, networkQualityMonitor);
|
|
196
|
+
statsAnalyzer = new StatsAnalyzer(initialConfig, () => ({}), networkQualityMonitor);
|
|
192
197
|
|
|
193
198
|
statsAnalyzer.on(EVENTS.LOCAL_MEDIA_STARTED, (data) => {
|
|
194
199
|
receivedEventsData.local.started = data;
|