@webex/plugin-meetings 3.3.1-next.2 → 3.3.1-next.21

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.
Files changed (56) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +7 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/interpretation/index.js +1 -1
  5. package/dist/interpretation/siLanguage.js +1 -1
  6. package/dist/media/MediaConnectionAwaiter.js +50 -13
  7. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  8. package/dist/mediaQualityMetrics/config.js +16 -6
  9. package/dist/mediaQualityMetrics/config.js.map +1 -1
  10. package/dist/meeting/connectionStateHandler.js +67 -0
  11. package/dist/meeting/connectionStateHandler.js.map +1 -0
  12. package/dist/meeting/index.js +98 -46
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/metrics/constants.js +2 -1
  15. package/dist/metrics/constants.js.map +1 -1
  16. package/dist/metrics/index.js +57 -0
  17. package/dist/metrics/index.js.map +1 -1
  18. package/dist/reachability/clusterReachability.js +108 -53
  19. package/dist/reachability/clusterReachability.js.map +1 -1
  20. package/dist/reachability/index.js +415 -56
  21. package/dist/reachability/index.js.map +1 -1
  22. package/dist/statsAnalyzer/index.js +81 -27
  23. package/dist/statsAnalyzer/index.js.map +1 -1
  24. package/dist/statsAnalyzer/mqaUtil.js +36 -10
  25. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  26. package/dist/types/media/MediaConnectionAwaiter.d.ts +18 -4
  27. package/dist/types/mediaQualityMetrics/config.d.ts +11 -0
  28. package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
  29. package/dist/types/meeting/index.d.ts +2 -0
  30. package/dist/types/metrics/constants.d.ts +1 -0
  31. package/dist/types/metrics/index.d.ts +15 -0
  32. package/dist/types/reachability/clusterReachability.d.ts +31 -3
  33. package/dist/types/reachability/index.d.ts +93 -2
  34. package/dist/types/statsAnalyzer/index.d.ts +15 -6
  35. package/dist/types/statsAnalyzer/mqaUtil.d.ts +17 -4
  36. package/dist/webinar/index.js +1 -1
  37. package/package.json +23 -22
  38. package/src/breakouts/index.ts +7 -1
  39. package/src/media/MediaConnectionAwaiter.ts +66 -11
  40. package/src/mediaQualityMetrics/config.ts +14 -3
  41. package/src/meeting/connectionStateHandler.ts +65 -0
  42. package/src/meeting/index.ts +72 -14
  43. package/src/metrics/constants.ts +1 -0
  44. package/src/metrics/index.ts +44 -0
  45. package/src/reachability/clusterReachability.ts +86 -25
  46. package/src/reachability/index.ts +316 -27
  47. package/src/statsAnalyzer/index.ts +85 -24
  48. package/src/statsAnalyzer/mqaUtil.ts +55 -7
  49. package/test/unit/spec/breakouts/index.ts +51 -32
  50. package/test/unit/spec/media/MediaConnectionAwaiter.ts +90 -32
  51. package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
  52. package/test/unit/spec/meeting/index.js +158 -36
  53. package/test/unit/spec/metrics/index.js +126 -0
  54. package/test/unit/spec/reachability/clusterReachability.ts +116 -22
  55. package/test/unit/spec/reachability/index.ts +1153 -84
  56. package/test/unit/spec/stats-analyzer/index.js +647 -319
@@ -10,12 +10,21 @@ import testUtils from '../../../utils/testUtils';
10
10
  import {MEDIA_DEVICES, MQA_INTERVAL, _UNKNOWN_} from '@webex/plugin-meetings/src/constants';
11
11
  import LoggerProxy from '../../../../src/common/logs/logger-proxy';
12
12
  import LoggerConfig from '../../../../src/common/logs/logger-config';
13
+ import {CpuInfo} from '@webex/web-capabilities';
13
14
 
14
15
  const {assert} = chai;
15
16
 
16
17
  chai.use(chaiAsPromised);
17
18
  sinon.assert.expose(chai.assert, {prefix: ''});
18
19
 
20
+ const startStatsAnalyzer = async ({statsAnalyzer, mediaStatus, lastEmittedEvents = {}, pc}) => {
21
+ statsAnalyzer.updateMediaStatus(mediaStatus);
22
+ statsAnalyzer.startAnalyzer(pc);
23
+ statsAnalyzer.lastEmittedStartStopEvent = lastEmittedEvents;
24
+
25
+ await testUtils.flushPromises();
26
+ };
27
+
19
28
  describe('plugin-meetings', () => {
20
29
  describe('StatsAnalyzer', () => {
21
30
  describe('parseStatsResult', () => {
@@ -29,10 +38,12 @@ describe('plugin-meetings', () => {
29
38
  const networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
30
39
 
31
40
  statsAnalyzer = new StatsAnalyzer(
32
- initialConfig,
33
- () => ({}),
34
- networkQualityMonitor,
35
- defaultStats
41
+ {
42
+ config: initialConfig,
43
+ receiveSlotCallback: () => ({}),
44
+ networkQualityMonitor,
45
+ statsResults: defaultStats,
46
+ },
36
47
  );
37
48
  });
38
49
 
@@ -89,7 +100,7 @@ describe('plugin-meetings', () => {
89
100
  requestedBitrate: 10000,
90
101
  },
91
102
  'audio-send',
92
- true
103
+ true,
93
104
  );
94
105
 
95
106
  assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.headerBytesSent, 25000);
@@ -126,7 +137,7 @@ describe('plugin-meetings', () => {
126
137
  requestedBitrate: 50000,
127
138
  },
128
139
  'video-send',
129
- true
140
+ true,
130
141
  );
131
142
 
132
143
  assert.strictEqual(statsAnalyzer.statsResults['video-send'].send.headerBytesSent, 50000);
@@ -136,11 +147,11 @@ describe('plugin-meetings', () => {
136
147
  assert.strictEqual(statsAnalyzer.statsResults['video-send'].send.requestedBitrate, 50000);
137
148
  assert.strictEqual(
138
149
  statsAnalyzer.statsResults['video-send'].send.totalRtxPacketsSent,
139
- 10
150
+ 10,
140
151
  );
141
152
  assert.strictEqual(
142
153
  statsAnalyzer.statsResults['video-send'].send.totalRtxBytesSent,
143
- 500
154
+ 500,
144
155
  );
145
156
  });
146
157
 
@@ -183,12 +194,12 @@ describe('plugin-meetings', () => {
183
194
  requestedBitrate: 10000,
184
195
  },
185
196
  'audio-recv-1',
186
- false
197
+ false,
187
198
  );
188
199
 
189
200
  assert.strictEqual(
190
201
  statsAnalyzer.statsResults['audio-recv-1'].recv.totalPacketsReceived,
191
- 12
202
+ 12,
192
203
  );
193
204
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.fecPacketsDiscarded, 1);
194
205
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.fecPacketsReceived, 1);
@@ -196,18 +207,18 @@ describe('plugin-meetings', () => {
196
207
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.requestedBitrate, 10000);
197
208
  assert.strictEqual(
198
209
  statsAnalyzer.statsResults['audio-recv-1'].recv.headerBytesReceived,
199
- 250
210
+ 250,
200
211
  );
201
212
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.audioLevel, 0);
202
213
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalAudioEnergy, 133);
203
214
  assert.strictEqual(
204
215
  statsAnalyzer.statsResults['audio-recv-1'].recv.totalSamplesReceived,
205
- 300000
216
+ 300000,
206
217
  );
207
218
  assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalSamplesDecoded, 0);
208
219
  assert.strictEqual(
209
220
  statsAnalyzer.statsResults['audio-recv-1'].recv.concealedSamples,
210
- 200000
221
+ 200000,
211
222
  );
212
223
  });
213
224
 
@@ -243,7 +254,7 @@ describe('plugin-meetings', () => {
243
254
  retransmittedPacketsReceived: 10,
244
255
  },
245
256
  'video-recv',
246
- false
257
+ false,
247
258
  );
248
259
 
249
260
  assert.strictEqual(statsAnalyzer.statsResults['video-recv'].recv.totalPacketsReceived, 1500);
@@ -275,7 +286,7 @@ describe('plugin-meetings', () => {
275
286
  type: 'media-source',
276
287
  },
277
288
  'audio-send',
278
- true
289
+ true,
279
290
  );
280
291
 
281
292
  assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.audioLevel, 0.03);
@@ -322,15 +333,17 @@ describe('plugin-meetings', () => {
322
333
  const networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
323
334
 
324
335
  statsAnalyzer = new StatsAnalyzer(
325
- initialConfig,
326
- () => ({}),
327
- networkQualityMonitor,
328
- defaultStats
336
+ {
337
+ config: initialConfig,
338
+ receiveSlotCallback: () => ({}),
339
+ networkQualityMonitor,
340
+ statsResults: defaultStats,
341
+ },
329
342
  );
330
343
 
331
344
  sandBoxSpy = sandbox.spy(
332
345
  statsAnalyzer.networkQualityMonitor,
333
- 'determineUplinkNetworkQuality'
346
+ 'determineUplinkNetworkQuality',
334
347
  );
335
348
  });
