@webex/plugin-meetings 2.59.2 → 2.59.3

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.
@@ -25,6 +25,8 @@ import {
25
25
 
26
26
  export const EVENTS = {
27
27
  MEDIA_QUALITY: 'MEDIA_QUALITY',
28
+ NO_FRAMES_SENT: 'NO_FRAMES_SENT',
29
+ NO_VIDEO_ENCODED: 'NO_VIDEO_ENCODED',
28
30
  LOCAL_MEDIA_STARTED: 'LOCAL_MEDIA_STARTED',
29
31
  LOCAL_MEDIA_STOPPED: 'LOCAL_MEDIA_STOPPED',
30
32
  REMOTE_MEDIA_STARTED: 'REMOTE_MEDIA_STARTED',
@@ -517,7 +519,7 @@ export class StatsAnalyzer extends EventsScope {
517
519
 
518
520
  if (currentValue - previousValue > 0) {
519
521
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STARTED : EVENTS.REMOTE_MEDIA_STARTED;
520
- } else if (currentValue === previousValue && currentValue > 0) {
522
+ } else if (currentValue === previousValue && currentValue >= 0) {
521
523
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STOPPED : EVENTS.REMOTE_MEDIA_STOPPED;
522
524
  }
523
525
 
@@ -632,14 +634,25 @@ export class StatsAnalyzer extends EventsScope {
632
634
  LoggerProxy.logger.info(
633
635
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
634
636
  );
635
- } else {
637
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
636
638
  if (
637
639
  currentStats.framesEncoded === previousStats.framesEncoded ||
638
640
  currentStats.framesEncoded === 0
639
641
  ) {
642
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
640
643
  LoggerProxy.logger.info(
641
644
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames Encoded`
642
645
  );
646
+ this.emit(
647
+ {
648
+ file: 'statsAnalyzer',
649
+ function: 'compareLastStatsResult',
650
+ },
651
+ EVENTS.NO_VIDEO_ENCODED,
652
+ {
653
+ mediaType,
654
+ }
655
+ );
643
656
  }
644
657
 
645
658
  if (
@@ -651,8 +664,28 @@ export class StatsAnalyzer extends EventsScope {
651
664
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames sent`
652
665
  );
653
666
  }
654
- }
655
667
 
668
+ // Video is encoded but frames are not sent
669
+ if (
670
+ currentStats.framesEncoded !== previousStats.framesEncoded &&
671
+ (currentStats.framesSent === previousStats.framesSent || currentStats.framesSent === 0)
672
+ ) {
673
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_FRAMES_SENT;
674
+ LoggerProxy.logger.info(
675
+ `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames sent even though frames are encoded`
676
+ );
677
+ this.emit(
678
+ {
679
+ file: 'statsAnalyzer',
680
+ function: 'compareLastStatsResult',
681
+ },
682
+ EVENTS.NO_FRAMES_SENT,
683
+ {
684
+ mediaType,
685
+ }
686
+ );
687
+ }
688
+ }
656
689
  this.emitStartStopEvents(
657
690
  mediaType,
658
691
  previousStats.framesSent,
@@ -728,14 +761,25 @@ export class StatsAnalyzer extends EventsScope {
728
761
  LoggerProxy.logger.info(
729
762
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
730
763
  );
731
- } else {
764
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
732
765
  if (
733
766
  currentStats.framesEncoded === previousStats.framesEncoded ||
734
767
  currentStats.framesEncoded === 0
735
768
  ) {
769
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
736
770
  LoggerProxy.logger.info(
737
771
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames getting encoded`
738
772
  );
773
+ this.emit(
774
+ {
775
+ file: 'statsAnalyzer',
776
+ function: 'compareLastStatsResult',
777
+ },
778
+ EVENTS.NO_VIDEO_ENCODED,
779
+ {
780
+ mediaType,
781
+ }
782
+ );
739
783
  }
740
784
 
741
785
  if (
@@ -747,6 +791,27 @@ export class StatsAnalyzer extends EventsScope {
747
791
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames sent`
748
792
  );
749
793
  }
794
+
795
+ // Share video is encoded but frames are not sent
796
+ if (
797
+ currentStats.framesEncoded !== previousStats.framesEncoded &&
798
+ (currentStats.framesSent === previousStats.framesSent || currentStats.framesSent === 0)
799
+ ) {
800
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_FRAMES_SENT;
801
+ LoggerProxy.logger.info(
802
+ `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames sent even though frames are being encoded`
803
+ );
804
+ this.emit(
805
+ {
806
+ file: 'statsAnalyzer',
807
+ function: 'compareLastStatsResult',
808
+ },
809
+ EVENTS.NO_FRAMES_SENT,
810
+ {
811
+ mediaType,
812
+ }
813
+ );
814
+ }
750
815
  }
751
816
 
752
817
  // TODO:need to check receive share value
@@ -902,7 +967,6 @@ export class StatsAnalyzer extends EventsScope {
902
967
 
903
968
  this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
904
969
  this.statsResults[mediaType].bytesSent = kilobytes;
905
-
906
970
  this.statsResults[mediaType][sendrecvType].framesEncoded =
907
971
  result.framesEncoded - this.statsResults.internal[mediaType][sendrecvType].framesEncoded;
908
972
  this.statsResults[mediaType][sendrecvType].keyFramesEncoded =
@@ -1329,6 +1329,71 @@ describe('plugin-meetings', () => {
1329
1329
  data: {intervalData: fakeData, networkType: 'wifi'},
1330
1330
  });
1331
1331
  });
1332
+ it('NO_FRAMES_SENT triggers "meeting:noFramesSent" event and sends metrics', async () => {
1333
+ meeting.mediaProperties.mediaDirection = {sendVideo: true};
1334
+ statsAnalyzerStub.emit(
1335
+ {file: 'test', function: 'test'},
1336
+ StatsAnalyzerModule.EVENTS.NO_FRAMES_SENT,
1337
+ {mediaType: 'video'}
1338
+ );
1339
+
1340
+ assert.calledWith(
1341
+ TriggerProxy.trigger,
1342
+ sinon.match.instanceOf(Meeting),
1343
+ {
1344
+ file: 'meeting/index',
1345
+ function: 'compareLastStatsResult',
1346
+ },
1347
+ EVENT_TRIGGERS.MEETING_NO_FRAMES_SENT,
1348
+ {
1349
+ mediaType: 'video',
1350
+ }
1351
+ );
1352
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_FRAMES_SENT);
1353
+ });
1354
+ it('NO_FRAMES_SENT triggers "meeting:noFramesSent" event and sends metrics for share', async () => {
1355
+ meeting.mediaProperties.mediaDirection = {sendShare: true};
1356
+ statsAnalyzerStub.emit(
1357
+ {file: 'test', function: 'test'},
1358
+ StatsAnalyzerModule.EVENTS.NO_FRAMES_SENT,
1359
+ {mediaType: 'share'}
1360
+ );
1361
+
1362
+ assert.calledWith(
1363
+ TriggerProxy.trigger,
1364
+ sinon.match.instanceOf(Meeting),
1365
+ {
1366
+ file: 'meeting/index',
1367
+ function: 'compareLastStatsResult',
1368
+ },
1369
+ EVENT_TRIGGERS.MEETING_NO_FRAMES_SENT,
1370
+ {
1371
+ mediaType: 'share',
1372
+ }
1373
+ );
1374
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_FRAMES_SENT);
1375
+ });
1376
+ it('NO_VIDEO_ENCODED triggers "meeting:noVideoEncoded" event and sends metrics', async () => {
1377
+ statsAnalyzerStub.emit(
1378
+ {file: 'test', function: 'test'},
1379
+ StatsAnalyzerModule.EVENTS.NO_VIDEO_ENCODED,
1380
+ {mediaType: 'video'}
1381
+ );
1382
+
1383
+ assert.calledWith(
1384
+ TriggerProxy.trigger,
1385
+ sinon.match.instanceOf(Meeting),
1386
+ {
1387
+ file: 'meeting/index',
1388
+ function: 'compareLastStatsResult',
1389
+ },
1390
+ EVENT_TRIGGERS.MEETING_NO_VIDEO_ENCODED,
1391
+ {
1392
+ mediaType: 'video',
1393
+ }
1394
+ );
1395
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_VIDEO_ENCODED);
1396
+ });
1332
1397
  });
