@webex/plugin-meetings 3.11.0 → 3.12.0-mobius-socket.2

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 (175) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +14 -5
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +7 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +868 -419
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +522 -131
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1304 -928
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +117 -48
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +6 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/multistream/sendSlotManager.js +116 -2
  69. package/dist/multistream/sendSlotManager.js.map +1 -1
  70. package/dist/reachability/index.js +18 -10
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/reactions/reactions.type.js.map +1 -1
  73. package/dist/reconnection-manager/index.js +0 -1
  74. package/dist/reconnection-manager/index.js.map +1 -1
  75. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  76. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  77. package/dist/types/config.d.ts +4 -0
  78. package/dist/types/constants.d.ts +23 -1
  79. package/dist/types/hashTree/constants.d.ts +1 -0
  80. package/dist/types/hashTree/hashTree.d.ts +7 -0
  81. package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
  82. package/dist/types/hashTree/types.d.ts +3 -0
  83. package/dist/types/hashTree/utils.d.ts +6 -0
  84. package/dist/types/index.d.ts +1 -0
  85. package/dist/types/interceptors/constant.d.ts +5 -0
  86. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  87. package/dist/types/interceptors/index.d.ts +2 -1
  88. package/dist/types/interceptors/utils.d.ts +1 -0
  89. package/dist/types/locus-info/index.d.ts +60 -8
  90. package/dist/types/locus-info/types.d.ts +7 -0
  91. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  92. package/dist/types/media/properties.d.ts +2 -1
  93. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  94. package/dist/types/meeting/index.d.ts +72 -7
  95. package/dist/types/meeting/request.d.ts +16 -1
  96. package/dist/types/meeting/request.type.d.ts +5 -0
  97. package/dist/types/meeting/util.d.ts +31 -0
  98. package/dist/types/meetings/index.d.ts +4 -2
  99. package/dist/types/member/index.d.ts +1 -0
  100. package/dist/types/member/util.d.ts +5 -0
  101. package/dist/types/metrics/constants.d.ts +5 -0
  102. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  103. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  104. package/dist/types/reactions/reactions.type.d.ts +1 -0
  105. package/dist/types/webinar/utils.d.ts +6 -0
  106. package/dist/webinar/index.js +438 -163
  107. package/dist/webinar/index.js.map +1 -1
  108. package/dist/webinar/utils.js +25 -0
  109. package/dist/webinar/utils.js.map +1 -0
  110. package/package.json +24 -23
  111. package/src/aiEnableRequest/README.md +84 -0
  112. package/src/aiEnableRequest/index.ts +170 -0
  113. package/src/aiEnableRequest/utils.ts +25 -0
  114. package/src/annotation/index.ts +27 -7
  115. package/src/config.ts +4 -0
  116. package/src/constants.ts +29 -1
  117. package/src/hashTree/constants.ts +1 -0
  118. package/src/hashTree/hashTree.ts +17 -0
  119. package/src/hashTree/hashTreeParser.ts +761 -260
  120. package/src/hashTree/types.ts +4 -0
  121. package/src/hashTree/utils.ts +9 -0
  122. package/src/index.ts +8 -1
  123. package/src/interceptors/constant.ts +6 -0
  124. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  125. package/src/interceptors/index.ts +2 -1
  126. package/src/interceptors/utils.ts +16 -0
  127. package/src/interpretation/index.ts +2 -2
  128. package/src/locus-info/controlsUtils.ts +11 -0
  129. package/src/locus-info/index.ts +579 -113
  130. package/src/locus-info/selfUtils.ts +1 -0
  131. package/src/locus-info/types.ts +8 -0
  132. package/src/media/MediaConnectionAwaiter.ts +41 -1
  133. package/src/media/properties.ts +3 -1
  134. package/src/meeting/in-meeting-actions.ts +12 -0
  135. package/src/meeting/index.ts +389 -87
  136. package/src/meeting/request.ts +42 -0
  137. package/src/meeting/request.type.ts +6 -0
  138. package/src/meeting/util.ts +160 -2
  139. package/src/meetings/index.ts +157 -44
  140. package/src/member/index.ts +10 -0
  141. package/src/member/util.ts +12 -0
  142. package/src/metrics/constants.ts +6 -0
  143. package/src/multistream/mediaRequestManager.ts +4 -54
  144. package/src/multistream/remoteMediaManager.ts +13 -0
  145. package/src/multistream/sendSlotManager.ts +97 -3
  146. package/src/reachability/index.ts +9 -0
  147. package/src/reactions/reactions.type.ts +1 -0
  148. package/src/reconnection-manager/index.ts +0 -1
  149. package/src/webinar/index.ts +265 -6
  150. package/src/webinar/utils.ts +16 -0
  151. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  152. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  153. package/test/unit/spec/annotation/index.ts +69 -7
  154. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  155. package/test/unit/spec/hashTree/hashTreeParser.ts +2321 -175
  156. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  157. package/test/unit/spec/interceptors/utils.ts +75 -0
  158. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  159. package/test/unit/spec/locus-info/index.js +1134 -55
  160. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  161. package/test/unit/spec/media/properties.ts +12 -3
  162. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  163. package/test/unit/spec/meeting/index.js +884 -152
  164. package/test/unit/spec/meeting/request.js +70 -0
  165. package/test/unit/spec/meeting/utils.js +438 -26
  166. package/test/unit/spec/meetings/index.js +653 -32
  167. package/test/unit/spec/member/index.js +28 -4
  168. package/test/unit/spec/member/util.js +65 -27
  169. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  170. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  171. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  172. package/test/unit/spec/reachability/index.ts +23 -0
  173. package/test/unit/spec/reconnection-manager/index.js +4 -8
  174. package/test/unit/spec/webinar/index.ts +534 -37
  175. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -18,6 +18,7 @@ describe('member', () => {
18
18
  assert.exists(member.supportsBreakouts);
19
19
  assert.exists(member.supportLiveAnnotation);
20
20
  assert.exists(member.canReclaimHost);
21
+ assert.exists(member.canApproveAIEnablement);
21
22
  });