336
349
 
@@ -347,7 +360,7 @@ describe('plugin-meetings', () => {
347
360
  mediaType: 'video-send-1',
348
361
  remoteRtpResults: statusResult,
349
362
  statsAnalyzerCurrentStats: statsAnalyzer.statsResults,
350
- })
363
+ }),
351
364
  );
352
365
  });
353
366
  });
@@ -381,6 +394,24 @@ describe('plugin-meetings', () => {
381
394
  };
382
395
  };
383
396
 
397
+ const registerStatsAnalyzerEvents = (statsAnalyzer) => {
398
+ statsAnalyzer.on(EVENTS.LOCAL_MEDIA_STARTED, (data) => {
399
+ receivedEventsData.local.started = data;
400
+ });
401
+ statsAnalyzer.on(EVENTS.LOCAL_MEDIA_STOPPED, (data) => {
402
+ receivedEventsData.local.stopped = data;
403
+ });
404
+ statsAnalyzer.on(EVENTS.REMOTE_MEDIA_STARTED, (data) => {
405
+ receivedEventsData.remote.started = data;
406
+ });
407
+ statsAnalyzer.on(EVENTS.REMOTE_MEDIA_STOPPED, (data) => {
408
+ receivedEventsData.remote.stopped = data;
409
+ });
410
+ statsAnalyzer.on(EVENTS.MEDIA_QUALITY, ({data}) => {
411
+ mqeData = data;
412
+ });
413
+ };
414
+
384
415
  before(() => {
385
416
  LoggerConfig.set({enable: false});
386
417
  LoggerProxy.set();
@@ -404,6 +435,7 @@ describe('plugin-meetings', () => {
404
435
  type: 'outbound-rtp',
405
436
  bytesSent: 1,
406
437
  packetsSent: 0,
438
+ isRequested: true,
407
439
  },
408
440
  {
409
441
  type: 'remote-inbound-rtp',
@@ -437,6 +469,8 @@ describe('plugin-meetings', () => {
437
469
  fecPacketsReceived: 0,
438
470
  packetsLost: 0,
439
471
  packetsReceived: 0,
472
+ isRequested: true,
473
+ lastRequestedUpdateTimestamp: 0,
440
474
  },
441
475
  {
442
476
  type: 'remote-outbound-rtp',
@@ -470,6 +504,8 @@ describe('plugin-meetings', () => {
470
504
  bytesSent: 1,
471
505
  framesSent: 0,
472
506
  packetsSent: 0,
507
+ isRequested: true,
508
+ lastRequestedUpdateTimestamp: 0,
473
509
  },
474
510
  {
475
511
  type: 'remote-inbound-rtp',
@@ -505,6 +541,10 @@ describe('plugin-meetings', () => {
505
541
  framesReceived: 0,
506
542
  packetsLost: 0,
507
543
  packetsReceived: 0,
544
+ isRequested: true,
545
+ lastRequestedUpdateTimestamp: 0,
546
+ isActiveSpeaker: false,
547
+ lastActiveSpeakerUpdateTimestamp: 0,
508
548
  },
509
549
  {
510
550
  type: 'remote-outbound-rtp',
@@ -538,6 +578,9 @@ describe('plugin-meetings', () => {
538
578
  bytesSent: 1,
539
579
  framesSent: 0,
540
580
  packetsSent: 0,
581
+ isRequested: true,
582
+ lastRequestedUpdateTimestamp: 0,
583
+ encoderImplementation: 'fake-encoder',
541
584
  },
542
585
  {
543
586
  type: 'remote-inbound-rtp',
@@ -573,6 +616,8 @@ describe('plugin-meetings', () => {
573
616
  framesReceived: 0,
574
617
  packetsLost: 0,
575
618
  packetsReceived: 0,
619
+ isRequested: true,
620
+ lastRequestedUpdateTimestamp: 0,
576
621
  },
577
622
  {
578
623
  type: 'remote-outbound-rtp',
@@ -622,23 +667,11 @@ describe('plugin-meetings', () => {
622
667
 
623
668
  networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
624
669
 
625
- statsAnalyzer = new StatsAnalyzer(initialConfig, () => receiveSlot, networkQualityMonitor);
626
-
627
- statsAnalyzer.on(EVENTS.LOCAL_MEDIA_STARTED, (data) => {
628
- receivedEventsData.local.started = data;
629
- });
630
- statsAnalyzer.on(EVENTS.LOCAL_MEDIA_STOPPED, (data) => {
631
- receivedEventsData.local.stopped = data;
632
- });
633
- statsAnalyzer.on(EVENTS.REMOTE_MEDIA_STARTED, (data) => {
634
- receivedEventsData.remote.started = data;
635
- });
636
- statsAnalyzer.on(EVENTS.REMOTE_MEDIA_STOPPED, (data) => {
637
- receivedEventsData.remote.stopped = data;
638
- });
639
- statsAnalyzer.on(EVENTS.MEDIA_QUALITY, ({data}) => {
640
- mqeData = data;
670
+ statsAnalyzer = new StatsAnalyzer({
671
+ config: initialConfig, receiveSlotCallback: () => receiveSlot, networkQualityMonitor,
641
672
  });
673
+
674
+ registerStatsAnalyzerEvents(statsAnalyzer);
642
675
  });
643
676
 
644
677
  afterEach(() => {
@@ -646,20 +679,12 @@ describe('plugin-meetings', () => {
646
679
  clock.restore();
647
680
  });
648
681
 
649
- const startStatsAnalyzer = async (mediaStatus, lastEmittedEvents) => {
650
- statsAnalyzer.updateMediaStatus(mediaStatus);
651
- statsAnalyzer.startAnalyzer(pc);
652
- statsAnalyzer.lastEmittedStartStopEvent = lastEmittedEvents || {};
653
-
654
- await testUtils.flushPromises();
655
- };
656
-
657
682
  const mergeProperties = (
658
683
  target,
659
684
  properties,
660
685
  keyValue = 'fake-candidate-id',
661
686
  matchKey = 'type',
662
- matchValue = 'local-candidate'
687
+ matchValue = 'local-candidate',
663
688
  ) => {
664
689
  for (let key in target) {
665
690
  if (target.hasOwnProperty(key)) {
@@ -704,7 +729,15 @@ describe('plugin-meetings', () => {
704
729
  };
705
730
 
706
731
  it('emits LOCAL_MEDIA_STARTED and LOCAL_MEDIA_STOPPED events for audio', async () => {
707
- await startStatsAnalyzer({expected: {sendAudio: true}});
732
+ await startStatsAnalyzer({
733
+ statsAnalyzer,
734
+ pc,
735
+ mediaStatus: {
736
+ expected: {
737
+ sendAudio: true,
738
+ },
739
+ },
740
+ });
708
741
 
709
742
  // check that we haven't received any events yet
710
743
  checkReceivedEvent({expected: {}});
@@ -724,7 +757,7 @@ describe('plugin-meetings', () => {
724
757
  });
725
758
 
726
759
  it('emits LOCAL_MEDIA_STARTED and LOCAL_MEDIA_STOPPED events for video', async () => {
727
- await startStatsAnalyzer({expected: {sendVideo: true}});
760
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {sendVideo: true}}});
728
761
 
729
762
  // check that we haven't received any events yet
730
763
  checkReceivedEvent({expected: {}});
@@ -744,7 +777,7 @@ describe('plugin-meetings', () => {
744
777
  });
745
778
 
746
779
  it('emits LOCAL_MEDIA_STARTED and LOCAL_MEDIA_STOPPED events for share', async () => {
747
- await startStatsAnalyzer({expected: {sendShare: true}});
780
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {sendShare: true}}});
748
781
 
749
782
  // check that we haven't received any events yet
750
783
  checkReceivedEvent({expected: {}});
@@ -764,7 +797,7 @@ describe('plugin-meetings', () => {
764
797
  });
765
798
 
766
799
  it('emits REMOTE_MEDIA_STARTED and REMOTE_MEDIA_STOPPED events for audio', async () => {
767
- await startStatsAnalyzer({expected: {receiveAudio: true}});
800
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveAudio: true}}});
768
801
 
769
802
  // check that we haven't received any events yet
770
803
  checkReceivedEvent({expected: {}});
@@ -784,7 +817,7 @@ describe('plugin-meetings', () => {
784
817
  });
785
818
 
786
819
  it('emits REMOTE_MEDIA_STARTED and REMOTE_MEDIA_STOPPED events for video', async () => {
787
- await startStatsAnalyzer({expected: {receiveVideo: true}});
820
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
788
821
 
789
822
  // check that we haven't received any events yet
790
823
  checkReceivedEvent({expected: {}});
@@ -804,7 +837,7 @@ describe('plugin-meetings', () => {
804
837
  });
805
838
 
806
839
  it('emits REMOTE_MEDIA_STARTED and REMOTE_MEDIA_STOPPED events for share', async () => {
807
- await startStatsAnalyzer({expected: {receiveShare: true}});
840
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveShare: true}}});
808
841
 
809
842
  // check that we haven't received any events yet
810
843
  checkReceivedEvent({expected: {}});
@@ -824,7 +857,7 @@ describe('plugin-meetings', () => {
824
857
  });
825
858
 
826
859
  it('emits the correct MEDIA_QUALITY events', async () => {
827
- await startStatsAnalyzer({expected: {receiveVideo: true}});
860
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
828
861
 
829
862
  await progressTime();
830
863
 
@@ -833,7 +866,7 @@ describe('plugin-meetings', () => {
833
866
  });
834
867
 
835
868
  it('emits the correct transportType in MEDIA_QUALITY events', async () => {
836
- await startStatsAnalyzer({expected: {receiveVideo: true}});
869
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
837
870
 
838
871
  await progressTime();
839
872
 
@@ -847,7 +880,7 @@ describe('plugin-meetings', () => {
847
880
  fakeStats.audio.receivers[0].report[4].relayProtocol = 'tls';
848
881
  fakeStats.video.receivers[0].report[4].relayProtocol = 'tls';
849
882
 
850
- await startStatsAnalyzer({expected: {receiveVideo: true}});
883
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
851
884
 
852
885
  await progressTime();
853
886
 
@@ -856,19 +889,19 @@ describe('plugin-meetings', () => {
856
889
  });
857
890
 
858
891
  it('emits the correct peripherals in MEDIA_QUALITY events', async () => {
859
- await startStatsAnalyzer({expected: {receiveVideo: true}});
892
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
860
893
 
861
894
  await progressTime();
862
895
 
863
896
  assert.strictEqual(
864
897
  mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.MICROPHONE)
865
898
  .information,
866
- 'fake-microphone'
899
+ 'fake-microphone',
867
900
  );
868
901
  assert.strictEqual(
869
902
  mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.CAMERA)
870
903
  .information,
871
- 'fake-camera'
904
+ 'fake-camera',
872
905
  );
873
906
  });
874
907
 
@@ -876,30 +909,33 @@ describe('plugin-meetings', () => {
876
909
  fakeStats.audio.senders[0].localTrackLabel = undefined;
877
910
  fakeStats.video.senders[0].localTrackLabel = undefined;
878
911
 
879
- await startStatsAnalyzer({expected: {receiveVideo: true}});
912
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
880
913
 
881
914
  await progressTime();
882
915
 
883
916
  assert.strictEqual(
884
917
  mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.MICROPHONE)
885
918
  .information,
886
- _UNKNOWN_
919
+ _UNKNOWN_,
887
920
  );
888
921
  assert.strictEqual(
889
922
  mqeData.intervalMetadata.peripherals.find((val) => val.name === MEDIA_DEVICES.CAMERA)
890
923
  .information,
891
- _UNKNOWN_
924
+ _UNKNOWN_,
892
925
  );
893
926
  });
894
927
 
895
- it('emits the correct transmittedFrameRate/receivedFrameRate', async () => {
896
- it('at the start of the stats analyzer', async () => {
897
- await startStatsAnalyzer();
928
+ describe('frame rate reporting in stats analyzer', () => {
929
+ beforeEach(async () => {
930
+ await startStatsAnalyzer({pc, statsAnalyzer});
931
+ });
932
+
933
+ it('should report a zero frame rate for both transmitted and received video at the start', async () => {
898
934
  assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.transmittedFrameRate, 0);
899
935
  assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 0);
900
936
  });
901
937
 
902
- it('after frames are sent and received', async () => {
938
+ it('should accurately report the transmitted and received frame rate after video frames are processed', async () => {
903
939
  fakeStats.video.senders[0].report[0].framesSent += 300;
904
940
  fakeStats.video.receivers[0].report[0].framesReceived += 300;
905
941
  await progressTime(MQA_INTERVAL);
@@ -910,9 +946,12 @@ describe('plugin-meetings', () => {
910
946
  });
911
947
  });
912
948
 
913
- it('emits the correct rtpPackets', async () => {
914
- it('at the start of the stats analyzer', async () => {
915
- await startStatsAnalyzer();
949
+ describe('RTP packets count in stats analyzer', () => {
950
+ beforeEach(async () => {
951
+ await startStatsAnalyzer({pc, statsAnalyzer});
952
+ });
953
+
954
+ it('should report zero RTP packets for all streams at the start of the stats analyzer', async () => {
916
955
  assert.strictEqual(mqeData.audioTransmit[0].common.rtpPackets, 0);
917
956
  assert.strictEqual(mqeData.audioTransmit[0].streams[0].common.rtpPackets, 0);
918
957
  assert.strictEqual(mqeData.audioReceive[0].common.rtpPackets, 0);
@@ -923,7 +962,7 @@ describe('plugin-meetings', () => {
923
962
  assert.strictEqual(mqeData.videoReceive[0].streams[0].common.rtpPackets, 0);
924
963
  });
925
964
 
926
- it('after packets are sent', async () => {
965
+ it('should update the RTP packets count correctly after audio and video packets are sent', async () => {
927
966
  fakeStats.audio.senders[0].report[0].packetsSent += 5;
928
967
  fakeStats.video.senders[0].report[0].packetsSent += 5;
929
968
  await progressTime(MQA_INTERVAL);
@@ -934,7 +973,7 @@ describe('plugin-meetings', () => {
934
973
  assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.rtpPackets, 5);
935
974
  });
936
975
 
937
- it('after packets are received', async () => {
976
+ it('should update the RTP packets count correctly after audio and video packets are received', async () => {
938
977
  fakeStats.audio.senders[0].report[0].packetsSent += 10;
939
978
  fakeStats.video.senders[0].report[0].packetsSent += 10;
940
979
  fakeStats.audio.receivers[0].report[0].packetsReceived += 10;
@@ -948,38 +987,83 @@ describe('plugin-meetings', () => {
948
987
  });
949
988
  });
950
989
 
951
- it('emits the correct fecPackets', async () => {
952
- it('at the start of the stats analyzer', async () => {
953
- await startStatsAnalyzer();
990
+ describe('FEC packet reporting in stats analyzer', () => {
991
+ beforeEach(async () => {
992
+ await startStatsAnalyzer({pc, statsAnalyzer});
993
+ });
994
+
995
+ it('should initially report zero FEC packets at the start of the stats analyzer', async () => {
954
996
  assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 0);
955
997
  });
956
998
 
957
- it('after FEC packets are received', async () => {
999
+ it('should accurately report the count of FEC packets received', async () => {
958
1000
  fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 5;
959
1001
  await progressTime(MQA_INTERVAL);
960
1002
 
961
1003
  assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 5);
962
1004
  });
963
1005
 
964
- it('after FEC packets are received and some FEC packets are discarded', async () => {
1006
+ it('should accurately update and reset the FEC packet count based on received packets over MQA intervals', async () => {
965
1007
  fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 15;
966
- fakeStats.audio.receivers[0].report[0].fecPacketsDiscarded += 5;
967
1008
  await progressTime(MQA_INTERVAL);
1009
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 15);
968
1010
 
969
- assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 10);
1011
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 45;
1012
+ await progressTime(MQA_INTERVAL);
1013
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 45);
1014
+
1015
+ await progressTime(MQA_INTERVAL);
1016
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 0);
970
1017
  });
971
1018
  });
972
1019
 
973
- it('emits the correct mediaHopByHopLost/rtpHopByHopLost', async () => {
974
- it('at the start of the stats analyzer', async () => {
975
- await startStatsAnalyzer();
1020
+ describe('RTP recovered packets emission', async() => {
1021
+ beforeEach(async() => {
1022
+ await startStatsAnalyzer({pc, statsAnalyzer});
1023
+ });
1024
+
1025
+ it('should initially report zero RTP recovered packets', async() => {
1026
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpRecovered, 0);
1027
+ })
1028
+
1029
+ it('should report RTP recovered packets equal to FEC packets received', async() => {
1030
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 10;
1031
+
1032
+ await progressTime(MQA_INTERVAL);
1033
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpRecovered, 10);
1034
+ })
1035
+
1036
+ it('should reset RTP recovered packets count after each interval', async () => {
1037
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 100;
1038
+ await progressTime(MQA_INTERVAL);
1039
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpRecovered, 100);
1040
+
1041
+ await progressTime(MQA_INTERVAL);
1042
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpRecovered, 0);
1043
+ })
1044
+
1045
+ it('should correctly calculate RTP recovered packets after discarding FEC packets', async () => {
1046
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 100;
1047
+ fakeStats.audio.receivers[0].report[0].fecPacketsDiscarded += 20;
1048
+
1049
+ await progressTime(MQA_INTERVAL);
1050
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpRecovered, 80);
1051
+ })
1052
+ })
1053
+
1054
+ describe('packet loss metrics reporting in stats analyzer', () => {
1055
+ beforeEach(async () => {
1056
+ await startStatsAnalyzer({pc, statsAnalyzer});
1057
+ });
1058
+
1059
+ it('should report zero packet loss for both audio and video at the start of the stats analyzer', async () => {
976
1060
  assert.strictEqual(mqeData.audioReceive[0].common.mediaHopByHopLost, 0);
977
1061
  assert.strictEqual(mqeData.audioReceive[0].common.rtpHopByHopLost, 0);
978
1062
  assert.strictEqual(mqeData.videoReceive[0].common.mediaHopByHopLost, 0);
979
1063
  assert.strictEqual(mqeData.videoReceive[0].common.rtpHopByHopLost, 0);
980
1064
  });
981
1065
 
982
- it('after packets are lost', async () => {
1066
+ it('should update packet loss metrics correctly for both audio and video after packet loss is detected', async () => {
983
1067
  fakeStats.audio.receivers[0].report[0].packetsLost += 5;
984
1068
  fakeStats.video.receivers[0].report[0].packetsLost += 5;
985
1069
  await progressTime(MQA_INTERVAL);
@@ -991,36 +1075,56 @@ describe('plugin-meetings', () => {
991
1075
  });
992
1076
  });
993
1077
 
994
- it('emits the correct remoteLossRate', async () => {
995
- it('at the start of the stats analyzer', async () => {
996
- await startStatsAnalyzer();
997
- assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 0);
998
- assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 0);
1078
+ describe('maximum remote loss rate reporting in stats analyzer', () => {
1079
+ beforeEach(async () => {
1080
+ await startStatsAnalyzer({pc, statsAnalyzer});
999
1081
  });
1000
1082
 
1001
- it('after packets are sent', async () => {
1083
+ it('should report a zero maximum remote loss rate for both audio and video at the start', async () => {
1084
+ assert.strictEqual(mqeData.audioTransmit[0].common.maxRemoteLossRate, 0);
1085
+ assert.strictEqual(mqeData.videoTransmit[0].common.maxRemoteLossRate, 0);
1086
+ });
1087
+
1088
+ it('should maintain a zero maximum remote loss rate for both audio and video after packets are sent without loss', async () => {
1002
1089
  fakeStats.audio.senders[0].report[0].packetsSent += 100;
1003
1090
  fakeStats.video.senders[0].report[0].packetsSent += 100;
1004
1091
  await progressTime(MQA_INTERVAL);
1005
1092
 
1006
- assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 0);
1007
- assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 0);
1093
+ assert.strictEqual(mqeData.audioTransmit[0].common.maxRemoteLossRate, 0);
1094
+ assert.strictEqual(mqeData.videoTransmit[0].common.maxRemoteLossRate, 0);
1008
1095
  });
1009
1096
 
1010
- it('after packets are sent and some packets are lost', async () => {
1097
+ it('should accurately calculate the maximum remote loss rate for both audio and video after packet loss is detected', async () => {
1011
1098
  fakeStats.audio.senders[0].report[0].packetsSent += 200;
1012
1099
  fakeStats.audio.senders[0].report[1].packetsLost += 10;
1013
1100
  fakeStats.video.senders[0].report[0].packetsSent += 200;
1014
1101
  fakeStats.video.senders[0].report[1].packetsLost += 10;
1015
1102
  await progressTime(MQA_INTERVAL);
1016
1103
 
1017
- assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 5);
1018
- assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 5);
1104
+ assert.strictEqual(mqeData.audioTransmit[0].common.maxRemoteLossRate, 5);
1105
+ assert.strictEqual(mqeData.videoTransmit[0].common.maxRemoteLossRate, 5);
1019
1106
  });
1107
+
1108
+ it('should reset the maximum remote loss rate across MQA intervals', async() => {
1109
+ fakeStats.audio.senders[0].report[0].packetsSent += 100;
1110
+ fakeStats.audio.senders[0].report[1].packetsLost += 10;
1111
+ fakeStats.video.senders[0].report[0].packetsSent += 50;
1112
+ fakeStats.video.senders[0].report[1].packetsLost += 5;
1113
+ await progressTime(MQA_INTERVAL);
1114
+
1115
+ assert.strictEqual(mqeData.audioTransmit[0].common.maxRemoteLossRate, 10);
1116
+ assert.strictEqual(mqeData.videoTransmit[0].common.maxRemoteLossRate, 10);
1117
+
1118
+ await progressTime(MQA_INTERVAL);
1119
+
1120
+ assert.strictEqual(mqeData.audioTransmit[0].common.maxRemoteLossRate, 0);
1121
+ assert.strictEqual(mqeData.videoTransmit[0].common.maxRemoteLossRate, 0);
1122
+
1123
+ })
1020
1124
  });
1021
1125
 
1022
1126
  it('has the correct localIpAddress set when the candidateType is host', async () => {
1023
- await startStatsAnalyzer();
1127
+ await startStatsAnalyzer({pc, statsAnalyzer});
1024
1128
 
1025
1129
  await progressTime();
1026
1130
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
@@ -1030,7 +1134,7 @@ describe('plugin-meetings', () => {
1030
1134
  });
1031
1135
 
1032
1136
  it('has the correct localIpAddress set when the candidateType is prflx and relayProtocol is set', async () => {
1033
- await startStatsAnalyzer();
1137
+ await startStatsAnalyzer({pc, statsAnalyzer});
1034
1138
 
1035
1139
  await progressTime();
1036
1140
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
@@ -1044,7 +1148,7 @@ describe('plugin-meetings', () => {
1044
1148
  });
1045
1149
 
1046
1150
  it('has the correct localIpAddress set when the candidateType is prflx and relayProtocol is not set', async () => {
1047
- await startStatsAnalyzer();
1151
+ await startStatsAnalyzer({pc, statsAnalyzer});
1048
1152
 
1049
1153
  await progressTime();
1050
1154
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
@@ -1058,7 +1162,7 @@ describe('plugin-meetings', () => {
1058
1162
  });
1059
1163
 
1060
1164
  it('has no localIpAddress set when the candidateType is invalid', async () => {
1061
- await startStatsAnalyzer();
1165
+ await startStatsAnalyzer({pc, statsAnalyzer});
1062
1166
 
1063
1167
  await progressTime();
1064
1168
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
@@ -1067,10 +1171,19 @@ describe('plugin-meetings', () => {
1067
1171
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
1068
1172
  });
1069
1173
 
1174
+ it('has the correct share video encoder implementation as provided by the stats', async () => {
1175
+ await startStatsAnalyzer({pc, statsAnalyzer});
1176
+
1177
+ await progressTime();
1178
+ assert.strictEqual(statsAnalyzer.shareVideoEncoderImplementation, 'fake-encoder');
1179
+ });
1180
+
1070
1181
  it('logs a message when audio send packets do not increase', async () => {
1071
1182
  await startStatsAnalyzer(
1072
- {expected: {sendAudio: true}},
1073
- {audio: {local: EVENTS.LOCAL_MEDIA_STARTED}}
1183
+ {
1184
+ statsAnalyzer, pc, mediaStatus: {expected: {sendAudio: true}},
1185
+ lastEmittedEvents: {audio: {local: EVENTS.LOCAL_MEDIA_STARTED}},
1186
+ },
1074
1187
  );
1075
1188
 
1076
1189
  // don't increase the packets when time progresses.
@@ -1078,15 +1191,17 @@ describe('plugin-meetings', () => {
1078
1191
 
1079
1192
  assert(
1080
1193
  loggerSpy.calledWith(
1081
- 'StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent'
1082
- )
1194
+ 'StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent',
1195
+ ),
1083
1196
  );
1084
1197
  });
1085
1198
 
1086
1199
  it('does not log a message when audio send packets increase', async () => {
1087
- await startStatsAnalyzer(
1088
- {expected: {sendAudio: true}},
1089
- {audio: {local: EVENTS.LOCAL_MEDIA_STOPPED}}
1200
+ await startStatsAnalyzer({
1201
+ statsAnalyzer, pc,
1202
+ mediaStatus: {expected: {sendAudio: true}},
1203
+ lastEmittedEvents: {audio: {local: EVENTS.LOCAL_MEDIA_STOPPED}},
1204
+ },
1090
1205
  );
1091
1206
 
1092
1207
  fakeStats.audio.senders[0].report[0].packetsSent += 5;
@@ -1094,15 +1209,16 @@ describe('plugin-meetings', () => {
1094
1209
 
1095
1210
  assert(
1096
1211
  loggerSpy.neverCalledWith(
1097
- 'StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent'
1098
- )
1212
+ 'StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent',
1213
+ ),
1099
1214
  );
1100
1215
  });
1101
1216
 
1102
1217
  it('logs a message when video send packets do not increase', async () => {
1103
- await startStatsAnalyzer(
1104
- {expected: {sendVideo: true}},
1105
- {video: {local: EVENTS.LOCAL_MEDIA_STARTED}}
1218
+ await startStatsAnalyzer({
1219
+ statsAnalyzer, pc, mediaStatus: {expected: {sendVideo: true}},
1220
+ lastEmittedEvents: {video: {local: EVENTS.LOCAL_MEDIA_STARTED}},
1221
+ },
1106
1222
  );
1107
1223
 
1108
1224
  // don't increase the packets when time progresses.
@@ -1110,31 +1226,42 @@ describe('plugin-meetings', () => {
1110
1226
 
1111
1227
  assert(
1112
1228
  loggerSpy.calledWith(
1113
- 'StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent'
1114
- )
1229
+ 'StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent',
1230
+ ),
1115
1231
  );
1116
1232
  });
1117
1233
 
1118
1234
  it('does not log a message when video send packets increase', async () => {
1119
1235
  await startStatsAnalyzer(
1120
- {expected: {sendVideo: true}},
1121
- {video: {local: EVENTS.LOCAL_MEDIA_STOPPED}}
1122
- );
1236
+ {
1237
+ statsAnalyzer, pc,
1238
+ mediaStatus: {
1239
+ expected: {
1240
+ sendVideo: true,
1241
+ },
1242
+ },
1243
+ lastEmittedEvents: {
1244
+ video: {
1245
+ local: EVENTS.LOCAL_MEDIA_STOPPED,
1246
+ },
1247
+ },
1248
+ });
1123
1249
 
1124
1250
  fakeStats.video.senders[0].report[0].packetsSent += 5;
1125
1251
  await progressTime();
1126
1252
 
1127
1253
  assert(
1128
1254
  loggerSpy.neverCalledWith(
1129
- 'StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent'
1130
- )
1255
+ 'StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent',
1256
+ ),
1131
1257
  );
1132
1258
  });
1133
1259
 
1134
1260
  it('logs a message when share send packets do not increase', async () => {
1135
- await startStatsAnalyzer(
1136
- {expected: {sendShare: true}},
1137
- {share: {local: EVENTS.LOCAL_MEDIA_STARTED}}
1261
+ await startStatsAnalyzer({
1262
+ pc, mediaStatus: {expected: {sendShare: true}},
1263
+ lastEmittedEvents: {share: {local: EVENTS.LOCAL_MEDIA_STARTED}}, statsAnalyzer,
1264
+ },
1138
1265
  );
1139
1266
 
1140
1267
  // don't increase the packets when time progresses.
@@ -1142,15 +1269,16 @@ describe('plugin-meetings', () => {
1142
1269
 
1143
1270
  assert(
1144
1271
  loggerSpy.calledWith(
1145
- 'StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent'
1146
- )
1272
+ 'StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent',
1273
+ ),
1147
1274
  );
1148
1275
  });
1149
1276
 
1150
1277
  it('does not log a message when share send packets increase', async () => {
1151
- await startStatsAnalyzer(
1152
- {expected: {sendShare: true}},
1153
- {share: {local: EVENTS.LOCAL_MEDIA_STOPPED}}
1278
+ await startStatsAnalyzer({
1279
+ pc, statsAnalyzer, mediaStatus: {expected: {sendShare: true}},
1280
+ lastEmittedEvents: {share: {local: EVENTS.LOCAL_MEDIA_STOPPED}},
1281
+ },
1154
1282
  );
1155
1283
 
1156
1284
  fakeStats.share.senders[0].report[0].packetsSent += 5;
@@ -1158,8 +1286,8 @@ describe('plugin-meetings', () => {
1158
1286
 
1159
1287
  assert(
1160
1288
  loggerSpy.neverCalledWith(
1161
- 'StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent'
1162
- )
1289
+ 'StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent',
1290
+ ),
1163
1291
  );
1164
1292
  });
1165
1293
 
@@ -1172,7 +1300,7 @@ describe('plugin-meetings', () => {
1172
1300
  id: '4',
1173
1301
  };
1174
1302
 
1175
- await startStatsAnalyzer();
1303
+ await startStatsAnalyzer({pc, statsAnalyzer});
1176
1304
 
1177
1305
  // don't increase the packets when time progresses.
1178
1306
  await progressTime();
@@ -1180,10 +1308,10 @@ describe('plugin-meetings', () => {
1180
1308
  assert.neverCalledWith(
1181
1309
  loggerSpy,
1182
1310
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot id: "4" and csi: 2. Total packets received on slot: ',
1183
- 0
1311
+ 0,
1184
1312
  );
1185
1313
  });
1186
- }
1314
+ },
1187
1315
  );
1188
1316
 
1189
1317
  it(`logs a message if no packets are sent`, async () => {
@@ -1192,7 +1320,7 @@ describe('plugin-meetings', () => {
1192
1320
  csi: 2,
1193
1321
  id: '4',
1194
1322
  };
1195
- await startStatsAnalyzer();
1323
+ await startStatsAnalyzer({pc, statsAnalyzer});
1196
1324
 
1197
1325
  // don't increase the packets when time progresses.
1198
1326
  await progressTime();
@@ -1200,52 +1328,52 @@ describe('plugin-meetings', () => {
1200
1328
  assert.calledWith(
1201
1329
  loggerSpy,
1202
1330
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: video-recv-0, receive slot id: "4" and csi: 2. Total packets received on slot: ',
1203
- 0
1331
+ 0,
1204
1332
  );
1205
1333
 
1206
1334
  assert.calledWith(
1207
1335
  loggerSpy,
1208
1336
  'StatsAnalyzer:index#processInboundRTPResult --> No frames received for mediaType: video-recv-0, receive slot id: "4" and csi: 2. Total frames received on slot: ',
1209
- 0
1337
+ 0,
1210
1338
  );
1211
1339
 
1212
1340
  assert.calledWith(
1213
1341
  loggerSpy,
1214
1342
  'StatsAnalyzer:index#processInboundRTPResult --> No frames decoded for mediaType: video-recv-0, receive slot id: "4" and csi: 2. Total frames decoded on slot: ',
1215
- 0
1343
+ 0,
1216
1344
  );
1217
1345
 
1218
1346
  assert.calledWith(
1219
1347
  loggerSpy,
1220
1348
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: audio-recv-0, receive slot id: "4" and csi: 2. Total packets received on slot: ',
1221
- 0
1349
+ 0,
1222
1350
  );
1223
1351
 
1224
1352
  assert.calledWith(
1225
1353
  loggerSpy,
1226
1354
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: video-share-recv-0, receive slot id: "4" and csi: 2. Total packets received on slot: ',
1227
- 0
1355
+ 0,
1228
1356
  );
1229
1357
 
1230
1358
  assert.calledWith(
1231
1359
  loggerSpy,
1232
1360
  'StatsAnalyzer:index#processInboundRTPResult --> No frames received for mediaType: video-share-recv-0, receive slot id: "4" and csi: 2. Total frames received on slot: ',
1233
- 0
1361
+ 0,
1234
1362
  );
1235
1363
  assert.calledWith(
1236
1364
  loggerSpy,
1237
1365
  'StatsAnalyzer:index#processInboundRTPResult --> No frames decoded for mediaType: video-share-recv-0, receive slot id: "4" and csi: 2. Total frames decoded on slot: ',
1238
- 0
1366
+ 0,
1239
1367
  );
1240
1368
  assert.calledWith(
1241
1369
  loggerSpy,
1242
1370
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: audio-share-recv-0, receive slot id: "4" and csi: 2. Total packets received on slot: ',
1243
- 0
1371
+ 0,
1244
1372
  );
1245
1373
  });
1246
1374
 
1247
1375
  it(`does not log a message if receiveSlot is undefined`, async () => {
1248
- await startStatsAnalyzer();
1376
+ await startStatsAnalyzer({pc, statsAnalyzer});
1249
1377
 
1250
1378
  // don't increase the packets when time progresses.
1251
1379
  await progressTime();
@@ -1253,12 +1381,12 @@ describe('plugin-meetings', () => {
1253
1381
  assert.neverCalledWith(
1254
1382
  loggerSpy,
1255
1383
  'StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot "". Total packets received on slot: ',
1256
- 0
1384
+ 0,
1257
1385
  );
1258
1386
  });
1259
1387
 
1260
1388
  it('has the correct number of senders and receivers (2)', async () => {
1261
- await startStatsAnalyzer({expected: {receiveVideo: true}});
1389
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
1262
1390
 
1263
1391
  await progressTime();
1264
1392
 
@@ -1269,7 +1397,7 @@ describe('plugin-meetings', () => {
1269
1397
  });
1270
1398
 
1271
1399
  it('has one stream per sender/reciever', async () => {
1272
- await startStatsAnalyzer({expected: {receiveVideo: true}});
1400
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
1273
1401
 
1274
1402
  await progressTime();
1275
1403
 
@@ -1456,7 +1584,7 @@ describe('plugin-meetings', () => {
1456
1584
  framesDropped: 0,
1457
1585
  },
1458
1586
  h264CodecProfile: 'BP',
1459
- isActiveSpeaker: true,
1587
+ isActiveSpeaker: false,
1460
1588
  optimalFrameSize: 0,
1461
1589
  receivedFrameSize: 3600,
1462
1590
  receivedHeight: 720,
@@ -1490,7 +1618,7 @@ describe('plugin-meetings', () => {
1490
1618
  framesDropped: 0,
1491
1619
  },
1492
1620
  h264CodecProfile: 'BP',
1493
- isActiveSpeaker: true,
1621
+ isActiveSpeaker: false,
1494
1622
  optimalFrameSize: 0,
1495
1623
  receivedFrameSize: 3600,
1496
1624
  receivedHeight: 720,
@@ -1529,7 +1657,7 @@ describe('plugin-meetings', () => {
1529
1657
  },
1530
1658
  });
1531
1659
 
1532
- await startStatsAnalyzer({expected: {receiveVideo: true}});
1660
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
1533
1661
 
1534
1662
  await progressTime();
1535
1663
 
@@ -1554,7 +1682,7 @@ describe('plugin-meetings', () => {
1554
1682
  framesDropped: 0,
1555
1683
  },
1556
1684
  h264CodecProfile: 'BP',
1557
- isActiveSpeaker: true,
1685
+ isActiveSpeaker: false,
1558
1686
  optimalFrameSize: 0,
1559
1687
  receivedFrameSize: 3600,
1560
1688
  receivedHeight: 720,
@@ -1586,7 +1714,7 @@ describe('plugin-meetings', () => {
1586
1714
  framesDropped: 0,
1587
1715
  },
1588
1716
  h264CodecProfile: 'BP',
1589
- isActiveSpeaker: true,
1717
+ isActiveSpeaker: false,
1590
1718
  optimalFrameSize: 0,
1591
1719
  receivedFrameSize: 3600,
1592
1720
  receivedHeight: 720,
@@ -1618,7 +1746,7 @@ describe('plugin-meetings', () => {
1618
1746
  framesDropped: 0,
1619
1747
  },
1620
1748
  h264CodecProfile: 'BP',
1621
- isActiveSpeaker: true,
1749
+ isActiveSpeaker: false,
1622
1750
  optimalFrameSize: 0,
1623
1751
  receivedFrameSize: 3600,
1624
1752
  receivedHeight: 720,
@@ -1633,187 +1761,387 @@ describe('plugin-meetings', () => {
1633
1761
  ]);
1634
1762
  });
1635
1763
 
1636
- it('has three streams for video senders for simulcast', async () => {
1637
- pc.getTransceiverStats = sinon.stub().resolves({
1638
- audio: {
1639
- senders: [fakeStats.audio.senders[0]],
1640
- receivers: [fakeStats.audio.receivers[0]],
1641
- },
1642
- video: {
1643
- senders: [
1644
- {
1645
- localTrackLabel: 'fake-camera',
1646
- report: [
1647
- {
1648
- type: 'outbound-rtp',
1649
- bytesSent: 1,
1650
- framesSent: 0,
1651
- packetsSent: 0,
1652
- },
1653
- {
1654
- type: 'outbound-rtp',
1655
- bytesSent: 0,
1656
- framesSent: 0,
1657
- packetsSent: 0,
1658
- },
1659
- {
1660
- type: 'outbound-rtp',
1661
- bytesSent: 1000,
1662
- framesSent: 1,
1663
- packetsSent: 1,
1664
- },
1665
- {
1666
- type: 'remote-inbound-rtp',
1667
- packetsLost: 0,
1668
- },
1669
- {
1670
- type: 'candidate-pair',
1671
- state: 'succeeded',
1672
- localCandidateId: 'fake-candidate-id',
1673
- },
1674
- {
1675
- type: 'candidate-pair',
1676
- state: 'failed',
1677
- localCandidateId: 'bad-candidate-id',
1678
- },
1679
- {
1680
- type: 'local-candidate',
1681
- id: 'fake-candidate-id',
1682
- protocol: 'tcp',
1683
- },
1684
- ],
1685
- },
1686
- ],
1687
- receivers: [fakeStats.video.receivers[0]],
1688
- },
1689
- screenShareAudio: {
1690
- senders: [fakeStats.audio.senders[0]],
1691
- receivers: [fakeStats.audio.receivers[0]],
1692
- },
1693
- screenShareVideo: {
1694
- senders: [fakeStats.video.senders[0]],
1695
- receivers: [fakeStats.video.receivers[0]],
1696
- },
1697
- });
1698
-
1699
- await startStatsAnalyzer({expected: {receiveVideo: true}});
1764
+ describe('stream count for simulcast', async () => {
1765
+ it('has three streams for video senders for simulcast', async () => {
1766
+ pc.getTransceiverStats = sinon.stub().resolves({
1767
+ audio: {
1768
+ senders: [fakeStats.audio.senders[0]],
1769
+ receivers: [fakeStats.audio.receivers[0]],
1770
+ },
1771
+ video: {
1772
+ senders: [
1773
+ {
1774
+ localTrackLabel: 'fake-camera',
1775
+ report: [
1776
+ {
1777
+ type: 'outbound-rtp',
1778
+ bytesSent: 1,
1779
+ framesSent: 0,
1780
+ packetsSent: 0,
1781
+ isRequested: true,
1782
+ },
1783
+ {
1784
+ type: 'outbound-rtp',
1785
+ bytesSent: 1,
1786
+ framesSent: 0,
1787
+ packetsSent: 1,
1788
+ isRequested: true,
1789
+ },
1790
+ {
1791
+ type: 'outbound-rtp',
1792
+ bytesSent: 1000,
1793
+ framesSent: 1,
1794
+ packetsSent: 0,
1795
+ isRequested: true,
1796
+ },
1797
+ {
1798
+ type: 'remote-inbound-rtp',
1799
+ packetsLost: 0,
1800
+ },
1801
+ {
1802
+ type: 'candidate-pair',
1803
+ state: 'succeeded',
1804
+ localCandidateId: 'fake-candidate-id',
1805
+ },
1806
+ {
1807
+ type: 'candidate-pair',
1808
+ state: 'failed',
1809
+ localCandidateId: 'bad-candidate-id',
1810
+ },
1811
+ {
1812
+ type: 'local-candidate',
1813
+ id: 'fake-candidate-id',
1814
+ protocol: 'tcp',
1815
+ },
1816
+ ],
1817
+ },
1818
+ ],
1819
+ receivers: [fakeStats.video.receivers[0]],
1820
+ },
1821
+ screenShareAudio: {
1822
+ senders: [fakeStats.audio.senders[0]],
1823
+ receivers: [fakeStats.audio.receivers[0]],
1824
+ },
1825
+ screenShareVideo: {
1826
+ senders: [fakeStats.video.senders[0]],
1827
+ receivers: [fakeStats.video.receivers[0]],
1828
+ },
1829
+ });
1700
1830
 
1701
- await progressTime();
1831
+ await startStatsAnalyzer({
1832
+ pc,
1833
+ statsAnalyzer,
1834
+ mediaStatus: {
1835
+ expected: {
1836
+ receiveVideo: true,
1837
+ },
1838
+ },
1839
+ });
1702
1840
 
1703
- assert.deepEqual(mqeData.videoTransmit[0].streams, [
1704
- {
1705
- common: {
1706
- codec: 'H264',
1707
- csi: [],
1708
- duplicateSsci: 0,
1841
+ await progressTime();
1842
+
1843
+ assert.deepEqual(mqeData.videoTransmit[0].streams, [
1844
+ {
1845
+ common: {
1846
+ codec: 'H264',
1847
+ csi: [],
1848
+ duplicateSsci: 0,
1849
+ requestedBitrate: 0,
1850
+ requestedFrames: 0,
1851
+ rtpPackets: 0,
1852
+ ssci: 0,
1853
+ transmittedBitrate: 0.13333333333333333,
1854
+ transmittedFrameRate: 0,
1855
+ },
1856
+ h264CodecProfile: 'BP',
1857
+ isAvatar: false,
1858
+ isHardwareEncoded: false,
1859
+ localConfigurationChanges: 2,
1860
+ maxFrameQp: 0,
1861
+ maxNoiseLevel: 0,
1862
+ minRegionQp: 0,
1863
+ remoteConfigurationChanges: 0,
1864
+ requestedFrameSize: 0,
1865
+ requestedKeyFrames: 0,
1866
+ transmittedFrameSize: 0,
1867
+ transmittedHeight: 0,
1868
+ transmittedKeyFrames: 0,
1869
+ transmittedKeyFramesClient: 0,
1870
+ transmittedKeyFramesConfigurationChange: 0,
1871
+ transmittedKeyFramesFeedback: 0,
1872
+ transmittedKeyFramesLocalDrop: 0,
1873
+ transmittedKeyFramesOtherLayer: 0,
1874
+ transmittedKeyFramesPeriodic: 0,
1875
+ transmittedKeyFramesSceneChange: 0,
1876
+ transmittedKeyFramesStartup: 0,
1877
+ transmittedKeyFramesUnknown: 0,
1878
+ transmittedWidth: 0,
1709
1879
  requestedBitrate: 0,
1710
- requestedFrames: 0,
1711
- rtpPackets: 0,
1712
- ssci: 0,
1713
- transmittedBitrate: 0.13333333333333333,
1714
- transmittedFrameRate: 0
1715
1880
  },
1716
- h264CodecProfile: 'BP',
1717
- isAvatar: false,
1718
- isHardwareEncoded: false,
1719
- localConfigurationChanges: 2,
1720
- maxFrameQp: 0,
1721
- maxNoiseLevel: 0,
1722
- minRegionQp: 0,
1723
- remoteConfigurationChanges: 0,
1724
- requestedFrameSize: 0,
1725
- requestedKeyFrames: 0,
1726
- transmittedFrameSize: 0,
1727
- transmittedHeight: 0,
1728
- transmittedKeyFrames: 0,
1729
- transmittedKeyFramesClient: 0,
1730
- transmittedKeyFramesConfigurationChange: 0,
1731
- transmittedKeyFramesFeedback: 0,
1732
- transmittedKeyFramesLocalDrop: 0,
1733
- transmittedKeyFramesOtherLayer: 0,
1734
- transmittedKeyFramesPeriodic: 0,
1735
- transmittedKeyFramesSceneChange: 0,
1736
- transmittedKeyFramesStartup: 0,
1737
- transmittedKeyFramesUnknown: 0,
1738
- transmittedWidth: 0,
1739
- requestedBitrate: 0,
1740
- },
1741
- {
1742
- common: {
1743
- codec: 'H264',
1744
- csi: [],
1745
- duplicateSsci: 0,
1881
+ {
1882
+ common: {
1883
+ codec: 'H264',
1884
+ csi: [],
1885
+ duplicateSsci: 0,
1886
+ requestedBitrate: 0,
1887
+ requestedFrames: 0,
1888
+ rtpPackets: 1,
1889
+ ssci: 0,
1890
+ transmittedBitrate: 0.13333333333333333,
1891
+ transmittedFrameRate: 0,
1892
+ },
1893
+ h264CodecProfile: 'BP',
1894
+ isAvatar: false,
1895
+ isHardwareEncoded: false,
1896
+ localConfigurationChanges: 2,
1897
+ maxFrameQp: 0,
1898
+ maxNoiseLevel: 0,
1899
+ minRegionQp: 0,
1900
+ remoteConfigurationChanges: 0,
1901
+ requestedFrameSize: 0,
1902
+ requestedKeyFrames: 0,
1903
+ transmittedFrameSize: 0,
1904
+ transmittedHeight: 0,
1905
+ transmittedKeyFrames: 0,
1906
+ transmittedKeyFramesClient: 0,
1907
+ transmittedKeyFramesConfigurationChange: 0,
1908
+ transmittedKeyFramesFeedback: 0,
1909
+ transmittedKeyFramesLocalDrop: 0,
1910
+ transmittedKeyFramesOtherLayer: 0,
1911
+ transmittedKeyFramesPeriodic: 0,
1912
+ transmittedKeyFramesSceneChange: 0,
1913
+ transmittedKeyFramesStartup: 0,
1914
+ transmittedKeyFramesUnknown: 0,
1915
+ transmittedWidth: 0,
1746
1916
  requestedBitrate: 0,
1747
- requestedFrames: 0,
1748
- rtpPackets: 0,
1749
- ssci: 0,
1750
- transmittedBitrate: 0,
1751
- transmittedFrameRate: 0,
1752
1917
  },
1753
- h264CodecProfile: 'BP',
1754
- isAvatar: false,
1755
- isHardwareEncoded: false,
1756
- localConfigurationChanges: 2,
1757
- maxFrameQp: 0,
1758
- maxNoiseLevel: 0,
1759
- minRegionQp: 0,
1760
- remoteConfigurationChanges: 0,
1761
- requestedFrameSize: 0,
1762
- requestedKeyFrames: 0,
1763
- transmittedFrameSize: 0,
1764
- transmittedHeight: 0,
1765
- transmittedKeyFrames: 0,
1766
- transmittedKeyFramesClient: 0,
1767
- transmittedKeyFramesConfigurationChange: 0,
1768
- transmittedKeyFramesFeedback: 0,
1769
- transmittedKeyFramesLocalDrop: 0,
1770
- transmittedKeyFramesOtherLayer: 0,
1771
- transmittedKeyFramesPeriodic: 0,
1772
- transmittedKeyFramesSceneChange: 0,
1773
- transmittedKeyFramesStartup: 0,
1774
- transmittedKeyFramesUnknown: 0,
1775
- transmittedWidth: 0,
1776
- requestedBitrate: 0,
1777
- },
1778
- {
1779
- common: {
1780
- codec: 'H264',
1781
- csi: [],
1782
- duplicateSsci: 0,
1918
+ {
1919
+ common: {
1920
+ codec: 'H264',
1921
+ csi: [],
1922
+ duplicateSsci: 0,
1923
+ requestedBitrate: 0,
1924
+ requestedFrames: 0,
1925
+ rtpPackets: 0,
1926
+ ssci: 0,
1927
+ transmittedBitrate: 133.33333333333334,
1928
+ transmittedFrameRate: 0,
1929
+ },
1930
+ h264CodecProfile: 'BP',
1931
+ isAvatar: false,
1932
+ isHardwareEncoded: false,
1933
+ localConfigurationChanges: 2,
1934
+ maxFrameQp: 0,
1935
+ maxNoiseLevel: 0,
1936
+ minRegionQp: 0,
1937
+ remoteConfigurationChanges: 0,
1938
+ requestedFrameSize: 0,
1939
+ requestedKeyFrames: 0,
1940
+ transmittedFrameSize: 0,
1941
+ transmittedHeight: 0,
1942
+ transmittedKeyFrames: 0,
1943
+ transmittedKeyFramesClient: 0,
1944
+ transmittedKeyFramesConfigurationChange: 0,
1945
+ transmittedKeyFramesFeedback: 0,
1946
+ transmittedKeyFramesLocalDrop: 0,
1947
+ transmittedKeyFramesOtherLayer: 0,
1948
+ transmittedKeyFramesPeriodic: 0,
1949
+ transmittedKeyFramesSceneChange: 0,
1950
+ transmittedKeyFramesStartup: 0,
1951
+ transmittedKeyFramesUnknown: 0,
1952
+ transmittedWidth: 0,
1783
1953
  requestedBitrate: 0,
1784
- requestedFrames: 0,
1785
- rtpPackets: 1,
1786
- ssci: 0,
1787
- transmittedBitrate: 133.33333333333334,
1788
- transmittedFrameRate: 0,
1789
1954
  },
1790
- h264CodecProfile: 'BP',
1791
- isAvatar: false,
1792
- isHardwareEncoded: false,
1793
- localConfigurationChanges: 2,
1794
- maxFrameQp: 0,
1795
- maxNoiseLevel: 0,
1796
- minRegionQp: 0,
1797
- remoteConfigurationChanges: 0,
1798
- requestedFrameSize: 0,
1799
- requestedKeyFrames: 0,
1800
- transmittedFrameSize: 0,
1801
- transmittedHeight: 0,
1802
- transmittedKeyFrames: 0,
1803
- transmittedKeyFramesClient: 0,
1804
- transmittedKeyFramesConfigurationChange: 0,
1805
- transmittedKeyFramesFeedback: 0,
1806
- transmittedKeyFramesLocalDrop: 0,
1807
- transmittedKeyFramesOtherLayer: 0,
1808
- transmittedKeyFramesPeriodic: 0,
1809
- transmittedKeyFramesSceneChange: 0,
1810
- transmittedKeyFramesStartup: 0,
1811
- transmittedKeyFramesUnknown: 0,
1812
- transmittedWidth: 0,
1813
- requestedBitrate: 0,
1955
+ ]);
1956
+ });
1957
+ });
1958
+ describe('active speaker status emission', async () => {
1959
+ beforeEach(async () => {
1960
+ await startStatsAnalyzer({pc, statsAnalyzer});
1961
+ performance.timeOrigin = 1;
1962
+ });
1963
+
1964
+ it('reports active speaker as true when the participant has been speaking', async () => {
1965
+ fakeStats.video.receivers[0].report[0].isActiveSpeaker = true;
1966
+ await progressTime(5 * MQA_INTERVAL);
1967
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].isActiveSpeaker, true);
1968
+ });
1969
+
1970
+ it('reports active speaker as false when the participant has not spoken', async () => {
1971
+ fakeStats.video.receivers[0].report[0].isActiveSpeaker = false;
1972
+ await progressTime(5 * MQA_INTERVAL);
1973
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].isActiveSpeaker, false);
1974
+ });
1975
+
1976
+ it('defaults to false when active speaker status is indeterminate', async () => {
1977
+ fakeStats.video.receivers[0].report[0].isActiveSpeaker = undefined;
1978
+ await progressTime(MQA_INTERVAL);
1979
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].isActiveSpeaker, false);
1980
+ });
1981
+
1982
+ it('updates active speaker to true following a recent status change to speaking', async () => {
1983
+ fakeStats.video.receivers[0].report[0].isActiveSpeaker = false;
1984
+ fakeStats.video.receivers[0].report[0].lastActiveSpeakerUpdateTimestamp = performance.timeOrigin + performance.now() + (30 * 1000);
1985
+ await progressTime(MQA_INTERVAL);
1986
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].isActiveSpeaker, true);
1987
+ await progressTime(MQA_INTERVAL);
1988
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].isActiveSpeaker, false);
1989
+ });
1990
+ });
1991
+ describe('sends streams according to their is requested flag', async () => {
1992
+
1993
+ beforeEach(async () => {
1994
+ performance.timeOrigin = 0;
1995
+ await startStatsAnalyzer({pc, statsAnalyzer});
1996
+ });
1997
+
1998
+ it('should send a stream if it is requested', async () => {
1999
+ fakeStats.audio.senders[0].report[0].isRequested = true;
2000
+ await progressTime(MQA_INTERVAL);
2001
+ assert.strictEqual(mqeData.audioTransmit[0].streams.length, 1);
2002
+ });
2003
+
2004
+ it('should not sent a stream if its is requested flag is undefined', async () => {
2005
+ fakeStats.audio.senders[0].report[0].isRequested = undefined;
2006
+ await progressTime(MQA_INTERVAL);
2007
+ assert.strictEqual(mqeData.audioTransmit[0].streams.length, 0);
2008
+ });
2009
+
2010
+ it('should not send a stream if it is not requested', async () => {
2011
+ fakeStats.audio.receivers[0].report[0].isRequested = false;
2012
+ await progressTime(MQA_INTERVAL);
2013
+ assert.strictEqual(mqeData.audioReceive[0].streams.length, 0);
2014
+ });
2015
+
2016
+ it('should send the stream if it was recently requested', async () => {
2017
+ fakeStats.audio.receivers[0].report[0].lastRequestedUpdateTimestamp = performance.timeOrigin + performance.now() + (30 * 1000);
2018
+ fakeStats.audio.receivers[0].report[0].isRequested = false;
2019
+ await progressTime(MQA_INTERVAL);
2020
+ assert.strictEqual(mqeData.audioReceive[0].streams.length, 1);
2021
+ await progressTime(MQA_INTERVAL);
2022
+ assert.strictEqual(mqeData.audioReceive[0].streams.length, 0);
2023
+ });
2024
+ });
2025
+
2026
+ describe('window and screen size emission', async () => {
2027
+ beforeEach(async () => {
2028
+ await startStatsAnalyzer({pc, statsAnalyzer});
2029
+ });
2030
+
2031
+ it('should record the screen size from window.screen properties', async () => {
2032
+ sinon.stub(window.screen, 'width').get(() => 1280);
2033
+ sinon.stub(window.screen, 'height').get(() => 720);
2034
+ await progressTime(MQA_INTERVAL);
2035
+ assert.strictEqual(mqeData.intervalMetadata.screenWidth, 1280);
2036
+ assert.strictEqual(mqeData.intervalMetadata.screenHeight, 720);
2037
+ assert.strictEqual(mqeData.intervalMetadata.screenResolution, 3600);
2038
+ });
2039
+
2040
+ it('should record the initial app window size from window properties', async () => {
2041
+ sinon.stub(window, 'innerWidth').get(() => 720);
2042
+ sinon.stub(window, 'innerHeight').get(() => 360);
2043
+ await progressTime(MQA_INTERVAL);
2044
+ assert.strictEqual(mqeData.intervalMetadata.appWindowWidth, 720);
2045
+ assert.strictEqual(mqeData.intervalMetadata.appWindowHeight, 360);
2046
+ assert.strictEqual(mqeData.intervalMetadata.appWindowSize, 1013);
2047
+
2048
+ sinon.stub(window, 'innerWidth').get(() => 1080);
2049
+ sinon.stub(window, 'innerHeight').get(() => 720);
2050
+ await progressTime(MQA_INTERVAL);
2051
+ assert.strictEqual(mqeData.intervalMetadata.appWindowWidth, 1080);
2052
+ assert.strictEqual(mqeData.intervalMetadata.appWindowHeight, 720);
2053
+ assert.strictEqual(mqeData.intervalMetadata.appWindowSize, 3038);
2054
+ });
2055
+ });
2056
+
2057
+ describe('sends multistreamEnabled', async () => {
2058
+ it('false if StatsAnalyzer initialized with default value for isMultistream', async () => {
2059
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
2060
+
2061
+ await progressTime();
2062
+
2063
+ for (const data of [
2064
+ mqeData.audioTransmit,
2065
+ mqeData.audioReceive,
2066
+ mqeData.videoTransmit,
2067
+ mqeData.videoReceive,
2068
+ ]) {
2069
+ assert.strictEqual(data[0].common.common.multistreamEnabled, false);
1814
2070
  }
1815
- ]);
2071
+ });
2072
+
2073
+ it('false if StatsAnalyzer initialized with false', async () => {
2074
+ statsAnalyzer = new StatsAnalyzer({
2075
+ config: initialConfig,
2076
+ receiveSlotCallback: () => receiveSlot,
2077
+ networkQualityMonitor,
2078
+ isMultistream: false,
2079
+ });
2080
+ registerStatsAnalyzerEvents(statsAnalyzer);
2081
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: false}}});
2082
+
2083
+ await progressTime();
2084
+
2085
+ for (const data of [
2086
+ mqeData.audioTransmit,
2087
+ mqeData.audioReceive,
2088
+ mqeData.videoTransmit,
2089
+ mqeData.videoReceive,
2090
+ ]) {
2091
+ assert.strictEqual(data[0].common.common.multistreamEnabled, false);
2092
+ }
2093
+ });
2094
+
2095
+ it('true if StatsAnalyzer initialized with multistream', async () => {
2096
+ statsAnalyzer = new StatsAnalyzer({
2097
+ config: initialConfig,
2098
+ receiveSlotCallback: () => receiveSlot,
2099
+ networkQualityMonitor,
2100
+ isMultistream: true,
2101
+ });
2102
+ registerStatsAnalyzerEvents(statsAnalyzer);
2103
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
2104
+
2105
+ await progressTime();
2106
+
2107
+ for (const data of [
2108
+ mqeData.audioTransmit,
2109
+ mqeData.audioReceive,
2110
+ mqeData.videoTransmit,
2111
+ mqeData.videoReceive,
2112
+ ]) {
2113
+ assert.strictEqual(data[0].common.common.multistreamEnabled, true);
2114
+ }
2115
+ });
1816
2116
  });
1817
- });
2117
+
2118
+ describe('CPU Information Reporting', async () => {
2119
+ let getNumLogicalCoresStub;
2120
+
2121
+ beforeEach(async () => {
2122
+ getNumLogicalCoresStub = sinon.stub(CpuInfo, 'getNumLogicalCores');
2123
+ });
2124
+
2125
+ afterEach(() => {
2126
+ getNumLogicalCoresStub.restore();
2127
+ });
2128
+
2129
+ it('reports 1 of logical CPU cores when not available', async () => {
2130
+ getNumLogicalCoresStub.returns(undefined);
2131
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
2132
+
2133
+ await progressTime();
2134
+ assert.equal(mqeData.intervalMetadata.cpuInfo.numberOfCores, 1);
2135
+ });
2136
+
2137
+ it('reports the number of logical CPU cores', async () => {
2138
+ getNumLogicalCoresStub.returns(12);
2139
+ await startStatsAnalyzer({pc, statsAnalyzer, mediaStatus: {expected: {receiveVideo: true}}});
2140
+
2141
+ await progressTime();
2142
+ assert.equal(mqeData.intervalMetadata.cpuInfo.numberOfCores, 12);
2143
+ });
2144
+ });
2145
+ })
1818
2146
  });
1819
2147
  });