@webex/plugin-meetings 2.60.1-next.7 → 2.60.1-next.9

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 (54) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.d.ts +12 -2
  5. package/dist/constants.js +15 -5
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/locus-info/index.d.ts +1 -1
  10. package/dist/locus-info/index.js +8 -8
  11. package/dist/locus-info/index.js.map +1 -1
  12. package/dist/meeting/index.d.ts +62 -18
  13. package/dist/meeting/index.js +679 -568
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +25 -18
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.d.ts +16 -0
  18. package/dist/meeting/util.js +71 -0
  19. package/dist/meeting/util.js.map +1 -1
  20. package/dist/meetings/index.d.ts +25 -3
  21. package/dist/meetings/index.js +83 -32
  22. package/dist/meetings/index.js.map +1 -1
  23. package/dist/reachability/index.js +11 -6
  24. package/dist/reachability/index.js.map +1 -1
  25. package/dist/reconnection-manager/index.js +3 -1
  26. package/dist/reconnection-manager/index.js.map +1 -1
  27. package/dist/roap/index.js +50 -54
  28. package/dist/roap/index.js.map +1 -1
  29. package/dist/statsAnalyzer/index.js +1 -1
  30. package/dist/statsAnalyzer/index.js.map +1 -1
  31. package/dist/statsAnalyzer/mqaUtil.js +13 -10
  32. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  33. package/dist/webinar/index.js +1 -1
  34. package/package.json +22 -22
  35. package/src/constants.ts +13 -2
  36. package/src/locus-info/index.ts +13 -12
  37. package/src/meeting/index.ts +215 -116
  38. package/src/meeting/request.ts +7 -0
  39. package/src/meeting/util.ts +97 -0
  40. package/src/meetings/index.ts +59 -18
  41. package/src/reachability/index.ts +7 -4
  42. package/src/reconnection-manager/index.ts +1 -1
  43. package/src/roap/index.ts +49 -51
  44. package/src/statsAnalyzer/index.ts +2 -2
  45. package/src/statsAnalyzer/mqaUtil.ts +15 -14
  46. package/test/unit/spec/locus-info/index.js +53 -5
  47. package/test/unit/spec/meeting/index.js +1792 -1139
  48. package/test/unit/spec/meeting/request.js +22 -12
  49. package/test/unit/spec/meeting/utils.js +93 -0
  50. package/test/unit/spec/meetings/index.js +180 -21
  51. package/test/unit/spec/reachability/index.ts +2 -1
  52. package/test/unit/spec/reconnection-manager/index.js +1 -0
  53. package/test/unit/spec/roap/index.ts +28 -42
  54. package/test/unit/spec/stats-analyzer/index.js +415 -30
@@ -7,7 +7,9 @@ import {ConnectionState} from '@webex/internal-media-core';
7
7
  import {StatsAnalyzer, EVENTS} from '../../../../src/statsAnalyzer';
8
8
  import NetworkQualityMonitor from '../../../../src/networkQualityMonitor';
9
9
  import testUtils from '../../../utils/testUtils';
10
- import {MEDIA_DEVICES, _UNKNOWN_} from '@webex/plugin-meetings/src/constants';
10
+ import {MEDIA_DEVICES, MQA_INTERVAL, _UNKNOWN_} from '@webex/plugin-meetings/src/constants';
11
+ import LoggerProxy from '../../../../src/common/logs/logger-proxy';
12
+ import LoggerConfig from '../../../../src/common/logs/logger-config';
11
13
 
12
14
  const {assert} = chai;
13
15
 
@@ -16,6 +18,156 @@ sinon.assert.expose(chai.assert, {prefix: ''});
16
18
 
