@webex/plugin-meetings 3.0.0-beta.17 → 3.0.0-beta.18

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/breakouts/breakout.js +116 -0
  2. package/dist/breakouts/breakout.js.map +1 -0
  3. package/dist/breakouts/collection.js +23 -0
  4. package/dist/breakouts/collection.js.map +1 -0
  5. package/dist/breakouts/index.js +226 -0
  6. package/dist/breakouts/index.js.map +1 -0
  7. package/dist/config.js +4 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +38 -5
  10. package/dist/constants.js.map +1 -1
  11. package/dist/locus-info/controlsUtils.js +2 -1
  12. package/dist/locus-info/controlsUtils.js.map +1 -1
  13. package/dist/locus-info/index.js +48 -0
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/locus-info/parser.js +1 -0
  16. package/dist/locus-info/parser.js.map +1 -1
  17. package/dist/locus-info/selfUtils.js +19 -11
  18. package/dist/locus-info/selfUtils.js.map +1 -1
  19. package/dist/media/index.js +3 -3
  20. package/dist/media/index.js.map +1 -1
  21. package/dist/media/properties.js +4 -4
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/index.js +651 -460
  24. package/dist/meeting/index.js.map +1 -1
  25. package/dist/meeting/request.js +25 -44
  26. package/dist/meeting/request.js.map +1 -1
  27. package/dist/meeting/request.type.js.map +1 -1
  28. package/dist/meeting/util.js +4 -57
  29. package/dist/meeting/util.js.map +1 -1
  30. package/dist/meeting-info/meeting-info-v2.js +2 -0
  31. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  32. package/dist/meetings/index.js +28 -18
  33. package/dist/meetings/index.js.map +1 -1
  34. package/dist/meetings/request.js +14 -12
  35. package/dist/meetings/request.js.map +1 -1
  36. package/dist/member/index.js +9 -0
  37. package/dist/member/index.js.map +1 -1
  38. package/dist/member/util.js +14 -1
  39. package/dist/member/util.js.map +1 -1
  40. package/dist/members/index.js +8 -6
  41. package/dist/members/index.js.map +1 -1
  42. package/dist/members/request.js +3 -1
  43. package/dist/members/request.js.map +1 -1
  44. package/dist/multistream/mediaRequestManager.js +46 -6
  45. package/dist/multistream/mediaRequestManager.js.map +1 -1
  46. package/dist/multistream/multistreamMedia.js +4 -0
  47. package/dist/multistream/multistreamMedia.js.map +1 -1
  48. package/dist/multistream/receiveSlot.js +3 -3
  49. package/dist/multistream/receiveSlot.js.map +1 -1
  50. package/dist/multistream/receiveSlotManager.js +8 -6
  51. package/dist/multistream/receiveSlotManager.js.map +1 -1
  52. package/dist/multistream/remoteMedia.js.map +1 -1
  53. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  54. package/dist/multistream/remoteMediaManager.js +168 -63
  55. package/dist/multistream/remoteMediaManager.js.map +1 -1
  56. package/dist/reachability/index.js +63 -51
  57. package/dist/reachability/index.js.map +1 -1
  58. package/dist/reactions/constants.js +13 -0
  59. package/dist/reactions/constants.js.map +1 -0
  60. package/dist/reactions/reactions.type.js.map +1 -1
  61. package/dist/reconnection-manager/index.js +25 -12
  62. package/dist/reconnection-manager/index.js.map +1 -1
  63. package/dist/recording-controller/enums.js +17 -0
  64. package/dist/recording-controller/enums.js.map +1 -0
  65. package/dist/recording-controller/index.js +343 -0
  66. package/dist/recording-controller/index.js.map +1 -0
  67. package/dist/recording-controller/util.js +63 -0
  68. package/dist/recording-controller/util.js.map +1 -0
  69. package/dist/roap/request.js +88 -68
  70. package/dist/roap/request.js.map +1 -1
  71. package/dist/roap/turnDiscovery.js +72 -47
  72. package/dist/roap/turnDiscovery.js.map +1 -1
  73. package/dist/statsAnalyzer/index.js +3 -3
  74. package/dist/statsAnalyzer/index.js.map +1 -1
  75. package/dist/statsAnalyzer/mqaUtil.js +18 -6
  76. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  77. package/package.json +24 -19
  78. package/src/breakouts/README.md +190 -0
  79. package/src/breakouts/breakout.ts +110 -0
  80. package/src/breakouts/collection.ts +19 -0
  81. package/src/breakouts/index.ts +225 -0
  82. package/src/config.ts +4 -1
  83. package/src/constants.ts +35 -1
  84. package/src/locus-info/controlsUtils.ts +2 -0
  85. package/src/locus-info/index.ts +59 -1
  86. package/src/locus-info/parser.ts +1 -0
  87. package/src/locus-info/selfUtils.ts +8 -0
  88. package/src/media/index.ts +1 -2
  89. package/src/media/properties.ts +6 -9
  90. package/src/meeting/index.ts +352 -111
  91. package/src/meeting/request.ts +9 -31
  92. package/src/meeting/request.type.ts +2 -0
  93. package/src/meeting/util.ts +3 -60
  94. package/src/meeting-info/meeting-info-v2.ts +2 -0
  95. package/src/meetings/index.ts +10 -5
  96. package/src/meetings/request.ts +1 -1
  97. package/src/member/index.ts +9 -0
  98. package/src/member/util.ts +14 -1
  99. package/src/members/index.ts +1 -0
  100. package/src/members/request.ts +1 -0
  101. package/src/multistream/mediaRequestManager.ts +79 -15
  102. package/src/multistream/multistreamMedia.ts +4 -0
  103. package/src/multistream/receiveSlot.ts +17 -12
  104. package/src/multistream/receiveSlotManager.ts +22 -21
  105. package/src/multistream/remoteMedia.ts +1 -1
  106. package/src/multistream/remoteMediaGroup.ts +2 -2
  107. package/src/multistream/remoteMediaManager.ts +150 -37
  108. package/src/reachability/index.ts +16 -13
  109. package/src/reactions/constants.ts +4 -0
  110. package/src/reactions/reactions.type.ts +25 -0
  111. package/src/reconnection-manager/index.ts +18 -9
  112. package/src/recording-controller/enums.ts +8 -0
  113. package/src/recording-controller/index.ts +315 -0
  114. package/src/recording-controller/util.ts +58 -0
  115. package/src/roap/request.ts +78 -73
  116. package/src/roap/turnDiscovery.ts +8 -6
  117. package/src/statsAnalyzer/index.ts +4 -4
  118. package/src/statsAnalyzer/mqaUtil.ts +6 -0
  119. package/test/unit/spec/breakouts/breakout.ts +119 -0
  120. package/test/unit/spec/breakouts/collection.ts +15 -0
  121. package/test/unit/spec/breakouts/index.ts +293 -0
  122. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  123. package/test/unit/spec/locus-info/index.js +103 -0
  124. package/test/unit/spec/locus-info/selfConstant.js +25 -0
  125. package/test/unit/spec/locus-info/selfUtils.js +84 -0
  126. package/test/unit/spec/media/index.ts +1 -1
  127. package/test/unit/spec/media/properties.ts +9 -9
  128. package/test/unit/spec/meeting/effectsState.js +5 -1
  129. package/test/unit/spec/meeting/index.js +235 -50
  130. package/test/unit/spec/meeting/request.js +17 -0
  131. package/test/unit/spec/meeting/utils.js +20 -129
  132. package/test/unit/spec/meetings/index.js +1 -0
  133. package/test/unit/spec/member/util.js +26 -1
  134. package/test/unit/spec/multistream/mediaRequestManager.ts +312 -50
  135. package/test/unit/spec/multistream/receiveSlot.ts +6 -6
  136. package/test/unit/spec/multistream/receiveSlotManager.ts +13 -13
  137. package/test/unit/spec/multistream/remoteMedia.ts +2 -2
  138. package/test/unit/spec/multistream/remoteMediaGroup.ts +5 -5
  139. package/test/unit/spec/multistream/remoteMediaManager.ts +354 -65
  140. package/test/unit/spec/reachability/index.ts +58 -24
  141. package/test/unit/spec/reconnection-manager/index.js +42 -13
  142. package/test/unit/spec/recording-controller/index.js +231 -0
  143. package/test/unit/spec/recording-controller/util.js +102 -0
  144. package/test/unit/spec/roap/index.ts +2 -1
  145. package/test/unit/spec/roap/request.ts +114 -0
  146. package/test/unit/spec/roap/turnDiscovery.ts +45 -29
  147. package/test/unit/spec/stats-analyzer/index.js +2 -2
  148. package/test/utils/webex-test-users.js +1 -0
  149. package/tsconfig.json +6 -0
  150. package/dist/media/internal-media-core-wrapper.js +0 -18
  151. package/dist/media/internal-media-core-wrapper.js.map +0 -1
  152. package/src/media/internal-media-core-wrapper.ts +0 -9
