@webex/plugin-meetings 2.59.6-next.2 → 2.59.7

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 (152) hide show
  1. package/dist/common/browser-detection.d.ts +9 -0
  2. package/dist/common/browser-detection.js +2 -2
  3. package/dist/common/browser-detection.js.map +1 -1
  4. package/dist/common/collection.d.ts +48 -0
  5. package/dist/common/collection.js +2 -2
  6. package/dist/common/collection.js.map +1 -1
  7. package/dist/common/config.d.ts +2 -0
  8. package/dist/common/errors/captcha-error.d.ts +15 -0
  9. package/dist/common/errors/intent-to-join.d.ts +16 -0
  10. package/dist/common/errors/join-meeting.d.ts +17 -0
  11. package/dist/common/errors/media.d.ts +15 -0
  12. package/dist/common/errors/parameter.d.ts +15 -0
  13. package/dist/common/errors/password-error.d.ts +15 -0
  14. package/dist/common/errors/permission.d.ts +14 -0
  15. package/dist/common/errors/reconnection-in-progress.d.ts +9 -0
  16. package/dist/common/errors/reconnection.d.ts +15 -0
  17. package/dist/common/errors/stats.d.ts +15 -0
  18. package/dist/common/errors/webex-errors.d.ts +81 -0
  19. package/dist/common/errors/webex-meetings-error.d.ts +20 -0
  20. package/dist/common/events/events-scope.d.ts +17 -0
  21. package/dist/common/events/events.d.ts +12 -0
  22. package/dist/common/events/trigger-proxy.d.ts +2 -0
  23. package/dist/common/events/util.d.ts +2 -0
  24. package/dist/common/logs/logger-config.d.ts +2 -0
  25. package/dist/common/logs/logger-proxy.d.ts +2 -0
  26. package/dist/common/logs/request.d.ts +34 -0
  27. package/dist/common/queue.d.ts +32 -0
  28. package/dist/config.d.ts +73 -0
  29. package/dist/config.js +2 -2
  30. package/dist/config.js.map +1 -1
  31. package/dist/constants.d.ts +926 -0
  32. package/dist/constants.js +2 -0
  33. package/dist/constants.js.map +1 -1
  34. package/dist/controls-options-manager/constants.d.ts +4 -0
  35. package/dist/controls-options-manager/enums.d.ts +5 -0
  36. package/dist/controls-options-manager/index.d.ts +120 -0
  37. package/dist/controls-options-manager/index.js +2 -2
  38. package/dist/controls-options-manager/index.js.map +1 -1
  39. package/dist/controls-options-manager/util.d.ts +7 -0
  40. package/dist/index.d.ts +4 -0
  41. package/dist/locus-info/controlsUtils.d.ts +2 -0
  42. package/dist/locus-info/controlsUtils.js +6 -6
  43. package/dist/locus-info/controlsUtils.js.map +1 -1
  44. package/dist/locus-info/embeddedAppsUtils.d.ts +2 -0
  45. package/dist/locus-info/fullState.d.ts +2 -0
  46. package/dist/locus-info/hostUtils.d.ts +2 -0
  47. package/dist/locus-info/index.d.ts +269 -0
  48. package/dist/locus-info/index.js +18 -18
  49. package/dist/locus-info/index.js.map +1 -1
  50. package/dist/locus-info/infoUtils.d.ts +2 -0
  51. package/dist/locus-info/mediaSharesUtils.d.ts +2 -0
  52. package/dist/locus-info/parser.d.ts +212 -0
  53. package/dist/locus-info/parser.js +2 -2
  54. package/dist/locus-info/parser.js.map +1 -1
  55. package/dist/locus-info/selfUtils.d.ts +2 -0
  56. package/dist/media/index.d.ts +32 -0
  57. package/dist/media/properties.d.ts +108 -0
  58. package/dist/media/util.d.ts +2 -0
  59. package/dist/mediaQualityMetrics/config.d.ts +233 -0
  60. package/dist/meeting/effectsState.d.ts +42 -0
  61. package/dist/meeting/in-meeting-actions.d.ts +79 -0
  62. package/dist/meeting/index.d.ts +1622 -0
  63. package/dist/meeting/index.js +51 -28
  64. package/dist/meeting/index.js.map +1 -1
  65. package/dist/meeting/muteState.d.ts +116 -0
  66. package/dist/meeting/request.d.ts +255 -0
  67. package/dist/meeting/request.js +2 -2
  68. package/dist/meeting/request.js.map +1 -1
  69. package/dist/meeting/state.d.ts +9 -0
  70. package/dist/meeting/util.d.ts +2 -0
  71. package/dist/meeting/util.js +4 -4
  72. package/dist/meeting/util.js.map +1 -1
  73. package/dist/meeting-info/collection.d.ts +20 -0
  74. package/dist/meeting-info/collection.js +2 -2
  75. package/dist/meeting-info/collection.js.map +1 -1
  76. package/dist/meeting-info/index.d.ts +57 -0
  77. package/dist/meeting-info/meeting-info-v2.d.ts +93 -0
  78. package/dist/meeting-info/request.d.ts +22 -0
  79. package/dist/meeting-info/util.d.ts +2 -0
  80. package/dist/meeting-info/utilv2.d.ts +2 -0
  81. package/dist/meetings/collection.d.ts +23 -0
  82. package/dist/meetings/collection.js +2 -2
  83. package/dist/meetings/collection.js.map +1 -1
  84. package/dist/meetings/index.d.ts +296 -0
  85. package/dist/meetings/request.d.ts +27 -0
  86. package/dist/meetings/util.d.ts +18 -0
  87. package/dist/member/index.d.ts +147 -0
  88. package/dist/member/member.types.d.ts +11 -0
  89. package/dist/member/util.d.ts +2 -0
  90. package/dist/members/collection.d.ts +24 -0
  91. package/dist/members/index.d.ts +298 -0
  92. package/dist/members/index.js +2 -2
  93. package/dist/members/index.js.map +1 -1
  94. package/dist/members/request.d.ts +50 -0
  95. package/dist/members/util.d.ts +2 -0
  96. package/dist/metrics/config.d.ts +169 -0
  97. package/dist/metrics/constants.d.ts +59 -0
  98. package/dist/metrics/constants.js +2 -0
  99. package/dist/metrics/constants.js.map +1 -1
  100. package/dist/metrics/index.d.ts +152 -0
  101. package/dist/metrics/index.js +2 -2
  102. package/dist/metrics/index.js.map +1 -1
  103. package/dist/networkQualityMonitor/index.d.ts +70 -0
  104. package/dist/peer-connection-manager/index.d.ts +6 -0
  105. package/dist/peer-connection-manager/util.d.ts +6 -0
  106. package/dist/personal-meeting-room/index.d.ts +47 -0
  107. package/dist/personal-meeting-room/request.d.ts +14 -0
  108. package/dist/personal-meeting-room/util.d.ts +2 -0
  109. package/dist/reachability/index.d.ts +139 -0
  110. package/dist/reachability/index.js +2 -9
  111. package/dist/reachability/index.js.map +1 -1
  112. package/dist/reachability/request.d.ts +35 -0
  113. package/dist/reactions/reactions.d.ts +4 -0
  114. package/dist/reactions/reactions.type.d.ts +32 -0
  115. package/dist/reconnection-manager/index.d.ts +112 -0
  116. package/dist/recording-controller/enums.d.ts +7 -0
  117. package/dist/recording-controller/index.d.ts +193 -0
  118. package/dist/recording-controller/util.d.ts +13 -0
  119. package/dist/roap/collection.d.ts +10 -0
  120. package/dist/roap/handler.d.ts +47 -0
  121. package/dist/roap/index.d.ts +116 -0
  122. package/dist/roap/index.js +13 -12
  123. package/dist/roap/index.js.map +1 -1
  124. package/dist/roap/request.d.ts +35 -0
  125. package/dist/roap/state.d.ts +9 -0
  126. package/dist/roap/turnDiscovery.d.ts +81 -0
  127. package/dist/roap/turnDiscovery.js +130 -44
  128. package/dist/roap/turnDiscovery.js.map +1 -1
  129. package/dist/roap/util.d.ts +2 -0
  130. package/dist/statsAnalyzer/global.d.ts +118 -0
  131. package/dist/statsAnalyzer/global.js +4 -12
  132. package/dist/statsAnalyzer/global.js.map +1 -1
  133. package/dist/statsAnalyzer/index.d.ts +193 -0
  134. package/dist/statsAnalyzer/index.js +56 -14
  135. package/dist/statsAnalyzer/index.js.map +1 -1
  136. package/dist/statsAnalyzer/mqaUtil.d.ts +22 -0
  137. package/dist/statsAnalyzer/mqaUtil.js +15 -15
  138. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  139. package/dist/transcription/index.d.ts +64 -0
  140. package/package.json +21 -22
  141. package/src/config.ts +2 -2
  142. package/src/constants.ts +2 -0
  143. package/src/meeting/index.ts +35 -0
  144. package/src/metrics/constants.ts +2 -0
  145. package/src/roap/index.ts +14 -10
  146. package/src/roap/turnDiscovery.ts +48 -20
  147. package/src/statsAnalyzer/global.ts +2 -10
  148. package/src/statsAnalyzer/index.ts +80 -17
  149. package/test/unit/spec/meeting/index.js +77 -6
  150. package/test/unit/spec/roap/index.ts +84 -80
  151. package/test/unit/spec/roap/turnDiscovery.ts +21 -0
  152. package/test/unit/spec/stats-analyzer/index.js +151 -0