22
23
 
23
24
  describe('roles', () => {
@@ -47,17 +48,24 @@ describe('member', () => {
47
48
  it('checks that processParticipant calls canReclaimHost', () => {
48
49
  sinon.spy(MemberUtil, 'canReclaimHost');
49
50
  member.processParticipant(participant);
50
-
51
+
51
52
  assert.calledOnceWithExactly(MemberUtil.canReclaimHost, participant);
52
53
  });
53
54
 
54
55
  it('checks that processParticipant calls isPresenterAssignmentProhibited', () => {
55
56
  sinon.spy(MemberUtil, 'isPresenterAssignmentProhibited');
56
57
  member.processParticipant(participant);
57
-
58
+
58
59
  assert.calledOnceWithExactly(MemberUtil.isPresenterAssignmentProhibited, participant);
59
60
  });
60
- })
61
+
62
+ it('checks that processParticipant calls canApproveAIEnablement', () => {
63
+ sinon.spy(MemberUtil, 'canApproveAIEnablement');
64
+ member.processParticipant(participant);
65
+
66
+ assert.calledOnceWithExactly(MemberUtil.canApproveAIEnablement, participant);
67
+ });
68
+ });
61
69
 
62
70
  describe('#processMember', () => {
63
71
  it('checks that processMember calls isRemovable', () => {
@@ -80,5 +88,21 @@ describe('member', () => {
80
88
 
81
89
  assert.calledOnceWithExactly(MemberUtil.extractMediaStatus, participant);
82
90
  });
83
- })
91
+ });
92
+
93
+ describe('canApproveAIEnablement integration', () => {
94
+ it('sets canApproveAIEnablement to the value returned by MemberUtil.canApproveAIEnablement', () => {
95
+ const testParticipant = {controls: {}, status: {}};
96
+
97
+ sinon.stub(MemberUtil, 'canApproveAIEnablement').returns(true);
98
+ const memberWithTrue = new Member(testParticipant);
99
+ assert.isTrue(memberWithTrue.canApproveAIEnablement);
100
+
101
+ MemberUtil.canApproveAIEnablement.restore();
102
+
103
+ sinon.stub(MemberUtil, 'canApproveAIEnablement').returns(false);
104
+ const memberWithFalse = new Member(testParticipant);
105
+ assert.isFalse(memberWithFalse.canApproveAIEnablement);
106
+ });
107
+ });
84
108
  });
@@ -82,6 +82,46 @@ describe('plugin-meetings', () => {
82
82
  });
83
83
  });
84
84
 