@@ -9,6 +9,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
9
9
  import {
10
10
  ALERT,
11
11
  ALTERNATE_REDIRECT_TRUE,
12
+ BREAKOUTS,
12
13
  CALL,
13
14
  CONTROLS,
14
15
  DECLINE,
@@ -55,6 +56,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
55
56
  * @param {boolean} options.pin
56
57
  * @param {boolean} options.moveToResource
57
58
  * @param {Object} options.roapMessage
59
+ * @param {boolean} options.breakoutsSupported
58
60
  * @returns {Promise}
59
61
  */
60
62
  async joinMeeting(options: {
@@ -73,6 +75,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
73
75
  meetingNumber: any;
74
76
  permissionToken: any;
75
77
  preferTranscoding: any;
78
+ breakoutsSupported: boolean;
76
79
  }) {
77
80
  const {
78
81
  asResourceOccupant,
@@ -89,6 +92,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
89
92
  moveToResource,
90
93
  roapMessage,
91
94
  preferTranscoding,
95
+ breakoutsSupported,
92
96
  } = options;
93
97
 
94
98
  LoggerProxy.logger.info('Meeting:request#joinMeeting --> Joining a meeting', correlationId);
@@ -114,6 +118,10 @@ export default class MeetingRequest extends StatelessWebexPlugin {
114
118
  },
115
119
  };
116
120
 
121
+ if (breakoutsSupported) {
122
+ body.deviceCapabilities = [BREAKOUTS.BREAKOUTS_SUPPORTED];
123
+ }
124
+
117
125
  // @ts-ignore
118
126
  if (this.webex.meetings.clientRegion) {
119
127
  // @ts-ignore
@@ -497,37 +505,6 @@ export default class MeetingRequest extends StatelessWebexPlugin {
497
505
  });
498
506
  }