@@ -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',
@@ -420,9 +422,6 @@ export class StatsAnalyzer extends EventsScope {
420
422
  case 'inbound-rtp':
421
423
  this.processInboundRTPResult(getStatsResult, type);
422
424
  break;
423
- case 'track':
424
- this.processTrackResult(getStatsResult, type);
425
- break;
426
425
  case 'remote-inbound-rtp':
427
426
  case 'remote-outbound-rtp':
428
427
  // @ts-ignore
@@ -520,7 +519,7 @@ export class StatsAnalyzer extends EventsScope {
520
519
 
521
520
  if (currentValue - previousValue > 0) {
522
521
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STARTED : EVENTS.REMOTE_MEDIA_STARTED;
523
- } else if (currentValue === previousValue && currentValue > 0) {
522
+ } else if (currentValue === previousValue && currentValue >= 0) {
524
523
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STOPPED : EVENTS.REMOTE_MEDIA_STOPPED;
525
524
  }
526
525
 
@@ -635,14 +634,25 @@ export class StatsAnalyzer extends EventsScope {
635
634
  LoggerProxy.logger.info(
636
635
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
637
636
  );
638
- } else {
637
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
639
638
  if (
640
639
  currentStats.framesEncoded === previousStats.framesEncoded ||
641
640
  currentStats.framesEncoded === 0
642
641
  ) {
642
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
643
643
  LoggerProxy.logger.info(
644
644
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames Encoded`
645
645
  );
646
+ this.emit(
647
+ {
648
+ file: 'statsAnalyzer',
649
+ function: 'compareLastStatsResult',
650
+ },
651
+ EVENTS.NO_VIDEO_ENCODED,
652
+ {
653
+ mediaType,
654
+ }
655
+ );
646
656
  }
647
657
 
648
658
  if (
@@ -654,8 +664,28 @@ export class StatsAnalyzer extends EventsScope {
654
664
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames sent`
655
665
  );
656
666
  }
657
- }
658
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
+ }
659
689
  this.emitStartStopEvents(
660
690
  mediaType,
661
691
  previousStats.framesSent,
@@ -731,14 +761,25 @@ export class StatsAnalyzer extends EventsScope {
731
761
  LoggerProxy.logger.info(
732
762
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
733
763
  );
734
- } else {
764
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
735
765
  if (
736
766
  currentStats.framesEncoded === previousStats.framesEncoded ||
737
767
  currentStats.framesEncoded === 0
738
768
  ) {
769
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
739
770
  LoggerProxy.logger.info(
740
771
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames getting encoded`
741
772
  );
773
+ this.emit(
774
+ {
775
+ file: 'statsAnalyzer',
776
+ function: 'compareLastStatsResult',
777
+ },
778
+ EVENTS.NO_VIDEO_ENCODED,
779
+ {
780
+ mediaType,
781
+ }
782
+ );
742
783
  }
743
784
 
744
785
  if (
@@ -750,6 +791,27 @@ export class StatsAnalyzer extends EventsScope {
750
791
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames sent`
751
792
  );
752
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
+ }
753
815
  }
754
816
 
755
817
  // TODO:need to check receive share value
@@ -881,6 +943,7 @@ export class StatsAnalyzer extends EventsScope {
881
943
  const mediaType = type || STATS.AUDIO_CORRELATE;
882
944
  const sendrecvType = STATS.SEND_DIRECTION;
883
945
 
946
+ this.processTrackResult(result, type, sendrecvType);
884
947
  if (result.bytesSent) {
885
948
  let kilobytes = 0;
886
949
 
@@ -904,7 +967,6 @@ export class StatsAnalyzer extends EventsScope {
904
967
 
905
968
  this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
906
969
  this.statsResults[mediaType].bytesSent = kilobytes;
907
-
908
970
  this.statsResults[mediaType][sendrecvType].framesEncoded =
909
971
  result.framesEncoded - this.statsResults.internal[mediaType][sendrecvType].framesEncoded;
910
972
  this.statsResults[mediaType][sendrecvType].keyFramesEncoded =
@@ -955,6 +1017,7 @@ export class StatsAnalyzer extends EventsScope {
955
1017
  const mediaType = type || STATS.AUDIO_CORRELATE;
956
1018
  const sendrecvType = STATS.RECEIVE_DIRECTION;
957
1019
 
1020
+ this.processTrackResult(result, type, sendrecvType);
958
1021
  if (result.bytesReceived) {
959
1022
  let kilobytes = 0;
960
1023
 
@@ -1158,29 +1221,29 @@ export class StatsAnalyzer extends EventsScope {
1158
1221
  * @private
1159
1222
  * @param {*} result
1160
1223
  * @param {*} mediaType
1224
+ * @param {*} sendrecvType
1161
1225
  * @returns {void}
1162
1226
  * @memberof StatsAnalyzer
1163
1227
  */
1164
- private processTrackResult(result: any, mediaType: any) {
1165
- if (!result || result.type !== 'track') {
1228
+ private processTrackResult(result: any, mediaType: any, sendrecvType: any) {
1229
+ if (!result || mediaType === STATS.AUDIO_CORRELATE) {
1230
+ return;
1231
+ }
1232
+ if (result.type !== 'inbound-rtp' && result.type !== 'outbound-rtp') {
1166
1233
  return;
1167
1234
  }
1168
- if (result.type !== 'track') return;
1169
-
1170
- const sendrecvType =
1171
- result.remoteSource === true ? STATS.RECEIVE_DIRECTION : STATS.SEND_DIRECTION;
1172
-
1173
1235
  if (result.frameWidth && result.frameHeight) {
1174
1236
  this.statsResults.resolutions[mediaType][sendrecvType].width = result.frameWidth;
1175
1237
  this.statsResults.resolutions[mediaType][sendrecvType].height = result.frameHeight;
1176
- this.statsResults.resolutions[mediaType][sendrecvType].framesSent = result.framesSent;
1177
- this.statsResults.resolutions[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
1178
1238
  }
1179
1239
 
1180
1240
  if (sendrecvType === STATS.RECEIVE_DIRECTION) {
1181
1241
  this.statsResults.resolutions[mediaType][sendrecvType].framesReceived = result.framesReceived;
1182
1242
  this.statsResults.resolutions[mediaType][sendrecvType].framesDecoded = result.framesDecoded;
1183
1243
  this.statsResults.resolutions[mediaType][sendrecvType].framesDropped = result.framesDropped;
1244
+ } else if (sendrecvType === STATS.SEND_DIRECTION) {
1245
+ this.statsResults.resolutions[mediaType][sendrecvType].framesSent = result.framesSent;
1246
+ this.statsResults.resolutions[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
1184
1247
  }
1185
1248
 
1186
1249
  if (result.trackIdentifier && mediaType !== STATS.AUDIO_CORRELATE) {
@@ -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', () => {
@@ -3646,14 +3711,17 @@ describe('plugin-meetings', () => {
3646
3711
  describe('#setUpLocusServicesListener', () => {
3647
3712
  it('listens to the locus services update event', (done) => {
3648
3713
  const newLocusServices = {
3649
- services: {
3650
- record: {
3651
- url: 'url',
3652
- }
3714
+ services: {
3715
+ record: {
3716
+ url: 'url',
3653
3717
  },
3718
+ },
3654
3719
  };
3655
3720
 
3656
- meeting.recordingController = {setServiceUrl: sinon.stub().returns(undefined), setSessionId: sinon.stub().returns(undefined)};
3721
+ meeting.recordingController = {
3722
+ setServiceUrl: sinon.stub().returns(undefined),
3723
+ setSessionId: sinon.stub().returns(undefined),
3724
+ };
3657
3725
 
3658
3726
  meeting.locusInfo.emit(
3659
3727
  {function: 'test', file: 'test'},
@@ -3661,7 +3729,10 @@ describe('plugin-meetings', () => {
3661
3729
  newLocusServices
3662
3730
  );
3663
3731
 
3664
- assert.calledWith(meeting.recordingController.setServiceUrl, newLocusServices.services.record.url);
3732
+ assert.calledWith(
3733
+ meeting.recordingController.setServiceUrl,
3734
+ newLocusServices.services.record.url
3735
+ );
3665
3736
  assert.calledOnce(meeting.recordingController.setSessionId);
3666
3737
  done();
3667
3738
  });
@@ -41,88 +41,92 @@ describe('Roap', () => {
41
41
  describe('sendRoapMediaRequest', () => {
42
42
  let sendRoapStub;
43
43
  let roapHandlerSubmitStub;
44
-
45
- const meeting = {
46
- id: 'some meeting id',
47
- correlationId: 'correlation id',
48
- selfUrl: 'self url',
49
- mediaId: 'media id',
50
- audio:{
51
- isLocallyMuted: () => true,
52
- },
53
- video:{
54
- isLocallyMuted: () => false,
55
- },
56
- setRoapSeq: sinon.stub(),
57
- config: {experimental: {enableTurnDiscovery: false}},
58
- };
44
+ let meeting;
59
45
 
60
46
  beforeEach(() => {
61
- sendRoapStub = sinon.stub(RoapRequest.prototype, 'sendRoap').resolves({});
62
- roapHandlerSubmitStub = sinon.stub(RoapHandler.prototype, 'submit');
63
- meeting.setRoapSeq.resetHistory();
47
+ meeting = {
48
+ id: 'some meeting id',
49
+ correlationId: 'correlation id',
50
+ selfUrl: 'self url',
51
+ mediaId: 'media id',
52
+ audio: {
53
+ isLocallyMuted: () => true,
54
+ },
55
+ video: {
56
+ isLocallyMuted: () => false,
57
+ },
58
+ setRoapSeq: sinon.stub(),
59
+ config: {experimental: {enableTurnDiscovery: false}},
60
+ webex: {meetings: {reachability: {isAnyClusterReachable: () => true}}},
61
+ };
62
+
63
+ beforeEach(() => {
64
+ sendRoapStub = sinon.stub(RoapRequest.prototype, 'sendRoap').resolves({});
65
+ roapHandlerSubmitStub = sinon.stub(RoapHandler.prototype, 'submit');
66
+ meeting.setRoapSeq.resetHistory();
67
+ });
68
+
69
+ afterEach(() => {
70
+ sinon.restore();
71
+ });
72
+
73
+ [
74
+ {reconnect: true, turnDiscoverySkipped: false, expectEmptyMediaId: false},
75
+ {reconnect: true, turnDiscoverySkipped: true, expectEmptyMediaId: true},
76
+ {reconnect: false, turnDiscoverySkipped: false, expectEmptyMediaId: false},
77
+ {reconnect: false, turnDiscoverySkipped: true, expectEmptyMediaId: false},
78
+ ].forEach(({reconnect, turnDiscoverySkipped, expectEmptyMediaId}) =>
79
+ it(`sends roap OFFER with ${expectEmptyMediaId ? 'empty ' : ''}mediaId when ${
80
+ reconnect ? '' : 'not '
81
+ }reconnecting and TURN discovery is ${
82
+ turnDiscoverySkipped ? 'skipped' : 'not skipped'
83
+ }`, async () => {
84
+ const roap = new Roap({}, {parent: 'fake'});
85
+
86
+ sinon.stub(roap.turnDiscovery, 'isSkipped').resolves(turnDiscoverySkipped);
87
+
88
+ await roap.sendRoapMediaRequest({
89
+ meeting,
90
+ sdp: 'sdp',
91
+ reconnect,
92
+ roapSeq: 1,
93
+ });
94
+
95
+ const expectedRoapMessage = {
96
+ messageType: 'OFFER',
97
+ sdps: ['sdp'],
98
+ version: '2',
99
+ seq: 2,
100
+ tieBreaker: 4294967294,
101
+ };
102
+
103
+ assert.calledOnce(sendRoapStub);
104
+ assert.calledWith(sendRoapStub, {
105
+ roapMessage: expectedRoapMessage,
106
+ correlationId: meeting.correlationId,
107
+ locusSelfUrl: meeting.selfUrl,
108
+ mediaId: expectEmptyMediaId ? '' : meeting.mediaId,
109
+ audioMuted: meeting.audio?.isLocallyMuted(),
110
+ videoMuted: meeting.video?.isLocallyMuted(),
111
+ meetingId: meeting.id,
112
+ });
113
+
114
+ assert.calledTwice(roapHandlerSubmitStub);
115
+ assert.calledWith(roapHandlerSubmitStub, {
116
+ type: ROAP.SEND_ROAP_MSG,
117
+ msg: expectedRoapMessage,
118
+ correlationId: meeting.correlationId,
119
+ });
120
+ assert.calledWith(roapHandlerSubmitStub, {
121
+ type: ROAP.SEND_ROAP_MSG_SUCCESS,
122
+ seq: 2,
123
+ correlationId: meeting.correlationId,
124
+ });
125
+
126
+ assert.calledOnce(meeting.setRoapSeq);
127
+ assert.calledWith(meeting.setRoapSeq, 2);
128
+ })
129
+ );
64
130
  });
65
-
66
- afterEach(() => {
67
- sinon.restore();
68
- });
69
-
70
- [
71
- {reconnect: true, enableTurnDiscovery: true, expectEmptyMediaId: false},
72
- {reconnect: true, enableTurnDiscovery: false, expectEmptyMediaId: true},
73
- {reconnect: false, enableTurnDiscovery: true, expectEmptyMediaId: false},
74
- {reconnect: false, enableTurnDiscovery: false, expectEmptyMediaId: false},
75
- ].forEach(({reconnect, enableTurnDiscovery, expectEmptyMediaId}) =>
76
- it(`sends roap OFFER with ${expectEmptyMediaId ? 'empty ' : ''}mediaId when ${
77
- reconnect ? '' : 'not '
78
- }reconnecting and TURN discovery is ${
79
- enableTurnDiscovery ? 'enabled' : 'disabled'
80
- }`, async () => {
81
- meeting.config.experimental.enableTurnDiscovery = enableTurnDiscovery;
82
-
83
- const roap = new Roap({}, {parent: 'fake'});
84
-
85
- await roap.sendRoapMediaRequest({
86
- meeting,
87
- sdp: 'sdp',
88
- reconnect,
89
- roapSeq: 1,
90
- });
91
-
92
- const expectedRoapMessage = {
93
- messageType: 'OFFER',
94
- sdps: ['sdp'],
95
- version: '2',
96
- seq: 2,
97
- tieBreaker: 4294967294,
98
- };
99
-
100
- assert.calledOnce(sendRoapStub);
101
- assert.calledWith(sendRoapStub, {
102
- roapMessage: expectedRoapMessage,
103
- correlationId: meeting.correlationId,
104
- locusSelfUrl: meeting.selfUrl,
105
- mediaId: expectEmptyMediaId ? '' : meeting.mediaId,
106
- audioMuted: meeting.audio?.isLocallyMuted(),
107
- videoMuted: meeting.video?.isLocallyMuted(),
108
- meetingId: meeting.id,
109
- });
110
-
111
- assert.calledTwice(roapHandlerSubmitStub);
112
- assert.calledWith(roapHandlerSubmitStub, {
113
- type: ROAP.SEND_ROAP_MSG,
114
- msg: expectedRoapMessage,
115
- correlationId: meeting.correlationId,
116
- });
117
- assert.calledWith(roapHandlerSubmitStub, {
118
- type: ROAP.SEND_ROAP_MSG_SUCCESS,
119
- seq: 2,
120
- correlationId: meeting.correlationId,
121
- });
122
-
123
- assert.calledOnce(meeting.setRoapSeq);
124
- assert.calledWith(meeting.setRoapSeq, 2);
125
- })
126
- );
127
131
  });
128
132
  });
@@ -352,6 +352,27 @@ describe('TurnDiscovery', () => {
352
352
  });
353
353
  });
354
354
 
355
+ describe('isSkipped', () => {
356
+ [
357
+ {enabledInConfig: true, isAnyClusterReachable: true, expectedIsSkipped: true},
358
+ {enabledInConfig: true, isAnyClusterReachable: false, expectedIsSkipped: false},
359
+ {enabledInConfig: false, isAnyClusterReachable: true, expectedIsSkipped: true},
360
+ {enabledInConfig: false, isAnyClusterReachable: false, expectedIsSkipped: true},
361
+ ].forEach(({enabledInConfig, isAnyClusterReachable, expectedIsSkipped}) => {
362
+ it(`returns ${expectedIsSkipped} when TURN discovery is ${enabledInConfig ? '' : 'not '} enabled in config and isAnyClusterReachable() returns ${isAnyClusterReachable ? 'true' : 'false'}`, async () => {
363
+ testMeeting.config.experimental.enableTurnDiscovery = enabledInConfig;
364
+
365
+ sinon.stub(testMeeting.webex.meetings.reachability, 'isAnyClusterReachable').resolves(isAnyClusterReachable);
366
+
367
+ const td = new TurnDiscovery(mockRoapRequest);
368
+
369
+ const isSkipped = await td.isSkipped(testMeeting);
370
+
371
+ assert.equal(isSkipped, expectedIsSkipped);
372
+ })
373
+ })
374
+ })
375
+
355
376
  describe('handleTurnDiscoveryResponse', () => {
356
377
  it("doesn't do anything if turn discovery was not started", () => {
357
378
  const td = new TurnDiscovery(mockRoapRequest);
@@ -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(() => {
@@ -282,6 +304,135 @@ describe('plugin-meetings', () => {
282
304
 
283
305
  checkReceivedEvent({expected: {remote: {stopped: {type: 'video'}}}});
284
306
  });
307
+
308
+ const checkStats = (type) => {
309
+ const statsResult = {
310
+ height: 720,
311
+ width: 1280,
312
+ jitterBufferDelay: 288.131459,
313
+ jitterBufferEmittedCount: 4013,
314
+ trackIdentifier: '6bbf5506-6a7e-4397-951c-c05b72ab0ace',
315
+ avgJitterDelay: 0.07179951632195365,
316
+ };
317
+ if (type === 'inbound-rtp') {
318
+ statsResult.framesDecoded = 4013;
319
+ statsResult.framesDropped = 0;
320
+ statsResult.framesReceived = 4016;
321
+ assert.deepEqual(statsAnalyzer.statsResults.resolutions.video.recv, statsResult);
322
+ } else if (type === 'outbound-rtp') {
323
+ statsResult.framesSent = 105;
324
+ statsResult.hugeFramesSent = 1;
325
+ assert.deepEqual(statsAnalyzer.statsResults.resolutions.video.send, statsResult);
326
+ }
327
+ };
328
+
329
+ it('processes track results and populate statsResults.resolutions object when type is inbound-rtp with video', async () => {
330
+ await startStatsAnalyzer({expected: {receiveVideo: true}});
331
+ const statusResultInboundRTP = {
332
+ type: 'inbound-rtp',
333
+ frameHeight: 720,
334
+ frameWidth: 1280,
335
+ packetsLost: 11,
336
+ rttThreshold: 501,
337
+ jitterThreshold: 501,
338
+ framesDecoded: 4013,
339
+ framesDropped: 0,
340
+ framesReceived: 4016,
341
+ jitterBufferDelay: 288.131459,
342
+ jitterBufferEmittedCount: 4013,
343
+ trackIdentifier: '6bbf5506-6a7e-4397-951c-c05b72ab0ace',
344
+ };
345
+ await statsAnalyzer.parseGetStatsResult(statusResultInboundRTP, 'video');
346
+ checkStats('inbound-rtp');
347
+ });
348
+ it('processes track results and populate statsResults.resolutions object when type is outbound-rtp with video', async () => {
349
+ await startStatsAnalyzer({expected: {receiveVideo: true}});
350
+
351
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'video');
352
+ checkStats('outbound-rtp');
353
+ });
354
+
355
+ it('doesnot processes track results with audio', async () => {
356
+ await startStatsAnalyzer({expected: {receiveAudio: true}});
357
+ await statsAnalyzer.parseGetStatsResult(statusResultOutboundRTP, 'audio');
358
+ assert.deepEqual(statsAnalyzer.statsResults.resolutions.audio, undefined);
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
+ });
285
436
  });
286
437
  });
287
438
  });