1333
1398
  });
1334
1399
  describe('#acknowledge', () => {
@@ -3605,14 +3670,17 @@ describe('plugin-meetings', () => {
3605
3670
  describe('#setUpLocusServicesListener', () => {
3606
3671
  it('listens to the locus services update event', (done) => {
3607
3672
  const newLocusServices = {
3608
- services: {
3609
- record: {
3610
- url: 'url',
3611
- }
3673
+ services: {
3674
+ record: {
3675
+ url: 'url',
3612
3676
  },
3677
+ },
3613
3678
  };
3614
3679
 
3615
- meeting.recordingController = {setServiceUrl: sinon.stub().returns(undefined), setSessionId: sinon.stub().returns(undefined)};
3680
+ meeting.recordingController = {
3681
+ setServiceUrl: sinon.stub().returns(undefined),
3682
+ setSessionId: sinon.stub().returns(undefined),
3683
+ };
3616
3684
 
3617
3685
  meeting.locusInfo.emit(
3618
3686
  {function: 'test', file: 'test'},
@@ -3620,7 +3688,10 @@ describe('plugin-meetings', () => {
3620
3688
  newLocusServices
3621
3689
  );
3622
3690
 
3623
- assert.calledWith(meeting.recordingController.setServiceUrl, newLocusServices.services.record.url);
3691
+ assert.calledWith(
3692
+ meeting.recordingController.setServiceUrl,
3693
+ newLocusServices.services.record.url
3694
+ );
3624
3695
  assert.calledOnce(meeting.recordingController.setSessionId);
3625
3696
  done();
3626
3697
  });
@@ -82,6 +82,22 @@ describe('plugin-meetings', () => {
82
82
  let pc;
83
83
  let networkQualityMonitor;
84
84
  let statsAnalyzer;
85
+ const statusResultOutboundRTP = {
86
+ type: 'outbound-rtp',
87
+ frameHeight: 720,
88
+ frameWidth: 1280,
89
+ packetsLost: 11,
90
+ framesSent: 105,
91
+ hugeFramesSent: 1,
92
+ framesEncoded: 102,
93
+ rttThreshold: 501,
94
+ jitterThreshold: 501,
95
+ jitterBufferDelay: 288.131459,
96
+ jitterBufferEmittedCount: 4013,
97
+ trackIdentifier: '6bbf5506-6a7e-4397-951c-c05b72ab0ace',
98
+ bytesSent: 1233,
99
+ totalPacketsSent: 100,
100
+ };
85
101
 
86
102
  let receivedEventsData = {
87
103
  local: {},
@@ -177,6 +193,12 @@ describe('plugin-meetings', () => {
177
193
  statsAnalyzer.on(EVENTS.REMOTE_MEDIA_STOPPED, (data) => {
178
194
  receivedEventsData.remote.stopped = data;
179
195
  });
196
+ statsAnalyzer.on(EVENTS.NO_FRAMES_SENT, (data) => {
197
+ receivedEventsData.noFramesSent = data;
198
+ });
199
+ statsAnalyzer.on(EVENTS.NO_VIDEO_ENCODED, (data) => {
200
+ receivedEventsData.noVideoEncoded = data;
201
+ });
180
202
  });
181
203
 
182
204
  afterEach(() => {
@@ -325,31 +347,92 @@ describe('plugin-meetings', () => {
325
347
  });
326
348
  it('processes track results and populate statsResults.resolutions object when type is outbound-rtp with video', async () => {
327
349
  await startStatsAnalyzer({expected: {receiveVideo: true}});
328
- const statusResultInboundRTP = {
329
- type: 'outbound-rtp',
330
- frameHeight: 720,
331
- frameWidth: 1280,
332
- packetsLost: 11,
333
- framesSent: 105,
334
- hugeFramesSent: 1,
335
- rttThreshold: 501,
336
- jitterThreshold: 501,
337
- jitterBufferDelay: 288.131459,
338
- jitterBufferEmittedCount: 4013,
339
- trackIdentifier: '6bbf5506-6a7e-4397-951c-c05b72ab0ace',
340
- };
341
- await statsAnalyzer.parseGetStatsResult(statusResultInboundRTP, 'video');
350
+
351
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'video');
342
352
  checkStats('outbound-rtp');
343
353
  });
344
354
 
345
355
  it('doesnot processes track results with audio', async () => {
346
356
  await startStatsAnalyzer({expected: {receiveAudio: true}});
347
- const statusResultInboundRTP = {
348
- type: 'outbound-rtp',
349
- };
350
- await statsAnalyzer.parseGetStatsResult(statusResultInboundRTP, 'audio');
357
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'audio');
351
358
  assert.deepEqual(statsAnalyzer.statsResults.resolutions.audio, undefined);
352
359
  });
360
+
361
+ it('emits NO_FRAMES_ENCODED when frames are not being encoded', async () => {
362
+ const expected = {mediaType: 'video'};
363
+ await startStatsAnalyzer({expected: {sendVideo: true}});
364
+
365
+ statsAnalyzer.lastStatsResults.video.send = {framesEncoded: 102, totalPacketsSent: 106};
366
+
367
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'video');
368
+
369
+ statsAnalyzer.compareLastStatsResult();
370
+ assert.deepEqual(receivedEventsData.noVideoEncoded, expected);
371
+ });
372
+
373
+ it('emits NO_FRAMES_SENT when frames are not being sent but frames are being encoded', async () => {
374
+ await startStatsAnalyzer({expected: {sendVideo: true}});
375
+
376
+ const expected = {mediaType: 'video'};
377
+
378
+ statsAnalyzer.lastStatsResults.video.send = {
379
+ framesEncoded: 10,
380
+ framesSent: 105,
381
+ totalPacketsSent: 106,
382
+ };
383
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'video');
384
+
385
+ statsAnalyzer.compareLastStatsResult();
386
+ assert.deepEqual(receivedEventsData.noFramesSent, expected);
387
+ });
388
+
389
+ it('doesnot emits NO_FRAMES_SENT when last emitted event is LOCAL_MEDIA_STOPPED', async () => {
390
+ statsAnalyzer.lastEmittedStartStopEvent.video.local = EVENTS.LOCAL_MEDIA_STOPPED;
391
+
392
+ await startStatsAnalyzer({expected: {sendVideo: true}});
393
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'video');
394
+
395
+ statsAnalyzer.compareLastStatsResult();
396
+ assert.deepEqual(receivedEventsData.noFramesSent, undefined);
397
+ });
398
+
399
+ it('emits NO_FRAMES_ENCODED when frames are not being encoded for share', async () => {
400
+ const expected = {mediaType: 'share'};
401
+ await startStatsAnalyzer({expected: {sendShare: true}});
402
+
403
+ statsAnalyzer.lastStatsResults.share.send = {framesEncoded: 102, totalPacketsSent: 106};
404
+
405
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'share');
406
+
407
+ statsAnalyzer.compareLastStatsResult();
408
+ assert.deepEqual(receivedEventsData.noVideoEncoded, expected);
409
+ });
410
+
411
+ it('emits NO_FRAMES_SENT when frames are not being sent but frames are being encoded for share', async () => {
412
+ const expected = {mediaType: 'share'};
413
+ await startStatsAnalyzer({expected: {sendShare: true}});
414
+
415
+ statsAnalyzer.lastStatsResults.share.send = {
416
+ framesEncoded: 10,
417
+ framesSent: 105,
418
+ totalPacketsSent: 106,
419
+ };
420
+
421
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'share');
422
+
423
+ statsAnalyzer.compareLastStatsResult();
424
+ assert.deepEqual(receivedEventsData.noFramesSent, expected);
425
+ });
426
+
427
+ it('doesnot emits NO_FRAMES_SENT when last emitted event is LOCAL_MEDIA_STOPPED for share', async () => {
428
+ statsAnalyzer.lastEmittedStartStopEvent.video.local = EVENTS.LOCAL_MEDIA_STOPPED;
429
+
430
+ await startStatsAnalyzer({expected: {sendShare: true}});
431
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'share');
432
+
433
+ statsAnalyzer.compareLastStatsResult();
434
+ assert.deepEqual(receivedEventsData.noFramesSent, undefined);
435
+ });
353
436
  });
354
437
  });
355
438
  });