17
19
  describe('plugin-meetings', () => {
18
20
  describe('StatsAnalyzer', () => {
21
+ describe('parseStatsResult', () => {
22
+ const sandbox = sinon.createSandbox();
23
+ let statsAnalyzer;
24
+
25
+ const initialConfig = {};
26
+ const defaultStats = {};
27
+
28
+ beforeEach(() => {
29
+ const networkQualityMonitor = new NetworkQualityMonitor(initialConfig);
30
+
31
+ statsAnalyzer = new StatsAnalyzer(
32
+ initialConfig,
33
+ () => ({}),
34
+ networkQualityMonitor,
35
+ defaultStats
36
+ );
37
+ });
38
+
39
+ afterEach(() => {
40
+ sandbox.reset();
41
+ });
42
+
43
+ it('should call processOutboundRTPResult', () => {
44
+ const calledSpy = sandbox.spy(statsAnalyzer, 'processOutboundRTPResult');
45
+ statsAnalyzer.parseGetStatsResult({ type: 'outbound-rtp' }, 'video-send');
46
+ assert(calledSpy.calledOnce);
47
+ });
48
+
49
+ it('should call processInboundRTPResult', () => {
50
+ const calledSpy = sandbox.spy(statsAnalyzer, 'processInboundRTPResult');
51
+ statsAnalyzer.parseGetStatsResult({ type: 'inbound-rtp' }, 'video-recv');
52
+ assert(calledSpy.calledOnce);
53
+ });
54
+
55
+ it('should call compareSentAndReceived', () => {
56
+ const calledSpy = sandbox.spy(statsAnalyzer, 'compareSentAndReceived');
57
+ statsAnalyzer.parseGetStatsResult({ type: 'remote-outbound-rtp' }, 'video-send');
58
+ assert(calledSpy.calledOnce);
59
+ });
60
+
61
+ it('should call parseCandidate', () => {
62
+ const calledSpy = sandbox.spy(statsAnalyzer, 'parseCandidate');
63
+ statsAnalyzer.parseGetStatsResult({ type: 'local-candidate' }, 'video-send');
64
+ assert(calledSpy.calledOnce);
65
+ });
66
+
67
+ it('processOutboundRTPResult should create the correct stats results', () => {
68
+ // establish the `statsResults` object.
69
+ statsAnalyzer.parseGetStatsResult({ type: 'none' }, 'audio-send', true);
70
+
71
+ statsAnalyzer.processOutboundRTPResult({
72
+ bytesSent: 50000,
73
+ codecId: "RTCCodec_1_Outbound_111",
74
+ headerBytesSent: 25000,
75
+ id: "RTCOutboundRTPAudioStream_123456789",
76
+ kind: "audio",
77
+ mediaSourceId: "RTCAudioSource_2",
78
+ mediaType: "audio",
79
+ nackCount: 1,
80
+ packetsSent: 3600,
81
+ remoteId: "RTCRemoteInboundRtpAudioStream_123456789",
82
+ retransmittedBytesSent: 100,
83
+ retransmittedPacketsSent: 2,
84
+ ssrc: 123456789,
85
+ targetBitrate: 256000,
86
+ timestamp: 1707341489336,
87
+ trackId: "RTCMediaStreamTrack_sender_2",
88
+ transportId: "RTCTransport_0_1",
89
+ type: "outbound-rtp",
90
+ }, 'audio-send', true);
91
+
92
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.headerBytesSent, 25000);
93
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.totalBytesSent, 50000);
94
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.totalNackCount, 1);
95
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.totalPacketsSent, 3600)
96
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.retransmittedPacketsSent, 2);
97
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.retransmittedBytesSent, 100);
98
+ });
99
+
100
+ it('processInboundRTPResult should create the correct stats results', () => {
101
+ // establish the `statsResults` object.
102
+ statsAnalyzer.parseGetStatsResult({ type: 'none' }, 'audio-recv-1', false);
103
+
104
+ statsAnalyzer.processInboundRTPResult({
105
+ audioLevel: 0,
106
+ bytesReceived: 509,
107
+ codecId: "RTCCodec_6_Inbound_111",
108
+ concealedSamples: 200000,
109
+ concealmentEvents: 13,
110
+ fecPacketsDiscarded: 1,
111
+ fecPacketsReceived: 1,
112
+ headerBytesReceived: 250,
113
+ id: "RTCInboundRTPAudioStream_123456789",
114
+ insertedSamplesForDeceleration: 0,
115
+ jitter: 0.012,
116
+ jitterBufferDelay: 1000,
117
+ jitterBufferEmittedCount: 10000,
118
+ kind: "audio",
119
+ lastPacketReceivedTimestamp: 1707341488529,
120
+ mediaType: "audio",
121
+ packetsDiscarded: 0,
122
+ packetsLost: 0,
123
+ packetsReceived: 12,
124
+ remoteId: "RTCRemoteOutboundRTPAudioStream_123456789",
125
+ removedSamplesForAcceleration: 0,
126
+ silentConcealedSamples: 200000,
127
+ ssrc: 123456789,
128
+ timestamp: 1707341489419,
129
+ totalAudioEnergy: 133,
130
+ totalSamplesDuration: 7,
131
+ totalSamplesReceived: 300000,
132
+ trackId: "RTCMediaStreamTrack_receiver_76",
133
+ transportId: "RTCTransport_0_1",
134
+ type: "inbound-rtp",
135
+ }, 'audio-recv-1', false);
136
+
137
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalPacketsReceived, 12);
138
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.fecPacketsDiscarded, 1);
139
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.fecPacketsReceived, 1);
140
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalBytesReceived, 509);
141
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.headerBytesReceived, 250);
142
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.audioLevel, 0);
143
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalAudioEnergy, 133);
144
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalSamplesReceived, 300000);
145
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.totalSamplesDecoded, 0);
146
+ assert.strictEqual(statsAnalyzer.statsResults['audio-recv-1'].recv.concealedSamples, 200000);
147
+ });
148
+
149
+ it('parseAudioSource should create the correct stats results', () => {
150
+ // establish the `statsResults` object.
151
+ statsAnalyzer.parseGetStatsResult({ type: 'none' }, 'audio-send', true);
152
+
153
+ statsAnalyzer.parseAudioSource({
154
+ audioLevel: 0.03,
155
+ echoReturnLoss: -30,
156
+ echoReturnLossEnhancement: 0.17,
157
+ id: "RTCAudioSource_2",
158
+ kind: "audio",
159
+ timestamp: 1707341488160.012,
160
+ totalAudioEnergy: 0.001,
161
+ totalSamplesDuration: 4.5,
162
+ trackIdentifier: "2207e5bf-c595-4301-93f7-283994d8143f",
163
+ type: "media-source",
164
+ }, 'audio-send', true);
165
+
166
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.audioLevel, 0.03);
167
+ assert.strictEqual(statsAnalyzer.statsResults['audio-send'].send.totalAudioEnergy, 0.001);
168
+ });
169
+ });
170
+
19
171
  describe('compareSentAndReceived()', () => {
20
172
  let statsAnalyzer;
21
173
  let sandBoxSpy;
@@ -91,6 +243,7 @@ describe('plugin-meetings', () => {
91
243
  let networkQualityMonitor;
92
244
  let statsAnalyzer;
93
245
  let mqeData;
246
+ let loggerSpy;
94
247
 
95
248
  let receivedEventsData = {
96
249
  local: {},
@@ -103,6 +256,8 @@ describe('plugin-meetings', () => {
103
256
 
104
257
  let fakeStats;
105
258
 
259
+ const sandbox = sinon.createSandbox();
260
+
106
261
  const resetReceivedEvents = () => {
107
262
  receivedEventsData = {
108
263
  local: {},
@@ -110,6 +265,12 @@ describe('plugin-meetings', () => {
110
265
  };
111
266
  };
112
267
 
268
+ before(() => {
269
+ LoggerConfig.set({ enable: false });
270
+ LoggerProxy.set();
271
+ loggerSpy = sandbox.spy(LoggerProxy.logger, 'info');
272
+ });
273
+
113
274
  beforeEach(() => {
114
275
  clock = sinon.useFakeTimers();
115
276
 
@@ -124,8 +285,12 @@ describe('plugin-meetings', () => {
124
285
  report: [
125
286
  {
126
287
  type: 'outbound-rtp',
127
- packetsSent: 0,
128
288
  bytesSent: 1,
289
+ packetsSent: 0,
290
+ },
291
+ {
292
+ type: 'remote-inbound-rtp',
293
+ packetsLost: 0,
129
294
  },
130
295
  {
131
296
  type: 'candidate-pair',
@@ -150,8 +315,14 @@ describe('plugin-meetings', () => {
150
315
  report: [
151
316
  {
152
317
  type: 'inbound-rtp',
153
- packetsReceived: 0,
154
318
  bytesReceived: 1,
319
+ fecPacketsDiscarded: 0,
320
+ fecPacketsReceived: 0,
321
+ packetsLost: 0,
322
+ packetsReceived: 0,
323
+ },
324
+ {
325
+ type: 'remote-outbound-rtp',
155
326
  },
156
327
  {
157
328
  type: 'candidate-pair',
@@ -179,8 +350,13 @@ describe('plugin-meetings', () => {
179
350
  report: [
180
351
  {
181
352
  type: 'outbound-rtp',
182
- framesSent: 1500,
183
353
  bytesSent: 1,
354
+ framesSent: 0,
355
+ packetsSent: 0,
356
+ },
357
+ {
358
+ type: 'remote-inbound-rtp',
359
+ packetsLost: 0,
184
360
  },
185
361
  {
186
362
  type: 'candidate-pair',
@@ -205,11 +381,16 @@ describe('plugin-meetings', () => {
205
381
  report: [
206
382
  {
207
383
  type: 'inbound-rtp',
208
- framesDecoded: 0,
209
384
  bytesReceived: 1,
210
385
  frameHeight: 720,
211
386
  frameWidth: 1280,
212
- framesReceived: 1500,
387
+ framesDecoded: 0,
388
+ framesReceived: 0,
389
+ packetsLost: 0,
390
+ packetsReceived: 0,
391
+ },
392
+ {
393
+ type: 'remote-outbound-rtp',
213
394
  },
214
395
  {
215
396
  type: 'candidate-pair',
@@ -276,6 +457,7 @@ describe('plugin-meetings', () => {
276
457
  });
277
458
 
278
459
  afterEach(() => {
460
+ sandbox.reset();
279
461
  clock.restore();
280
462
  });
281
463
 
@@ -286,21 +468,27 @@ describe('plugin-meetings', () => {
286
468
  await testUtils.flushPromises();
287
469
  };
288
470
 
289
- const mergeProperties = (target, properties, keyValue = 'fake-candidate-id', matchKey= 'type', matchValue = 'local-candidate') => {
471
+ const mergeProperties = (
472
+ target,
473
+ properties,
474
+ keyValue = 'fake-candidate-id',
475
+ matchKey = 'type',
476
+ matchValue = 'local-candidate'
477
+ ) => {
290
478
  for (let key in target) {
291
- if (target.hasOwnProperty(key)) {
292
- if (typeof target[key] === 'object') {
293
- mergeProperties(target[key], properties, keyValue, matchKey, matchValue);
294
- }
295
- if (key === 'id' && target[key] === keyValue && target[matchKey] === matchValue) {
296
- Object.assign(target, properties);
297
- }
479
+ if (target.hasOwnProperty(key)) {
480
+ if (typeof target[key] === 'object') {
481
+ mergeProperties(target[key], properties, keyValue, matchKey, matchValue);
482
+ }
483
+ if (key === 'id' && target[key] === keyValue && target[matchKey] === matchValue) {
484
+ Object.assign(target, properties);
298
485
  }
486
+ }
299
487
  }
300
- }
488
+ };
301
489
 
302
- const progressTime = async () => {
303
- await clock.tickAsync(initialConfig.analyzerInterval);
490
+ const progressTime = async (time = initialConfig.analyzerInterval) => {
491
+ await clock.tickAsync(time);
304
492
  await testUtils.flushPromises();
305
493
  };
306
494
 
@@ -428,10 +616,10 @@ describe('plugin-meetings', () => {
428
616
  });
429
617
 
430
618
  it('emits the correct transportType in MEDIA_QUALITY events when using a TURN server', async () => {
431
- fakeStats.audio.senders[0].report[3].relayProtocol = 'tls';
432
- fakeStats.video.senders[0].report[3].relayProtocol = 'tls';
433
- fakeStats.audio.receivers[0].report[3].relayProtocol = 'tls';
434
- fakeStats.video.receivers[0].report[3].relayProtocol = 'tls';
619
+ fakeStats.audio.senders[0].report[4].relayProtocol = 'tls';
620
+ fakeStats.video.senders[0].report[4].relayProtocol = 'tls';
621
+ fakeStats.audio.receivers[0].report[4].relayProtocol = 'tls';
622
+ fakeStats.video.receivers[0].report[4].relayProtocol = 'tls';
435
623
 
436
624
  await startStatsAnalyzer({expected: {receiveVideo: true}});
437
625
 
@@ -478,14 +666,131 @@ describe('plugin-meetings', () => {
478
666
  );
479
667
  });
480
668
 
481
- it('emits the correct frameRate', async () => {
482
- await startStatsAnalyzer({expected: {receiveVideo: true}});
669
+ it('emits the correct transmittedFrameRate/receivedFrameRate', async () => {
670
+ it('at the start of the stats analyzer', async () => {
671
+ await startStatsAnalyzer();
672
+ assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.transmittedFrameRate, 0);
673
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 0);
674
+ });
483
675
 
484
- await progressTime();
485
- assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 25);
486
- fakeStats.video.receivers[0].framesReceived = 3000;
487
- await progressTime();
488
- assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 25);
676
+ it('after frames are sent and received', async () => {
677
+ fakeStats.video.senders[0].report[0].framesSent += 300;
678
+ fakeStats.video.receivers[0].report[0].framesReceived += 300;
679
+ await progressTime(MQA_INTERVAL);
680
+
681
+ // 300 frames in 60 seconds = 5 frames per second
682
+ assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.transmittedFrameRate, 5);
683
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].common.receivedFrameRate, 5);
684
+ });
685
+ });
686
+
687
+ it('emits the correct rtpPackets', async () => {
688
+ it('at the start of the stats analyzer', async () => {
689
+ await startStatsAnalyzer();
690
+ assert.strictEqual(mqeData.audioTransmit[0].common.rtpPackets, 0);
691
+ assert.strictEqual(mqeData.audioTransmit[0].streams[0].common.rtpPackets, 0);
692
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpPackets, 0);
693
+ assert.strictEqual(mqeData.audioReceive[0].streams[0].common.rtpPackets, 0);
694
+ assert.strictEqual(mqeData.videoTransmit[0].common.rtpPackets, 0);
695
+ assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.rtpPackets, 0);
696
+ assert.strictEqual(mqeData.videoReceive[0].common.rtpPackets, 0);
697
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].common.rtpPackets, 0);
698
+ });
699
+
700
+ it('after packets are sent', async () => {
701
+ fakeStats.audio.senders[0].report[0].packetsSent += 5;
702
+ fakeStats.video.senders[0].report[0].packetsSent += 5;
703
+ await progressTime(MQA_INTERVAL);
704
+
705
+ assert.strictEqual(mqeData.audioTransmit[0].common.rtpPackets, 5);
706
+ assert.strictEqual(mqeData.audioTransmit[0].streams[0].common.rtpPackets, 5);
707
+ assert.strictEqual(mqeData.videoTransmit[0].common.rtpPackets, 5);
708
+ assert.strictEqual(mqeData.videoTransmit[0].streams[0].common.rtpPackets, 5);
709
+ });
710
+
711
+ it('after packets are received', async () => {
712
+ fakeStats.audio.senders[0].report[0].packetsSent += 10;
713
+ fakeStats.video.senders[0].report[0].packetsSent += 10;
714
+ fakeStats.audio.receivers[0].report[0].packetsReceived += 10;
715
+ fakeStats.video.receivers[0].report[0].packetsReceived += 10;
716
+ await progressTime(MQA_INTERVAL);
717
+
718
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpPackets, 10);
719
+ assert.strictEqual(mqeData.audioReceive[0].streams[0].common.rtpPackets, 10);
720
+ assert.strictEqual(mqeData.videoReceive[0].common.rtpPackets, 10);
721
+ assert.strictEqual(mqeData.videoReceive[0].streams[0].common.rtpPackets, 10);
722
+ });
723
+ });
724
+
725
+ it('emits the correct fecPackets', async () => {
726
+ it('at the start of the stats analyzer', async () => {
727
+ await startStatsAnalyzer();
728
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 0);
729
+ });
730
+
731
+ it('after FEC packets are received', async () => {
732
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 5;
733
+ await progressTime(MQA_INTERVAL);
734
+
735
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 5);
736
+ });
737
+
738
+ it('after FEC packets are received and some FEC packets are discarded', async () => {
739
+ fakeStats.audio.receivers[0].report[0].fecPacketsReceived += 15;
740
+ fakeStats.audio.receivers[0].report[0].fecPacketsDiscarded += 5;
741
+ await progressTime(MQA_INTERVAL);
742
+
743
+ assert.strictEqual(mqeData.audioReceive[0].common.fecPackets, 10);
744
+ });
745
+ });
746
+
747
+ it('emits the correct mediaHopByHopLost/rtpHopByHopLost', async () => {
748
+ it('at the start of the stats analyzer', async () => {
749
+ await startStatsAnalyzer();
750
+ assert.strictEqual(mqeData.audioReceive[0].common.mediaHopByHopLost, 0);
751
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpHopByHopLost, 0);
752
+ assert.strictEqual(mqeData.videoReceive[0].common.mediaHopByHopLost, 0);
753
+ assert.strictEqual(mqeData.videoReceive[0].common.rtpHopByHopLost, 0);
754
+ });
755
+
756
+ it('after packets are lost', async () => {
757
+ fakeStats.audio.receivers[0].report[0].packetsLost += 5;
758
+ fakeStats.video.receivers[0].report[0].packetsLost += 5;
759
+ await progressTime(MQA_INTERVAL);
760
+
761
+ assert.strictEqual(mqeData.audioReceive[0].common.mediaHopByHopLost, 5);
762
+ assert.strictEqual(mqeData.audioReceive[0].common.rtpHopByHopLost, 5);
763
+ assert.strictEqual(mqeData.videoReceive[0].common.mediaHopByHopLost, 5);
764
+ assert.strictEqual(mqeData.videoReceive[0].common.rtpHopByHopLost, 5);
765
+ });
766
+ });
767
+
768
+ it('emits the correct remoteLossRate', async () => {
769
+ it('at the start of the stats analyzer', async () => {
770
+ await startStatsAnalyzer();
771
+ assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 0);
772
+ assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 0);
773
+ });
774
+
775
+ it('after packets are sent', async () => {
776
+ fakeStats.audio.senders[0].report[0].packetsSent += 100;
777
+ fakeStats.video.senders[0].report[0].packetsSent += 100;
778
+ await progressTime(MQA_INTERVAL);
779
+
780
+ assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 0);
781
+ assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 0);
782
+ });
783
+
784
+ it('after packets are sent and some packets are lost', async () => {
785
+ fakeStats.audio.senders[0].report[0].packetsSent += 200;
786
+ fakeStats.audio.senders[0].report[1].packetsLost += 10;
787
+ fakeStats.video.senders[0].report[0].packetsSent += 200;
788
+ fakeStats.video.senders[0].report[1].packetsLost += 10;
789
+ await progressTime(MQA_INTERVAL);
790
+
791
+ assert.strictEqual(mqeData.audioTransmit[0].common.remoteLossRate, 5);
792
+ assert.strictEqual(mqeData.videoTransmit[0].common.remoteLossRate, 5);
793
+ });
489
794
  });
490
795
 
491
796
  it('has the correct localIpAddress set when the candidateType is host', async () => {
@@ -503,7 +808,11 @@ describe('plugin-meetings', () => {
503
808
 
504
809
  await progressTime();
505
810
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
506
- mergeProperties(fakeStats, {relayProtocol: 'test', address: 'test2', candidateType: 'prflx'});
811
+ mergeProperties(fakeStats, {
812
+ relayProtocol: 'test',
813
+ address: 'test2',
814
+ candidateType: 'prflx',
815
+ });
507
816
  await progressTime();
508
817
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), 'test2');
509
818
  });
@@ -513,7 +822,11 @@ describe('plugin-meetings', () => {
513
822
 
514
823
  await progressTime();
515
824
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
516
- mergeProperties(fakeStats, {relatedAddress: 'relatedAddress', address: 'test2', candidateType: 'prflx'});
825
+ mergeProperties(fakeStats, {
826
+ relatedAddress: 'relatedAddress',
827
+ address: 'test2',
828
+ candidateType: 'prflx',
829
+ });
517
830
  await progressTime();
518
831
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), 'relatedAddress');
519
832
  });
@@ -527,6 +840,78 @@ describe('plugin-meetings', () => {
527
840
  await progressTime();
528
841
  assert.strictEqual(statsAnalyzer.getLocalIpAddress(), '');
529
842
  });
843
+
844
+ it('logs a message when audio send packets do not increase', async () => {
845
+ await startStatsAnalyzer({expected: {sendAudio: true}});
846
+
847
+ // don't increase the packets when time progresses.
848
+ await progressTime();
849
+
850
+ assert(loggerSpy.calledWith('StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent'));
851
+ });
852
+
853
+ it('does not log a message when audio send packets increase', async () => {
854
+ await startStatsAnalyzer({expected: {sendAudio: true}});
855
+
856
+ fakeStats.audio.senders[0].report[0].packetsSent += 5;
857
+ await progressTime();
858
+
859
+ assert(loggerSpy.neverCalledWith('StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent'));
860
+ });
861
+
862
+ it('logs a message when audio receive packets do not increase', async () => {
863
+ await startStatsAnalyzer({expected: {receiveAudio: true}});
864
+
865
+ // don't increase the packets when time progresses.
866
+ await progressTime();
867
+
868
+ assert(loggerSpy.calledWith('StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received'));
869
+ });
870
+
871
+ it('does not log a message when audio receive packets increase', async () => {
872
+ await startStatsAnalyzer({expected: {receiveAudio: true}});
873
+
874
+ fakeStats.audio.receivers[0].report[0].packetsReceived += 5;
875
+ await progressTime();
876
+
877
+ assert(loggerSpy.neverCalledWith('StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received'));
878
+ });
879
+
880
+ it('logs a message when video send packets do not increase', async () => {
881
+ await startStatsAnalyzer({expected: {sendVideo: true}});
882
+
883
+ // don't increase the packets when time progresses.
884
+ await progressTime();
885
+
886
+ assert(loggerSpy.calledWith('StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent'));
887
+ });
888
+
889
+ it('does not log a message when video send packets increase', async () => {
890
+ await startStatsAnalyzer({expected: {sendVideo: true}});
891
+
892
+ fakeStats.video.senders[0].report[0].packetsSent += 5;
893
+ await progressTime();
894
+
895
+ assert(loggerSpy.neverCalledWith('StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent'));
896
+ });
897
+
898
+ it('logs a message when video receive packets do not increase', async () => {
899
+ await startStatsAnalyzer({expected: {receiveVideo: true}});
900
+
901
+ // don't increase the packets when time progresses.
902
+ await progressTime();
903
+
904
+ assert(loggerSpy.calledWith('StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received'));
905
+ });
906
+
907
+ it('does not log a message when video receive packets increase', async () => {
908
+ await startStatsAnalyzer({expected: {receiveVideo: true}});
909
+
910
+ fakeStats.video.receivers[0].report[0].packetsReceived += 5;
911
+ await progressTime();
912
+
913
+ assert(loggerSpy.neverCalledWith('StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received'));
914
+ });
530
915
  });
531
916
  });
532
917
  });