85
+ describe('MemberUtil.canApproveAIEnablement', () => {
86
+ it('returns false when there is no participant', () => {
87
+ assert.isFalse(MemberUtil.canApproveAIEnablement());
88
+ });
89
+
90
+ it('returns false when there is null participant', () => {
91
+ assert.isFalse(MemberUtil.canApproveAIEnablement(null));
92
+ });
93
+
94
+ it('returns true when attendeeRequestAiAssistantNotAllowed is false', () => {
95
+ const participant = {
96
+ attendeeRequestAiAssistantNotAllowed: false,
97
+ };
98
+
99
+ assert.isTrue(MemberUtil.canApproveAIEnablement(participant));
100
+ });
101
+
102
+ it('returns false when attendeeRequestAiAssistantNotAllowed is true', () => {
103
+ const participant = {
104
+ attendeeRequestAiAssistantNotAllowed: true,
105
+ };
106
+
107
+ assert.isFalse(MemberUtil.canApproveAIEnablement(participant));
108
+ });
109
+
110
+ it('returns true when attendeeRequestAiAssistantNotAllowed is undefined', () => {
111
+ const participant = {
112
+ attendeeRequestAiAssistantNotAllowed: undefined,
113
+ };
114
+
115
+ assert.isTrue(MemberUtil.canApproveAIEnablement(participant));
116
+ });
117
+
118
+ it('returns true when attendeeRequestAiAssistantNotAllowed is not present', () => {
119
+ const participant = {};
120
+
121
+ assert.isTrue(MemberUtil.canApproveAIEnablement(participant));
122
+ });
123
+ });
124
+
85
125
  describe('MemberUtil.extractControlRoles', () => {
86
126
  it('happy path extract control roles', () => {
87
127
  const participant = {
@@ -377,7 +417,6 @@ describe('plugin-meetings', () => {
377
417
  assert.isFalse(MemberUtil.isBrb(participant));
378
418
  });
379
419
 
380
-
381
420
  it('returns false when brb is not present', () => {
382
421
  const participant = {
383
422
  controls: {},
@@ -417,29 +456,28 @@ describe('plugin-meetings', () => {
417
456
  });
418
457
  });
419
458
 
420
- describe('MemberUtil.isSupportsSingleUserAutoEndMeeting', () => {
421
- it('throws an error when there is no participant', () => {
422
- assert.throws(() => {
423
- MemberUtil.isSupportsSingleUserAutoEndMeeting();
424
- }, 'Single user auto end meeting support could not be processed, participant is undefined.');
425
- });
459
+ describe('MemberUtil.isSupportsSingleUserAutoEndMeeting', () => {
460
+ it('throws an error when there is no participant', () => {
461
+ assert.throws(() => {
462
+ MemberUtil.isSupportsSingleUserAutoEndMeeting();
463
+ }, 'Single user auto end meeting support could not be processed, participant is undefined.');
464
+ });
426
465
 
427
- it('returns true when single user auto end meeting is supported', () => {
428
- const participant = {
429
- supportsSingleUserAutoEndMeeting: {},
430
- };
431
- assert.isTrue(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
432
- });
466
+ it('returns true when single user auto end meeting is supported', () => {
467
+ const participant = {
468
+ supportsSingleUserAutoEndMeeting: {},
469
+ };
470
+ assert.isTrue(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
471
+ });
433
472
 
434
- it('returns false when single user auto end meeting is not supported', () => {
435
- const participant = {
436
- doesNotSupportSingleUserAutoEndMeeting: {},
437
- };
473
+ it('returns false when single user auto end meeting is not supported', () => {
474
+ const participant = {
475
+ doesNotSupportSingleUserAutoEndMeeting: {},
476
+ };
438
477
 
439
- assert.isFalse(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
478
+ assert.isFalse(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
479
+ });
440
480
  });
441
- });
442
-
443
481
 
444
482
  describe('MemberUtil.isLiveAnnotationSupported', () => {
445
483
  it('throws an error when there is no participant', () => {
@@ -585,7 +623,7 @@ describe('MemberUtil.isSupportsSingleUserAutoEndMeeting', () => {
585
623
  describe('MemberUtil.isPresenterAssignmentProhibited', () => {
586
624
  it('returns true when isPresenterAssignmentProhibited is true', () => {
587
625
  const participant = {
588
- presenterAssignmentNotAllowed: true
626
+ presenterAssignmentNotAllowed: true,
589
627
  };
590
628
 
591
629
  assert.isTrue(MemberUtil.isPresenterAssignmentProhibited(participant));
@@ -610,16 +648,16 @@ describe('MemberUtil.isSupportsSingleUserAutoEndMeeting', () => {
610
648
  describe('extractMediaStatus', () => {
611
649
  it('throws an error when there is no participant', () => {
612
650
  assert.throws(() => {
613
- MemberUtil.extractMediaStatus()
651
+ MemberUtil.extractMediaStatus();
614
652
  }, 'Media status could not be extracted, participant is undefined.');
615
653
  });
616
654
 
617
655
  it('returns undefined media status when participant audio/video status is not present', () => {
618
656
  const participant = {
619
- status: {}
657
+ status: {},
620
658
  };
621
659
 
622
- const mediaStatus = MemberUtil.extractMediaStatus(participant)
660
+ const mediaStatus = MemberUtil.extractMediaStatus(participant);
623
661
 
624
662
  assert.deepEqual(mediaStatus, {audio: undefined, video: undefined});
625
663
  });
@@ -628,11 +666,11 @@ describe('extractMediaStatus', () => {
628
666
  const participant = {
629
667
  status: {
630
668
  audioStatus: 'RECVONLY',
631
- videoStatus: 'SENDRECV'
632
- }
669
+ videoStatus: 'SENDRECV',
670
+ },
633
671
  };
634
672
 
635
- const mediaStatus = MemberUtil.extractMediaStatus(participant)
673
+ const mediaStatus = MemberUtil.extractMediaStatus(participant);
636
674
 
637
675
  assert.deepEqual(mediaStatus, {audio: 'RECVONLY', video: 'SENDRECV'});
638
676
  });
@@ -666,8 +666,8 @@ describe('MediaRequestManager', () => {
666
666
  ]);
667
667
  });
668
668
 
669
- it('avoids sending duplicate requests and clears all the requests on reset()', () => {
670
- // send some requests and commit them one by one
669
+ it('clears all the requests on reset()', () => {
670
+ // send some requests and commit them
671
671
  addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
672
672
  addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
673
673
  addActiveSpeakerRequest(
@@ -722,95 +722,12 @@ describe('MediaRequestManager', () => {
722
722
  },
723
723
  ]);
724
724
 
725
- // check that when calling commit()
726
- // all requests are not re-sent again (avoid duplicate requests)
727
- mediaRequestManager.commit();
728
-
729
- assert.notCalled(sendMediaRequestsCallback);
730
-
731
- // now reset everything
732
- mediaRequestManager.reset();
733
-
734
- // calling commit now should not cause any requests to be sent out
735
- mediaRequestManager.commit();
736
- checkMediaRequestsSent([]);
737
- });
738
-
739
- it('makes sure to call requests correctly after reset was called and another request was added', () => {
740
- addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
741
-
742
- assert.notCalled(sendMediaRequestsCallback);
743
-
744
- mediaRequestManager.commit();
745
- checkMediaRequestsSent([
746
- {
747
- policy: 'receiver-selected',
748
- csi: 1500,
749
- receiveSlot: fakeWcmeSlots[0],
750
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
751
- maxFs: MAX_FS_1080p,
752
- maxMbps: MAX_MBPS_1080p,
753
- },
754
- ]);
755
-
756
725
  // now reset everything
757
726
  mediaRequestManager.reset();
758
727
 
759
728
  // calling commit now should not cause any requests to be sent out
760
729
  mediaRequestManager.commit();
761
730
  checkMediaRequestsSent([]);
762
-
763
- //add new request
764
- addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
765
-
766
- // commit
767
- mediaRequestManager.commit();
768
-
769
- // check the new request was sent
770
- checkMediaRequestsSent([
771
- {
772
- policy: 'receiver-selected',
773
- csi: 1501,
774
- receiveSlot: fakeWcmeSlots[1],
775
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
776
- maxFs: MAX_FS_1080p,
777
- maxMbps: MAX_MBPS_1080p,
778
- },
779
- ]);
780
- });
781
-
782
- it('can send same media request after previous requests have been cleared', () => {
783
- // add a request and commit
784
- addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
785
- mediaRequestManager.commit();
786
- checkMediaRequestsSent([
787
- {
788
- policy: 'receiver-selected',
789
- csi: 1500,
790
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
791
- receiveSlot: fakeWcmeSlots[0],
792
- maxFs: MAX_FS_1080p,
793
- maxMbps: MAX_MBPS_1080p,
794
- },
795
- ]);
796
-
797
- // clear previous requests
798
- mediaRequestManager.clearPreviousRequests();
799
-
800
- // commit same request
801
- mediaRequestManager.commit();
802
-
803
- // check the request was sent
804
- checkMediaRequestsSent([
805
- {
806
- policy: 'receiver-selected',
807
- csi: 1500,
808
- maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
809
- receiveSlot: fakeWcmeSlots[0],
810
- maxFs: MAX_FS_1080p,
811
- maxMbps: MAX_MBPS_1080p,
812
- },
813
- ]);
814
731
  });
815
732
 
816
733
  it('re-sends media requests after degradation preferences are set', () => {
@@ -965,6 +965,36 @@ describe('RemoteMediaManager', () => {
965
965
  );
966
966
  });
967
967
 
968
+ it('allocates 25 video slots for AllEqual25 layout', async () => {
969
+ const config = cloneDeep(DefaultTestConfiguration);
970
+ config.video.layouts['AllEqual25'] = {
971
+ activeSpeakerVideoPaneGroups: [
972
+ {id: 'main', numPanes: 25, size: 'best', priority: 255},
973
+ ],
974
+ };
975
+ config.video.initialLayoutId = 'AllEqual25';
976
+
977
+ let slotCount = 0;
978
+ fakeReceiveSlotManager.allocateSlot.callsFake((mediaType: MediaType) => {
979
+ if (mediaType === MediaType.VideoMain) {
980
+ slotCount += 1;
981
+ return Promise.resolve(new FakeSlot(mediaType, `fake video ${slotCount}`));
982
+ }
983
+ return Promise.resolve(fakeAudioSlot);
984
+ });
985
+
986
+ remoteMediaManager = new RemoteMediaManager(
987
+ fakeReceiveSlotManager,
988
+ fakeMediaRequestManagers,
989
+ config
990
+ );
991
+
992
+ await remoteMediaManager.start();
993
+
994
+ assert.strictEqual(remoteMediaManager.getLayoutId(), 'AllEqual25');
995
+ assert.strictEqual(remoteMediaManager.slots.video.activeSpeaker.length, 25);
996
+ });
997
+
968
998
  it('releases slots when switching to layout that requires less active speaker slots', async () => {
969
999
  // start with "AllEqual" layout that needs just 9 video slots
970
1000
  const config = cloneDeep(DefaultTestConfiguration);
@@ -1,19 +1,28 @@
1
1
  import 'jsdom-global/register';
2
2
  import SendSlotManager from '@webex/plugin-meetings/src/multistream/sendSlotManager';
3
- import { LocalStream, MediaType, MultistreamRoapMediaConnection } from "@webex/internal-media-core";
4
- import {expect} from '@webex/test-helper-chai';
3
+ import { LocalStream, MediaType, MultistreamRoapMediaConnection, MediaCodecMimeType } from "@webex/internal-media-core";
4
+ import {assert, expect} from '@webex/test-helper-chai';
5
5
  import sinon from 'sinon';
6
+ import Metrics from '@webex/plugin-meetings/src/metrics';
7
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
6
8
 
7
9
  describe('SendSlotsManager', () => {
8
10
  let sendSlotsManager: SendSlotManager;
9
11
  const LoggerProxy = {
10
12
  logger: {
11
13
  info: sinon.stub(),
14
+ warn: sinon.stub(),
15
+ error: sinon.stub(),
12
16
  },
13
17
  };
14
18
 
15
19
  beforeEach(() => {
16
20
  sendSlotsManager = new SendSlotManager(LoggerProxy);
21
+ sinon.stub(Metrics, 'sendBehavioralMetric');
22
+ });
23
+
24
+ afterEach(() => {
25
+ sinon.restore();
17
26
  });
18
27
 
19
28
  describe('createSlot', () => {
@@ -29,13 +38,13 @@ describe('SendSlotsManager', () => {
29
38
  it('should create a slot for the given mediaType', () => {
30
39
  sendSlotsManager.createSlot(mediaConnection, mediaType);
31
40
 
32
- expect(mediaConnection.createSendSlot.calledWith(mediaType, true));
41
+ assert.calledWith(mediaConnection.createSendSlot, mediaType, true);
33
42
  });
34
43
 
35
44
  it('should create a slot for the given mediaType & active state', () => {
36
45
  sendSlotsManager.createSlot(mediaConnection, mediaType, false);
37
46
 
38
- expect(mediaConnection.createSendSlot.calledWith(mediaType, false));
47
+ assert.calledWith(mediaConnection.createSendSlot, mediaType, false);
39
48
  });
40
49
 
41
50
  it('should throw an error if a slot for the given mediaType already exists', () => {
@@ -86,14 +95,12 @@ describe('SendSlotsManager', () => {
86
95
 
87
96
  await sendSlotsManager.publishStream(mediaType, stream);
88
97
 
89
- expect(slot.publishStream.calledWith(stream));
98
+ assert.calledWith(slot.publishStream, stream);
90
99
  });
91
100
 
92
- it('should throw an error if a slot for the given mediaType does not exist', (done) => {
93
- sendSlotsManager.publishStream(mediaType, stream).catch((error) => {
94
- expect(error.message).to.equal(`Slot for ${mediaType} does not exist`);
95
- done();
96
- });
101
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
102
+ await expect(sendSlotsManager.publishStream(mediaType, stream))
103
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
97
104
  });
98
105
  });
99
106
 
@@ -116,14 +123,12 @@ describe('SendSlotsManager', () => {
116
123
 
117
124
  await sendSlotsManager.unpublishStream(mediaType);
118
125
 
119
- expect(slot.unpublishStream.called);
126
+ assert.called(slot.unpublishStream);
120
127
  });
121
128
 
122
- it('should throw an error if a slot for the given mediaType does not exist',(done) => {
123
- sendSlotsManager.unpublishStream(mediaType).catch((error) => {
124
- expect(error.message).to.equal(`Slot for ${mediaType} does not exist`);
125
- done();
126
- });
129
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
130
+ await expect(sendSlotsManager.unpublishStream(mediaType))
131
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
127
132
  });
128
133
  });
129
134
 
@@ -147,7 +152,7 @@ describe('SendSlotsManager', () => {
147
152
 
148
153
  await sendSlotsManager.setNamedMediaGroups(mediaType, groups);
149
154
 
150
- expect(slot.setNamedMediaGroups.calledWith(groups));
155
+ assert.calledWith(slot.setNamedMediaGroups, groups);
151
156
  });
152
157
 
153
158
  it('should throw an error if the given mediaType is not audio', () => {
@@ -169,16 +174,16 @@ describe('SendSlotsManager', () => {
169
174
  } as MultistreamRoapMediaConnection;
170
175
  });
171
176
 
172
- it('should set the active state of the sendSlot for the given mediaType', async () => {
177
+ it('should set the active state of the sendSlot for the given mediaType', () => {
173
178
  const slot = {
174
- setActive: sinon.stub().resolves(),
179
+ active: false,
175
180
  };
176
181
  mediaConnection.createSendSlot.returns(slot);
177
182
  sendSlotsManager.createSlot(mediaConnection, mediaType);
178
183
 
179
- await sendSlotsManager.setActive(mediaType,true);
184
+ sendSlotsManager.setActive(mediaType, true);
180
185
 
181
- expect(slot.setActive.called);
186
+ expect(slot.active).to.be.true;
182
187
  });
183
188
 
184
189
  it('should throw an error if a slot for the given mediaType does not exist', () => {
@@ -197,7 +202,7 @@ describe('SendSlotsManager', () => {
197
202
  } as MultistreamRoapMediaConnection;
198
203
  });
199
204
 
200
- it('should set the codec parameters of the sendSlot for the given mediaType', async () => {
205
+ it('should delegate to slot.setCodecParameters, log deprecation warning and send deprecation metric', async () => {
201
206
  const slot = {
202
207
  setCodecParameters: sinon.stub().resolves(),
203
208
  };
@@ -206,14 +211,17 @@ describe('SendSlotsManager', () => {
206
211
 
207
212
  await sendSlotsManager.setCodecParameters(mediaType, codecParameters);
208
213
 
209
- expect(slot.setCodecParameters.calledWith(codecParameters));
214
+ assert.calledWith(slot.setCodecParameters, codecParameters);
215
+ assert.called(LoggerProxy.logger.warn);
216
+ assert.calledWith(Metrics.sendBehavioralMetric as sinon.SinonStub,
217
+ BEHAVIORAL_METRICS.DEPRECATED_SET_CODEC_PARAMETERS_USED,
218
+ { mediaType, codecParameters }
219
+ );
210
220
  });
211
221
 
212
- it('should throw an error if a slot for the given mediaType does not exist', (done) => {
213
- sendSlotsManager.setCodecParameters(mediaType, codecParameters).catch((error) => {
214
- expect(error.message).to.equal(`Slot for ${mediaType} does not exist`);
215
- done();
216
- });
222
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
223
+ await expect(sendSlotsManager.setCodecParameters(mediaType, codecParameters))
224
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
217
225
  });
218
226
  });
219
227
 
@@ -227,23 +235,114 @@ describe('SendSlotsManager', () => {
227
235
  } as MultistreamRoapMediaConnection;
228
236
  });
229
237
 
230
- it('should delete the codec parameters of the sendSlot for the given mediaType', async () => {
238
+ it('should delegate to slot.deleteCodecParameters, log deprecation warning and send deprecation metric', async () => {
231
239
  const slot = {
232
240
  deleteCodecParameters: sinon.stub().resolves(),
233
241
  };
234
242
  mediaConnection.createSendSlot.returns(slot);
235
243
  sendSlotsManager.createSlot(mediaConnection, mediaType);
236
244
 
237
- await sendSlotsManager.deleteCodecParameters(mediaType,[]);
245
+ await sendSlotsManager.deleteCodecParameters(mediaType, []);
246
+
247
+ assert.calledWith(slot.deleteCodecParameters, []);
248
+ assert.called(LoggerProxy.logger.warn);
249
+ assert.calledWith(Metrics.sendBehavioralMetric as sinon.SinonStub,
250
+ BEHAVIORAL_METRICS.DEPRECATED_DELETE_CODEC_PARAMETERS_USED,
251
+ { mediaType, parameters: [] }
252
+ );
253
+ });
254
+
255
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
256
+ await expect(sendSlotsManager.deleteCodecParameters(mediaType, []))
257
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
258
+ });
259
+ });
260
+
261
+ describe('setCustomCodecParameters', () => {
262
+ let mediaConnection;
263
+ const mediaType = MediaType.AudioMain;
264
+ const codecMimeType = MediaCodecMimeType.OPUS;
265
+ const parameters = { maxaveragebitrate: '64000' };
266
+
267
+ beforeEach(() => {
268
+ mediaConnection = {
269
+ createSendSlot: sinon.stub(),
270
+ } as MultistreamRoapMediaConnection;
271
+ });
272
+
273
+ it('should set custom codec parameters on the sendSlot for the given mediaType and codec, log info and send metric', async () => {
274
+ const slot = {
275
+ setCustomCodecParameters: sinon.stub().resolves(),
276
+ };
277
+ mediaConnection.createSendSlot.returns(slot);
278
+ sendSlotsManager.createSlot(mediaConnection, mediaType);
279
+
280
+ await sendSlotsManager.setCustomCodecParameters(mediaType, codecMimeType, parameters);
281
+
282
+ assert.calledWith(slot.setCustomCodecParameters, codecMimeType, parameters);
283
+ assert.called(LoggerProxy.logger.info);
284
+ assert.calledWith(Metrics.sendBehavioralMetric as sinon.SinonStub,
285
+ BEHAVIORAL_METRICS.SET_CUSTOM_CODEC_PARAMETERS_USED,
286
+ { mediaType, codecMimeType, parameters }
287
+ );
288
+ });
289
+
290
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
291
+ await expect(sendSlotsManager.setCustomCodecParameters(mediaType, codecMimeType, parameters))
292
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
293
+ });
294
+
295
+ it('should throw and log error when setCustomCodecParameters fails', async () => {
296
+ const error = new Error('codec parameter failure');
297
+ const slot = {
298
+ setCustomCodecParameters: sinon.stub().rejects(error),
299
+ };
300
+ mediaConnection.createSendSlot.returns(slot);
301
+ sendSlotsManager.createSlot(mediaConnection, mediaType);
302
+
303
+ await expect(sendSlotsManager.setCustomCodecParameters(mediaType, codecMimeType, parameters))
304
+ .to.be.rejectedWith('codec parameter failure');
305
+
306
+ assert.called(LoggerProxy.logger.error);
307
+ assert.calledWith(Metrics.sendBehavioralMetric as sinon.SinonStub,
308
+ BEHAVIORAL_METRICS.SET_CUSTOM_CODEC_PARAMETERS_USED,
309
+ { mediaType, codecMimeType, parameters }
310
+ );
311
+ });
312
+ });
313
+
314
+ describe('markCustomCodecParametersForDeletion', () => {
315
+ let mediaConnection;
316
+ const mediaType = MediaType.AudioMain;
317
+ const codecMimeType = MediaCodecMimeType.OPUS;
318
+ const parameters = ['maxaveragebitrate', 'maxplaybackrate'];
319
+
320
+ beforeEach(() => {
321
+ mediaConnection = {
322
+ createSendSlot: sinon.stub(),
323
+ } as MultistreamRoapMediaConnection;
324
+ });
325
+
326
+ it('should mark custom codec parameters for deletion on the sendSlot for the given mediaType and codec, log info and send metric', async () => {
327
+ const slot = {
328
+ markCustomCodecParametersForDeletion: sinon.stub().resolves(),
329
+ };
330
+ mediaConnection.createSendSlot.returns(slot);
331
+ sendSlotsManager.createSlot(mediaConnection, mediaType);
332
+
333
+ await sendSlotsManager.markCustomCodecParametersForDeletion(mediaType, codecMimeType, parameters);
238
334
 
239
- expect(slot.deleteCodecParameters.called);
335
+ assert.calledWith(slot.markCustomCodecParametersForDeletion, codecMimeType, parameters);
336
+ assert.called(LoggerProxy.logger.info);
337
+ assert.calledWith(Metrics.sendBehavioralMetric as sinon.SinonStub,
338
+ BEHAVIORAL_METRICS.MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED,
339
+ { mediaType, codecMimeType, parameters }
340
+ );
240
341
  });
241
342
 
242
- it('should throw an error if a slot for the given mediaType does not exist', (done) => {
243
- sendSlotsManager.deleteCodecParameters(mediaType,[]).catch((error) => {
244
- expect(error.message).to.equal(`Slot for ${mediaType} does not exist`);
245
- done();
246
- });
343
+ it('should throw an error if a slot for the given mediaType does not exist', async () => {
344
+ await expect(sendSlotsManager.markCustomCodecParametersForDeletion(mediaType, codecMimeType, parameters))
345
+ .to.be.rejectedWith(`Slot for ${mediaType} does not exist`);
247
346
  });
248
347
  });
249
348
 
@@ -1,5 +1,6 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
  import MockWebex from '@webex/test-helper-mock-webex';
3
+ import { CapabilityState, WebCapabilities } from '@webex/web-capabilities';
3
4
  import sinon from 'sinon';
4
5
  import EventEmitter from 'events';
5
6
  import testUtils from '../../../utils/testUtils';
@@ -8,6 +9,7 @@ import {ClusterNode} from '../../../../src/reachability/request';
8
9
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
9
10
  import * as ClusterReachabilityModule from '@webex/plugin-meetings/src/reachability/clusterReachability';
10
11
  import Metrics from '@webex/plugin-meetings/src/metrics';
12
+ import * as InternalMediaCore from '@webex/internal-media-core';
11
13
 
12
14
  import {IP_VERSION} from '@webex/plugin-meetings/src/constants';
13
15
  import { ReachabilityResultsForBackend } from '@webex/plugin-meetings/src/reachability/reachability.types';
@@ -466,11 +468,13 @@ describe('gatherReachability', () => {
466
468
  let clock;
467
469
  let clusterReachabilityCtorStub;
468
470
  let mockClusterReachabilityInstances: Record<string, MockClusterReachability>;
471
+ let supportsRTCPeerConnectionStub;
469
472
 
470
473
  beforeEach(async () => {
471
474
  webex = new MockWebex();
472
475
 
473
476
  sinon.stub(Metrics, 'sendBehavioralMetric');
477
+ supportsRTCPeerConnectionStub = sinon.stub(WebCapabilities, 'supportsRTCPeerConnection').returns(CapabilityState.CAPABLE);
474
478
 
475
479
  await webex.boundedStorage.put(
476
480
  'Reachability',
@@ -545,6 +549,25 @@ describe('gatherReachability', () => {
545
549
  await assert.isRejected(reachability.gatherReachability('test'), 'enableReachabilityChecks is disabled in config');
546
550
  });
547
551
 
552
+ [CapabilityState.NOT_CAPABLE, CapabilityState.UNKNOWN].forEach((capabilityState) =>
553
+ it(`returns empty object if WebRTC API is not available (capabilityState=${capabilityState}`, async () => {
554
+ supportsRTCPeerConnectionStub.returns(capabilityState);
555
+
556
+ const reachability = new Reachability(webex);
557
+
558
+ const result = await reachability.gatherReachability('test');
559
+
560
+ assert.deepEqual(result, {});
561
+
562
+ // Verify that no new reachability result was stored - old results should remain unchanged
563
+ // This check is mainly to ensure that we don't put any "unreachable" results into storage
564
+ const storedResults = await webex.boundedStorage.get('Reachability', 'reachability.result');
565
+ assert.equal(storedResults, JSON.stringify({old: 'results'}));
566
+
567
+ assert.equal(await reachability.isWebexMediaBackendUnreachable(), false);
568
+ })
569
+ );
570
+
548
571
  [
549
572
  // ========================================================================
550
573
  {