@webex/plugin-meetings 3.11.0-webex-services-ready.1 → 3.12.0-mobius-socket.1

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 (171) 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 +1293 -929
  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/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +4 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +60 -8
  88. package/dist/types/locus-info/types.d.ts +7 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +72 -7
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +5 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  102. package/dist/types/reactions/reactions.type.d.ts +1 -0
  103. package/dist/types/webinar/utils.d.ts +6 -0
  104. package/dist/webinar/index.js +438 -163
  105. package/dist/webinar/index.js.map +1 -1
  106. package/dist/webinar/utils.js +25 -0
  107. package/dist/webinar/utils.js.map +1 -0
  108. package/package.json +24 -23
  109. package/src/aiEnableRequest/README.md +84 -0
  110. package/src/aiEnableRequest/index.ts +170 -0
  111. package/src/aiEnableRequest/utils.ts +25 -0
  112. package/src/annotation/index.ts +27 -7
  113. package/src/config.ts +4 -0
  114. package/src/constants.ts +29 -1
  115. package/src/hashTree/constants.ts +1 -0
  116. package/src/hashTree/hashTree.ts +17 -0
  117. package/src/hashTree/hashTreeParser.ts +761 -260
  118. package/src/hashTree/types.ts +4 -0
  119. package/src/hashTree/utils.ts +9 -0
  120. package/src/index.ts +8 -1
  121. package/src/interceptors/constant.ts +6 -0
  122. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  123. package/src/interceptors/index.ts +2 -1
  124. package/src/interceptors/utils.ts +16 -0
  125. package/src/interpretation/index.ts +2 -2
  126. package/src/locus-info/controlsUtils.ts +11 -0
  127. package/src/locus-info/index.ts +579 -113
  128. package/src/locus-info/selfUtils.ts +1 -0
  129. package/src/locus-info/types.ts +8 -0
  130. package/src/media/MediaConnectionAwaiter.ts +41 -1
  131. package/src/media/properties.ts +3 -1
  132. package/src/meeting/in-meeting-actions.ts +12 -0
  133. package/src/meeting/index.ts +372 -86
  134. package/src/meeting/request.ts +42 -0
  135. package/src/meeting/request.type.ts +6 -0
  136. package/src/meeting/util.ts +160 -2
  137. package/src/meetings/index.ts +157 -44
  138. package/src/member/index.ts +10 -0
  139. package/src/member/util.ts +12 -0
  140. package/src/metrics/constants.ts +6 -0
  141. package/src/multistream/mediaRequestManager.ts +4 -54
  142. package/src/multistream/remoteMediaManager.ts +13 -0
  143. package/src/multistream/sendSlotManager.ts +97 -3
  144. package/src/reactions/reactions.type.ts +1 -0
  145. package/src/reconnection-manager/index.ts +0 -1
  146. package/src/webinar/index.ts +265 -6
  147. package/src/webinar/utils.ts +16 -0
  148. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  149. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  150. package/test/unit/spec/annotation/index.ts +69 -7
  151. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  152. package/test/unit/spec/hashTree/hashTreeParser.ts +2321 -175
  153. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  154. package/test/unit/spec/interceptors/utils.ts +75 -0
  155. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  156. package/test/unit/spec/locus-info/index.js +1134 -55
  157. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  158. package/test/unit/spec/media/properties.ts +12 -3
  159. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  160. package/test/unit/spec/meeting/index.js +829 -121
  161. package/test/unit/spec/meeting/request.js +70 -0
  162. package/test/unit/spec/meeting/utils.js +438 -26
  163. package/test/unit/spec/meetings/index.js +653 -32
  164. package/test/unit/spec/member/index.js +28 -4
  165. package/test/unit/spec/member/util.js +65 -27
  166. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  167. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  168. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  169. package/test/unit/spec/reconnection-manager/index.js +4 -8
  170. package/test/unit/spec/webinar/index.ts +534 -37
  171. 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
 
@@ -54,8 +54,8 @@ describe('plugin-meetings', () => {
54
54
  webrtcMediaConnection: fakeMediaConnection,
55
55
  },
56
56
  mediaRequestManagers: {
57
- audio: {commit: sinon.stub(), clearPreviousRequests: sinon.stub()},
58
- video: {commit: sinon.stub(), clearPreviousRequests: sinon.stub()},
57
+ audio: {commit: sinon.stub()},
58
+ video: {commit: sinon.stub()},
59
59
  },
60
60
  roap: {
61
61
  doTurnDiscovery: sinon.stub().resolves({
@@ -179,26 +179,22 @@ describe('plugin-meetings', () => {
179
179
  });
180
180
  });
181
181
 
182
- it('does not clear previous requests and re-request media for non-multistream meetings', async () => {
182
+ it('does not re-request media for non-multistream meetings', async () => {
183
183
  fakeMeeting.isMultistream = false;
184
184
  const rm = new ReconnectionManager(fakeMeeting);
185
185
 
186
186
  await rm.reconnect();
187
187
 
188
- assert.notCalled(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
189
- assert.notCalled(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
190
188
  assert.notCalled(fakeMeeting.mediaRequestManagers.audio.commit);
191
189
  assert.notCalled(fakeMeeting.mediaRequestManagers.video.commit);
192
190
  });
193
191
 
194
- it('does clear previous requests and re-request media for multistream meetings', async () => {
192
+ it('does re-request media for multistream meetings', async () => {
195
193
  fakeMeeting.isMultistream = true;
196
194
  const rm = new ReconnectionManager(fakeMeeting);
197
195
 
198
196
  await rm.reconnect();
199
197
 
200
- assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
201
- assert.calledOnce(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
202
198
  assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.commit);
203
199
  assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
204
200
  });