499
507
 
500
- /**
501
- * Make a network request to acknowledge a meeting
502
- * @param {Object} options
503
- * @param {String} options.locusUrl
504
- * @param {String} options.deviceUrl
505
- * @param {String} options.id
506
- * @returns {Promise}
507
- */
508
- recordMeeting(options: {
509
- locusUrl: string;
510
- deviceUrl: string;
511
- id: string;
512
- recording: any;
513
- paused: any;
514
- }) {
515
- const uri = `${options.locusUrl}/${CONTROLS}`;
516
- const body = {
517
- record: {
518
- recording: options.recording,
519
- paused: options.paused,
520
- },
521
- };
522
-
523
- // @ts-ignore
524
- return this.request({
525
- method: HTTP_VERBS.PATCH,
526
- uri,
527
- body,
528
- });
529
- }
530
-
531
508
  lockMeeting(options) {
532
509
  const uri = `${options.locusUrl}/${CONTROLS}`;
533
510
  const body = {
@@ -842,6 +819,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
842
819
  toggleReactions({enable, locusUrl, requestingParticipantId}: ToggleReactionsOptions) {
843
820
  const uri = `${locusUrl}/${CONTROLS}`;
844
821
 
822
+ // @ts-ignore
845
823
  return this.request({
846
824
  method: HTTP_VERBS.PUT,
847
825
  uri,
@@ -1,3 +1,5 @@
1
+ import {Reaction} from '../reactions/reactions.type';
2
+
1
3
  export type SendReactionOptions = {
2
4
  reactionChannelUrl: string;
3
5
  reaction: Reaction;
@@ -105,6 +105,7 @@ MeetingUtil.joinMeeting = (meeting, options) => {
105
105
  moveToResource: options.moveToResource,
106
106
  preferTranscoding: !meeting.isMultistream,
107
107
  asResourceOccupant: options.asResourceOccupant,
108
+ breakoutsSupported: options.breakoutsSupported,
108
109
  })
109
110
  .then((res) => {
110
111
  Metrics.postEvent({
@@ -123,6 +124,8 @@ MeetingUtil.joinMeeting = (meeting, options) => {
123
124
  };
124
125
 
125
126
  MeetingUtil.cleanUp = (meeting) => {
127
+ meeting.breakouts.cleanUp();
128
+
126
129
  // make sure we send last metrics before we close the peerconnection
127
130
  const stopStatsAnalyzer = meeting.statsAnalyzer
128
131
  ? meeting.statsAnalyzer.stopAnalyzer()
@@ -359,66 +362,6 @@ MeetingUtil.canUserUnlock = (displayHints) =>
359
362
  displayHints.includes(DISPLAY_HINTS.LOCK_CONTROL_UNLOCK) &&
360
363
  displayHints.includes(DISPLAY_HINTS.LOCK_STATUS_LOCKED);
361
364
 
362
- MeetingUtil.canUserRecord = (displayHints) =>
363
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START);
364
-
365
- MeetingUtil.canUserPause = (displayHints) =>
366
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE);
367
-
368
- MeetingUtil.canUserResume = (displayHints) =>
369
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME);
370
-
371
- MeetingUtil.canUserStop = (displayHints) =>
372
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP);
373
-
374
- MeetingUtil.startRecording = (request, locusUrl, locusInfo) => {
375
- const displayHints = MeetingUtil.getUserDisplayHintsFromLocusInfo(locusInfo);
376
-
377
- if (MeetingUtil.canUserRecord(displayHints)) {
378
- return request.recordMeeting({locusUrl, recording: true, paused: false});
379
- }
380
-
381
- return Promise.reject(
382
- new PermissionError('Start recording not allowed, due to moderator property.')
383
- );
384
- };
385
-
386
- MeetingUtil.pauseRecording = (request, locusUrl, locusInfo) => {
387
- const displayHints = MeetingUtil.getUserDisplayHintsFromLocusInfo(locusInfo);
388
-
389
- if (MeetingUtil.canUserPause(displayHints)) {
390
- return request.recordMeeting({locusUrl, recording: true, paused: true});
391
- }
392
-
393
- return Promise.reject(
394
- new PermissionError('Pause recording not allowed, due to moderator property.')
395
- );
396
- };
397
-
398
- MeetingUtil.resumeRecording = (request, locusUrl, locusInfo) => {
399
- const displayHints = MeetingUtil.getUserDisplayHintsFromLocusInfo(locusInfo);
400
-
401
- if (MeetingUtil.canUserResume(displayHints)) {
402
- return request.recordMeeting({locusUrl, recording: true, paused: false});
403
- }
404
-
405
- return Promise.reject(
406
- new PermissionError('Resume recording not allowed, due to moderator property.')
407
- );
408
- };
409
-
410
- MeetingUtil.stopRecording = (request, locusUrl, locusInfo) => {
411
- const displayHints = MeetingUtil.getUserDisplayHintsFromLocusInfo(locusInfo);
412
-
413
- if (MeetingUtil.canUserStop(displayHints)) {
414
- return request.recordMeeting({locusUrl, recording: false, paused: false});
415
- }
416
-
417
- return Promise.reject(
418
- new PermissionError('Stop recording not allowed, due to moderator property.')
419
- );
420
- };
421
-
422
365
  MeetingUtil.canUserRaiseHand = (displayHints) => displayHints.includes(DISPLAY_HINTS.RAISE_HAND);
423
366
 
424
367
  MeetingUtil.canUserLowerAllHands = (displayHints) =>
@@ -19,6 +19,7 @@ export class MeetingInfoV2PasswordError extends Error {
19
19
  meetingInfo: any;
20
20
  sdkMessage: any;
21
21
  wbxAppApiCode: any;
22
+ body: any;
22
23
 
23
24
  /**
24
25
  *
@@ -70,6 +71,7 @@ export class MeetingInfoV2CaptchaError extends Error {
70
71
  isPasswordRequired: any;
71
72
  sdkMessage: any;
72
73
  wbxAppApiCode: any;
74
+ body: any;
73
75
  /**
74
76
  *
75
77
  * @constructor
@@ -4,7 +4,7 @@ import '@webex/internal-plugin-mercury';
4
4
  import '@webex/internal-plugin-conversation';
5
5
  // @ts-ignore
6
6
  import {WebexPlugin} from '@webex/webex-core';
7
- import {MediaConnection as MC} from '@webex/internal-media-core';
7
+ import {setLogger} from '@webex/internal-media-core';
8
8
 
9
9
  import 'webrtc-adapter';
10
10
 
@@ -249,6 +249,7 @@ export default class Meetings extends WebexPlugin {
249
249
  // @ts-ignore
250
250
  this.meetingCollection.getByKey(
251
251
  CORRELATION_ID,
252
+ // @ts-ignore
252
253
  MeetingsUtil.checkForCorrelationId(this.webex.internal.device.url, data.locus)
253
254
  ) ||
254
255
  this.meetingCollection.getByKey(
@@ -460,7 +461,7 @@ export default class Meetings extends WebexPlugin {
460
461
  LoggerProxy.set(this.webex.logger);
461
462
 
462
463
  mediaLogger = new MediaLogger();
463
- MC.setLogger(mediaLogger);
464
+ setLogger(mediaLogger);
464
465
 
465
466
  /**
466
467
  * The MeetingInfo object to interact with server
@@ -471,11 +472,14 @@ export default class Meetings extends WebexPlugin {
471
472
  */
472
473
  // @ts-ignore
473
474
  this.meetingInfo = this.config.experimental.enableUnifiedMeetings
474
- ? new MeetingInfoV2(this.webex)
475
- : new MeetingInfo(this.webex);
475
+ ? // @ts-ignore
476
+ new MeetingInfoV2(this.webex)
477
+ : // @ts-ignore
478
+ new MeetingInfo(this.webex);
476
479
  // @ts-ignore
477
480
  this.personalMeetingRoom = new PersonalMeetingRoom(
478
481
  {meetingInfo: this.meetingInfo},
482
+ // @ts-ignore
479
483
  {parent: this.webex}
480
484
  );
481
485
 
@@ -585,6 +589,7 @@ export default class Meetings extends WebexPlugin {
585
589
  // @ts-ignore
586
590
  .then(() =>
587
591
  LoggerProxy.logger.info(
592
+ // @ts-ignore
588
593
  `Meetings:index#register --> INFO, Device registered ${this.webex.internal.device.url}`
589
594
  )
590
595
  )
@@ -638,8 +643,8 @@ export default class Meetings extends WebexPlugin {
638
643
 
639
644
  this.stopListeningForEvents();
640
645
 
641
- // @ts-ignore
642
646
  return (
647
+ // @ts-ignore
643
648
  this.webex.internal.mercury
644
649
  .disconnect()
645
650
  // @ts-ignore
@@ -53,9 +53,9 @@ export default class MeetingRequest extends StatelessWebexPlugin {
53
53
  */
54
54
  determineRedirections(responseBody: any) {
55
55
  if (responseBody.remoteLocusClusterUrls && responseBody.remoteLocusClusterUrls.length) {
56
- // @ts-ignore
57
56
  return Promise.all(
58
57
  responseBody.remoteLocusClusterUrls.map((url) =>
58
+ // @ts-ignore
59
59
  this.request({
60
60
  method: HTTP_VERBS.GET,
61
61
  url,
@@ -31,6 +31,7 @@ export default class Member {
31
31
  name: any;
32
32
  participant: any;
33
33
  status: any;
34
+ supportsBreakouts: boolean;
34
35
  type: any;
35
36
  namespace = MEETINGS;
36
37
 
@@ -100,6 +101,13 @@ export default class Member {
100
101
  * @memberof Member
101
102
  */
102
103
  this.isHandRaised = null;
104
+ /**
105
+ * @instance
106
+ * @type {Boolean}
107
+ * @public
108
+ * @memberof Member
109
+ */
110
+ this.supportsBreakouts = null;
103
111
  /**
104
112
  * @instance
105
113
  * @type {Boolean}
@@ -242,6 +250,7 @@ export default class Member {
242
250
  this.isAudioMuted = MemberUtil.isAudioMuted(participant);
243
251
  this.isVideoMuted = MemberUtil.isVideoMuted(participant);
244
252
  this.isHandRaised = MemberUtil.isHandRaised(participant);
253
+ this.supportsBreakouts = MemberUtil.isBreakoutsSupported(participant);
245
254
  this.isGuest = MemberUtil.isGuest(participant);
246
255
  this.isUser = MemberUtil.isUser(participant);
247
256
  this.isDevice = MemberUtil.isDevice(participant);
@@ -72,7 +72,7 @@ MemberUtil.isAssociatedSame = (participant: any, id: string) =>
72
72
  * @param {String} status
73
73
  * @returns {Boolean}
74
74
  */
75
- MemberUtil.isNotAdmitted = (participant: any, isGuest: boolean, status: string) =>
75
+ MemberUtil.isNotAdmitted = (participant: any, isGuest: boolean, status: string): boolean =>
76
76
  participant &&
77
77
  participant.guest &&
78
78
  ((participant.devices &&
@@ -82,6 +82,7 @@ MemberUtil.isNotAdmitted = (participant: any, isGuest: boolean, status: string)
82
82
  // @ts-ignore
83
83
  isGuest &&
84
84
  status === _IN_LOBBY_) ||
85
+ // @ts-ignore
85
86
  !status === _IN_MEETING_);
86
87
 
87
88
  /**
@@ -130,6 +131,18 @@ MemberUtil.isHandRaised = (participant: any) => {
130
131
  return participant.controls?.hand?.raised || false;
131
132
  };
132
133
 
134
+ /**
135
+ * @param {Object} participant the locus participant
136
+ * @returns {Boolean}
137
+ */
138
+ MemberUtil.isBreakoutsSupported = (participant) => {
139
+ if (!participant) {
140
+ throw new ParameterError('Breakout support could not be processed, participant is undefined.');
141
+ }
142
+
143
+ return !participant.doesNotSupportBreakouts;
144
+ };
145
+
133
146
  /**
134
147
  * utility method for audio/video muted status
135
148
  * @param {String} status
@@ -925,6 +925,7 @@ export default class Members extends StatelessWebexPlugin {
925
925
  */
926
926
  findMemberByCsi(csi) {
927
927
  return Object.values(this.membersCollection.getAll()).find((member) =>
928
+ // @ts-ignore
928
929
  member.participant?.devices?.find((device) =>
929
930
  device.csis?.find((memberCsi) => memberCsi === csi)
930
931
  )
@@ -140,6 +140,7 @@ export default class MembersRequest extends StatelessWebexPlugin {
140
140
  !options.locusUrl ||
141
141
  !options.memberId ||
142
142
  !options.url ||
143
+ // @ts-ignore
143
144
  (!options.tones && options.tones !== 0)
144
145
  ) {
145
146
  throw new ParameterError(
@@ -1,9 +1,18 @@
1
1
  /* eslint-disable require-jsdoc */
2
- import {MediaConnection as MC} from '@webex/internal-media-core';
2
+ import {
3
+ MediaRequest as WcmeMediaRequest,
4
+ Policy,
5
+ ActiveSpeakerInfo,
6
+ ReceiverSelectedInfo,
7
+ CodecInfo as WcmeCodecInfo,
8
+ H264Codec,
9
+ } from '@webex/internal-media-core';
10
+ import {cloneDeep} from 'lodash';
3
11
 
4
12
  import LoggerProxy from '../common/logs/logger-proxy';
5
13
 
6
14
  import {ReceiveSlot, ReceiveSlotId} from './receiveSlot';
15
+ import {getMaxFs} from './remoteMedia';
7
16
 
8
17
  export interface ActiveSpeakerPolicyInfo {
9
18
  policy: 'active-speaker';
@@ -47,22 +56,32 @@ const CODEC_DEFAULTS = {
47
56
  },
48
57
  };
49
58
 
50
- type SendMediaRequestsCallback = (mediaRequests: MC.MediaRequest[]) => void;
59
+ type DegradationPreferences = {
60
+ maxMacroblocksLimit: number;
61
+ };
62
+
63
+ type SendMediaRequestsCallback = (mediaRequests: WcmeMediaRequest[]) => void;
51
64
 
52
65
  export class MediaRequestManager {
53
66
  private sendMediaRequestsCallback: SendMediaRequestsCallback;
54
67
 
55
- private counter;
68
+ private counter: number;
56
69
 
57
70
  private clientRequests: {[key: MediaRequestId]: MediaRequest};
58
71
 
59
72
  private slotsActiveInLastMediaRequest: {[key: ReceiveSlotId]: ReceiveSlot};
60
73
 
61
- constructor(sendMediaRequestsCallback: SendMediaRequestsCallback) {
74
+ private degradationPreferences: DegradationPreferences;
75
+
76
+ constructor(
77
+ degradationPreferences: DegradationPreferences,
78
+ sendMediaRequestsCallback: SendMediaRequestsCallback
79
+ ) {
62
80
  this.sendMediaRequestsCallback = sendMediaRequestsCallback;
63
81
  this.counter = 0;
64
82
  this.clientRequests = {};
65
83
  this.slotsActiveInLastMediaRequest = {};
84
+ this.degradationPreferences = degradationPreferences;
66
85
  }
67
86
 
68
87
  private resetInactiveReceiveSlots() {
@@ -90,34 +109,79 @@ export class MediaRequestManager {
90
109
  this.slotsActiveInLastMediaRequest = activeSlots;
91
110
  }
92
111
 
112
+ public setDegradationPreferences(degradationPreferences: DegradationPreferences) {
113
+ this.degradationPreferences = degradationPreferences;
114
+ this.sendRequests(); // re-send requests after preferences are set
115
+ }
116
+
117
+ private getDegradedClientRequests() {
118
+ const clientRequests = cloneDeep(this.clientRequests);
119
+ const maxFsLimits = [
120
+ getMaxFs('best'),
121
+ getMaxFs('large'),
122
+ getMaxFs('medium'),
123
+ getMaxFs('small'),
124
+ getMaxFs('very small'),
125
+ getMaxFs('thumbnail'),
126
+ ];
127
+
128
+ // reduce max-fs until total macroblocks is below limit
129
+ for (let i = 0; i < maxFsLimits.length; i += 1) {
130
+ let totalMacroblocksRequested = 0;
131
+ Object.values(clientRequests).forEach((mr) => {
132
+ if (mr.codecInfo) {
133
+ mr.codecInfo.maxFs = Math.min(
134
+ mr.codecInfo.maxFs || CODEC_DEFAULTS.h264.maxFs,
135
+ maxFsLimits[i]
136
+ );
137
+ totalMacroblocksRequested += mr.codecInfo.maxFs * mr.receiveSlots.length;
138
+ }
139
+ });
140
+ if (totalMacroblocksRequested <= this.degradationPreferences.maxMacroblocksLimit) {
141
+ if (i !== 0) {
142
+ LoggerProxy.logger.warn(
143
+ `multistream:mediaRequestManager --> too many requests with high max-fs, frame size will be limited to ${maxFsLimits[i]}`
144
+ );
145
+ }
146
+ break;
147
+ } else if (i === maxFsLimits.length - 1) {
148
+ LoggerProxy.logger.warn(
149
+ `multistream:mediaRequestManager --> even with frame size limited to ${maxFsLimits[i]} you are still requesting too many streams, consider reducing the number of requests`
150
+ );
151
+ }
152
+ }
153
+
154
+ return clientRequests;
155
+ }
156
+
93
157
  private sendRequests() {
94
- const wcmeMediaRequests: MC.MediaRequest[] = [];
158
+ const wcmeMediaRequests: WcmeMediaRequest[] = [];
95
159
 
96
- // todo: check how many streams we're asking for and what resolution and introduce some limits (spark-377701)
160
+ const clientRequests = this.getDegradedClientRequests();
97
161
  const maxPayloadBitsPerSecond = 10 * 1000 * 1000;
98
162
 
99
163
  // map all the client media requests to wcme media requests
100
- Object.values(this.clientRequests).forEach((mr) => {
164
+ Object.values(clientRequests).forEach((mr) => {
101
165
  wcmeMediaRequests.push(
102
- new MC.MediaRequest(
166
+ new WcmeMediaRequest(
103
167
  mr.policyInfo.policy === 'active-speaker'
104
- ? MC.Policy.ActiveSpeaker
105
- : MC.Policy.ReceiverSelected,
168
+ ? Policy.ActiveSpeaker
169
+ : Policy.ReceiverSelected,
106
170
  mr.policyInfo.policy === 'active-speaker'
107
- ? new MC.ActiveSpeakerInfo(
171
+ ? new ActiveSpeakerInfo(
108
172
  mr.policyInfo.priority,
109
173
  mr.policyInfo.crossPriorityDuplication,
110
174
  mr.policyInfo.crossPolicyDuplication,
111
175
  mr.policyInfo.preferLiveVideo
112
176
  )
113
- : new MC.ReceiverSelectedInfo(mr.policyInfo.csi),
177
+ : new ReceiverSelectedInfo(mr.policyInfo.csi),
114
178
  mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
115
179
  maxPayloadBitsPerSecond,
116
180
  mr.codecInfo && [
117
- new MC.CodecInfo(
181
+ new WcmeCodecInfo(
118
182
  0x80,
119
- new MC.H264Codec(
120
- mr.codecInfo.maxFs || CODEC_DEFAULTS.h264.maxFs,
183
+ new H264Codec(
184
+ mr.codecInfo.maxFs,
121
185
  mr.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps,
122
186
  mr.codecInfo.maxMbps || CODEC_DEFAULTS.h264.maxMbps,
123
187
  mr.codecInfo.maxWidth,
@@ -48,6 +48,7 @@ export class MultistreamMedia {
48
48
  // todo: depending on how muting is done with Local tracks, this code here might need to change...
49
49
 
50
50
  if (track.kind === 'audio') {
51
+ // @ts-ignore
51
52
  this.meeting.setLocalAudioTrack(track);
52
53
  this.meeting.mediaProperties.mediaDirection.sendAudio = true;
53
54
 
@@ -56,6 +57,7 @@ export class MultistreamMedia {
56
57
  this.meeting.audio ||
57
58
  createMuteState(AUDIO, this.meeting, this.meeting.mediaProperties.mediaDirection);
58
59
  } else if (track.kind === 'video') {
60
+ // @ts-ignore
59
61
  this.meeting.setLocalVideoTrack(track);
60
62
  this.meeting.mediaProperties.mediaDirection.sendVideo = true;
61
63
 
@@ -80,9 +82,11 @@ export class MultistreamMedia {
80
82
  // muting etc
81
83
 
82
84
  if (track.kind === 'audio') {
85
+ // @ts-ignore
83
86
  this.meeting.setLocalVideoTrack(null);
84
87
  this.meeting.mediaProperties.mediaDirection.sendAudio = false;
85
88
  } else if (track.kind === 'video') {
89
+ // @ts-ignore
86
90
  this.meeting.setLocalAudioTrack(null);
87
91
  this.meeting.mediaProperties.mediaDirection.sendVideo = false;
88
92
  }
@@ -1,5 +1,10 @@
1
1
  /* eslint-disable valid-jsdoc */
2
- import {MediaConnection as MC} from '@webex/internal-media-core';
2
+ import {
3
+ MediaType,
4
+ ReceiveSlot as WcmeReceiveSlot,
5
+ ReceiveSlotEvents as WcmeReceiveSlotEvents,
6
+ SourceState,
7
+ } from '@webex/internal-media-core';
3
8
 
4
9
  import LoggerProxy from '../common/logs/logger-proxy';
5
10
  import EventsScope from '../common/events/events-scope';
@@ -8,7 +13,7 @@ export const ReceiveSlotEvents = {
8
13
  SourceUpdate: 'sourceUpdate',
9
14
  };
10
15
 
11
- export type SourceState = MC.SourceState;
16
+ export type {SourceState} from '@webex/internal-media-core';
12
17
  export type CSI = number;
13
18
  export type MemberId = string;
14
19
  export type ReceiveSlotId = string;
@@ -22,31 +27,31 @@ export type FindMemberIdCallback = (csi: CSI) => MemberId | undefined;
22
27
  * for example some participant's main video or audio
23
28
  */
24
29
  export class ReceiveSlot extends EventsScope {
25
- private readonly mcReceiveSlot: MC.ReceiveSlot;
30
+ private readonly mcReceiveSlot: WcmeReceiveSlot;
26
31
 
27
32
  private readonly findMemberIdCallback: FindMemberIdCallback;
28
33
 
29
34
  public readonly id: ReceiveSlotId;
30
35
 
31
- public readonly mediaType: MC.MediaType;
36
+ public readonly mediaType: MediaType;
32
37
 
33
38
  #memberId?: MemberId;
34
39
 
35
40
  #csi?: CSI;
36
41
 
37
- #sourceState: MC.SourceState;
42
+ #sourceState: SourceState;
38
43
 
39
44
  /**
40
45
  * constructor - don't use it directly, you should always use meeting.receiveSlotManager.allocateSlot()
41
46
  * to create any receive slots
42
47
  *
43
- * @param {MC.MediaType} mediaType
44
- * @param {MC.ReceiveSlot} mcReceiveSlot
48
+ * @param {MediaType} mediaType
49
+ * @param {ReceiveSlot} mcReceiveSlot
45
50
  * @param {FindMemberIdCallback} findMemberIdCallback callback for finding memberId for given CSI
46
51
  */
47
52
  constructor(
48
- mediaType: MC.MediaType,
49
- mcReceiveSlot: MC.ReceiveSlot,
53
+ mediaType: MediaType,
54
+ mcReceiveSlot: WcmeReceiveSlot,
50
55
  findMemberIdCallback: FindMemberIdCallback
51
56
  ) {
52
57
  super();
@@ -93,8 +98,8 @@ export class ReceiveSlot extends EventsScope {
93
98
  };
94
99
 
95
100
  this.mcReceiveSlot.on(
96
- MC.ReceiveSlotEvents.SourceUpdate,
97
- (state: MC.SourceState, csi?: number) => {
101
+ WcmeReceiveSlotEvents.SourceUpdate,
102
+ (state: SourceState, csi?: number) => {
98
103
  LoggerProxy.logger.log(
99
104
  `ReceiveSlot#setupEventListeners --> got source update on receive slot ${this.id}, mediaType=${this.mediaType}, csi=${csi}, state=${state}`
100
105
  );
@@ -123,7 +128,7 @@ export class ReceiveSlot extends EventsScope {
123
128
  /**
124
129
  * The underlying WCME receive slot
125
130
  */
126
- get wcmeReceiveSlot(): MC.ReceiveSlot {
131
+ get wcmeReceiveSlot(): WcmeReceiveSlot {
127
132
  return this.mcReceiveSlot;
128
133
  }
129
134