@webex/plugin-meetings 3.0.0-stream-classes.4 → 3.0.0-test.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 (239) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/no-meeting-info.js +51 -0
  5. package/dist/common/errors/no-meeting-info.js.map +1 -0
  6. package/dist/common/errors/reclaim-host-role-errors.js +158 -0
  7. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  8. package/dist/common/errors/webex-errors.js +23 -3
  9. package/dist/common/errors/webex-errors.js.map +1 -1
  10. package/dist/common/logs/request.js +5 -1
  11. package/dist/common/logs/request.js.map +1 -1
  12. package/dist/config.js +1 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants.js +69 -9
  15. package/dist/constants.js.map +1 -1
  16. package/dist/index.js +11 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/interceptors/index.js +15 -0
  19. package/dist/interceptors/index.js.map +1 -0
  20. package/dist/interceptors/locusRetry.js +93 -0
  21. package/dist/interceptors/locusRetry.js.map +1 -0
  22. package/dist/interpretation/index.js +16 -2
  23. package/dist/interpretation/index.js.map +1 -1
  24. package/dist/interpretation/siLanguage.js +1 -1
  25. package/dist/locus-info/index.js +40 -11
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/locus-info/mediaSharesUtils.js +15 -1
  28. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  29. package/dist/locus-info/parser.js +42 -21
  30. package/dist/locus-info/parser.js.map +1 -1
  31. package/dist/media/index.js +10 -6
  32. package/dist/media/index.js.map +1 -1
  33. package/dist/media/properties.js +13 -3
  34. package/dist/media/properties.js.map +1 -1
  35. package/dist/mediaQualityMetrics/config.js +135 -330
  36. package/dist/mediaQualityMetrics/config.js.map +1 -1
  37. package/dist/meeting/in-meeting-actions.js +4 -0
  38. package/dist/meeting/in-meeting-actions.js.map +1 -1
  39. package/dist/meeting/index.js +2187 -1074
  40. package/dist/meeting/index.js.map +1 -1
  41. package/dist/meeting/muteState.js +37 -25
  42. package/dist/meeting/muteState.js.map +1 -1
  43. package/dist/meeting/request.js +34 -19
  44. package/dist/meeting/request.js.map +1 -1
  45. package/dist/meeting/util.js +71 -0
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meeting-info/index.js +48 -23
  48. package/dist/meeting-info/index.js.map +1 -1
  49. package/dist/meeting-info/meeting-info-v2.js +25 -4
  50. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  51. package/dist/meeting-info/utilv2.js +1 -1
  52. package/dist/meeting-info/utilv2.js.map +1 -1
  53. package/dist/meetings/collection.js +17 -0
  54. package/dist/meetings/collection.js.map +1 -1
  55. package/dist/meetings/index.js +142 -57
  56. package/dist/meetings/index.js.map +1 -1
  57. package/dist/meetings/util.js +2 -6
  58. package/dist/meetings/util.js.map +1 -1
  59. package/dist/member/index.js +9 -0
  60. package/dist/member/index.js.map +1 -1
  61. package/dist/member/util.js +11 -0
  62. package/dist/member/util.js.map +1 -1
  63. package/dist/members/index.js +17 -1
  64. package/dist/members/index.js.map +1 -1
  65. package/dist/members/types.js.map +1 -1
  66. package/dist/members/util.js +15 -4
  67. package/dist/members/util.js.map +1 -1
  68. package/dist/metrics/constants.js +15 -1
  69. package/dist/metrics/constants.js.map +1 -1
  70. package/dist/multistream/mediaRequestManager.js +1 -1
  71. package/dist/multistream/mediaRequestManager.js.map +1 -1
  72. package/dist/multistream/remoteMediaGroup.js +16 -2
  73. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  74. package/dist/multistream/remoteMediaManager.js +222 -73
  75. package/dist/multistream/remoteMediaManager.js.map +1 -1
  76. package/dist/multistream/sendSlotManager.js +22 -0
  77. package/dist/multistream/sendSlotManager.js.map +1 -1
  78. package/dist/reachability/clusterReachability.js +356 -0
  79. package/dist/reachability/clusterReachability.js.map +1 -0
  80. package/dist/reachability/index.js +262 -432
  81. package/dist/reachability/index.js.map +1 -1
  82. package/dist/reachability/request.js +1 -1
  83. package/dist/reachability/request.js.map +1 -1
  84. package/dist/reachability/util.js +29 -0
  85. package/dist/reachability/util.js.map +1 -0
  86. package/dist/reconnection-manager/index.js +113 -96
  87. package/dist/reconnection-manager/index.js.map +1 -1
  88. package/dist/roap/index.js +57 -25
  89. package/dist/roap/index.js.map +1 -1
  90. package/dist/roap/request.js +5 -13
  91. package/dist/roap/request.js.map +1 -1
  92. package/dist/roap/turnDiscovery.js +173 -81
  93. package/dist/roap/turnDiscovery.js.map +1 -1
  94. package/dist/rtcMetrics/index.js +68 -6
  95. package/dist/rtcMetrics/index.js.map +1 -1
  96. package/dist/statsAnalyzer/index.js +338 -289
  97. package/dist/statsAnalyzer/index.js.map +1 -1
  98. package/dist/statsAnalyzer/mqaUtil.js +296 -156
  99. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  100. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  101. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  102. package/dist/types/common/errors/webex-errors.d.ts +13 -1
  103. package/dist/types/common/logs/request.d.ts +2 -0
  104. package/dist/types/config.d.ts +1 -1
  105. package/dist/types/constants.d.ts +66 -13
  106. package/dist/types/index.d.ts +1 -1
  107. package/dist/types/interceptors/index.d.ts +2 -0
  108. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  109. package/dist/types/locus-info/index.d.ts +1 -1
  110. package/dist/types/locus-info/parser.d.ts +3 -2
  111. package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
  112. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  113. package/dist/types/meeting/index.d.ts +285 -34
  114. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  115. package/dist/types/meeting/muteState.d.ts +2 -8
  116. package/dist/types/meeting/request.d.ts +4 -1
  117. package/dist/types/meeting/util.d.ts +25 -1
  118. package/dist/types/meeting-info/index.d.ts +7 -0
  119. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  120. package/dist/types/meetings/collection.d.ts +9 -0
  121. package/dist/types/meetings/index.d.ts +42 -14
  122. package/dist/types/member/index.d.ts +1 -0
  123. package/dist/types/members/types.d.ts +1 -0
  124. package/dist/types/members/util.d.ts +5 -0
  125. package/dist/types/metrics/constants.d.ts +15 -0
  126. package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
  127. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
  128. package/dist/types/multistream/remoteMediaManager.d.ts +25 -1
  129. package/dist/types/multistream/sendSlotManager.d.ts +9 -0
  130. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  131. package/dist/types/reachability/index.d.ts +59 -112
  132. package/dist/types/reachability/request.d.ts +1 -1
  133. package/dist/types/reachability/util.d.ts +8 -0
  134. package/dist/types/reconnection-manager/index.d.ts +10 -0
  135. package/dist/types/roap/index.d.ts +2 -1
  136. package/dist/types/roap/request.d.ts +2 -1
  137. package/dist/types/roap/turnDiscovery.d.ts +21 -4
  138. package/dist/types/rtcMetrics/index.d.ts +15 -1
  139. package/dist/types/statsAnalyzer/index.d.ts +28 -11
  140. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  141. package/dist/types/webinar/collection.d.ts +16 -0
  142. package/dist/types/webinar/index.d.ts +5 -0
  143. package/dist/webinar/collection.js +44 -0
  144. package/dist/webinar/collection.js.map +1 -0
  145. package/dist/webinar/index.js +69 -0
  146. package/dist/webinar/index.js.map +1 -0
  147. package/package.json +3 -2
  148. package/src/common/errors/no-meeting-info.ts +24 -0
  149. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  150. package/src/common/errors/webex-errors.ts +19 -2
  151. package/src/common/logs/request.ts +5 -1
  152. package/src/config.ts +1 -1
  153. package/src/constants.ts +71 -6
  154. package/src/index.ts +5 -0
  155. package/src/interceptors/index.ts +3 -0
  156. package/src/interceptors/locusRetry.ts +67 -0
  157. package/src/interpretation/index.ts +18 -1
  158. package/src/locus-info/index.ts +52 -16
  159. package/src/locus-info/mediaSharesUtils.ts +16 -0
  160. package/src/locus-info/parser.ts +47 -21
  161. package/src/media/index.ts +8 -6
  162. package/src/media/properties.ts +17 -2
  163. package/src/mediaQualityMetrics/config.ts +103 -238
  164. package/src/meeting/in-meeting-actions.ts +8 -0
  165. package/src/meeting/index.ts +1510 -529
  166. package/src/meeting/muteState.ts +34 -20
  167. package/src/meeting/request.ts +19 -1
  168. package/src/meeting/util.ts +97 -0
  169. package/src/meeting-info/index.ts +47 -20
  170. package/src/meeting-info/meeting-info-v2.ts +27 -5
  171. package/src/meeting-info/utilv2.ts +1 -1
  172. package/src/meetings/collection.ts +13 -0
  173. package/src/meetings/index.ts +112 -31
  174. package/src/meetings/util.ts +2 -8
  175. package/src/member/index.ts +9 -0
  176. package/src/member/util.ts +14 -0
  177. package/src/members/index.ts +29 -2
  178. package/src/members/types.ts +1 -0
  179. package/src/members/util.ts +15 -1
  180. package/src/metrics/constants.ts +14 -0
  181. package/src/multistream/mediaRequestManager.ts +4 -1
  182. package/src/multistream/remoteMediaGroup.ts +19 -0
  183. package/src/multistream/remoteMediaManager.ts +141 -18
  184. package/src/multistream/sendSlotManager.ts +29 -0
  185. package/src/reachability/clusterReachability.ts +320 -0
  186. package/src/reachability/index.ts +221 -382
  187. package/src/reachability/request.ts +1 -1
  188. package/src/reachability/util.ts +24 -0
  189. package/src/reconnection-manager/index.ts +87 -83
  190. package/src/roap/index.ts +60 -24
  191. package/src/roap/request.ts +3 -16
  192. package/src/roap/turnDiscovery.ts +112 -39
  193. package/src/rtcMetrics/index.ts +71 -5
  194. package/src/statsAnalyzer/index.ts +430 -427
  195. package/src/statsAnalyzer/mqaUtil.ts +317 -168
  196. package/src/webinar/collection.ts +31 -0
  197. package/src/webinar/index.ts +62 -0
  198. package/test/integration/spec/converged-space-meetings.js +7 -7
  199. package/test/integration/spec/journey.js +86 -104
  200. package/test/integration/spec/space-meeting.js +9 -9
  201. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  202. package/test/unit/spec/interpretation/index.ts +36 -3
  203. package/test/unit/spec/locus-info/index.js +205 -12
  204. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  205. package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
  206. package/test/unit/spec/locus-info/parser.js +54 -13
  207. package/test/unit/spec/media/index.ts +20 -4
  208. package/test/unit/spec/media/properties.ts +2 -2
  209. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  210. package/test/unit/spec/meeting/index.js +4027 -1075
  211. package/test/unit/spec/meeting/muteState.js +219 -67
  212. package/test/unit/spec/meeting/request.js +63 -12
  213. package/test/unit/spec/meeting/utils.js +93 -0
  214. package/test/unit/spec/meeting-info/index.js +180 -61
  215. package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
  216. package/test/unit/spec/meetings/collection.js +12 -0
  217. package/test/unit/spec/meetings/index.js +619 -206
  218. package/test/unit/spec/meetings/utils.js +35 -12
  219. package/test/unit/spec/member/index.js +8 -7
  220. package/test/unit/spec/member/util.js +32 -0
  221. package/test/unit/spec/members/index.js +130 -17
  222. package/test/unit/spec/members/utils.js +26 -0
  223. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  224. package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
  225. package/test/unit/spec/multistream/remoteMediaManager.ts +210 -3
  226. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  227. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  228. package/test/unit/spec/reachability/index.ts +505 -135
  229. package/test/unit/spec/reachability/util.ts +40 -0
  230. package/test/unit/spec/reconnection-manager/index.js +74 -17
  231. package/test/unit/spec/roap/index.ts +181 -61
  232. package/test/unit/spec/roap/request.ts +27 -3
  233. package/test/unit/spec/roap/turnDiscovery.ts +362 -101
  234. package/test/unit/spec/rtcMetrics/index.ts +57 -3
  235. package/test/unit/spec/stats-analyzer/index.js +1225 -12
  236. package/test/unit/spec/webinar/collection.ts +13 -0
  237. package/test/unit/spec/webinar/index.ts +60 -0
  238. package/test/utils/integrationTestUtils.js +4 -4
  239. package/test/utils/webex-test-users.js +12 -4
@@ -2,6 +2,7 @@
2
2
  import {cloneDeep, forEach, remove} from 'lodash';
3
3
  import {EventMap} from 'typed-emitter';
4
4
  import {MediaType} from '@webex/internal-media-core';
5
+ import {NamedMediaGroup} from '@webex/json-multistream';
5
6
 
6
7
  import LoggerProxy from '../common/logs/logger-proxy';
7
8
  import EventsScope from '../common/events/events-scope';
@@ -11,6 +12,7 @@ import {ReceiveSlot, CSI} from './receiveSlot';
11
12
  import {ReceiveSlotManager} from './receiveSlotManager';
12
13
  import {RemoteMediaGroup} from './remoteMediaGroup';
13
14
  import {MediaRequestManager} from './mediaRequestManager';
15
+ import {NAMED_MEDIA_GROUP_TYPE_AUDIO} from '../constants';
14
16
 
15
17
  export type PaneSize = RemoteVideoResolution;
16
18
  export type LayoutId = string;
@@ -49,6 +51,7 @@ export interface Configuration {
49
51
 
50
52
  layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
51
53
  };
54
+ namedMediaGroup?: NamedMediaGroup;
52
55
  }
53
56
 
54
57
  /* Predefined layouts: */
@@ -173,6 +176,7 @@ export const DefaultConfiguration: Configuration = {
173
176
  export enum Event {
174
177
  // events for audio streams
175
178
  AudioCreated = 'AudioCreated',
179
+ InterpretationAudioCreated = 'InterpretationAudioCreated',
176
180
  ScreenShareAudioCreated = 'ScreenShareAudioCreated',
177
181
 
178
182
  // events for video streams
@@ -221,7 +225,10 @@ export class RemoteMediaManager extends EventsScope {
221
225
  private currentLayout?: VideoLayout;
222
226
 
223
227
  private slots: {
224
- audio: ReceiveSlot[];
228
+ audio: {
229
+ main: ReceiveSlot[];
230
+ si: ReceiveSlot;
231
+ };
225
232
  screenShare: {
226
233
  audio: ReceiveSlot[];
227
234
  video?: ReceiveSlot;
@@ -234,7 +241,10 @@ export class RemoteMediaManager extends EventsScope {
234
241
  };
235
242
 
236
243
  private media: {
237
- audio?: RemoteMediaGroup;
244
+ audio: {
245
+ main?: RemoteMediaGroup;
246
+ si?: RemoteMediaGroup;
247
+ };
238
248
  video: {
239
249
  activeSpeakerGroups: {
240
250
  [key: PaneGroupId]: RemoteMediaGroup;
@@ -277,7 +287,10 @@ export class RemoteMediaManager extends EventsScope {
277
287
  this.receiveSlotManager = receiveSlotManager;
278
288
  this.mediaRequestManagers = mediaRequestManagers;
279
289
  this.media = {
280
- audio: undefined,
290
+ audio: {
291
+ main: undefined,
292
+ si: undefined,
293
+ },
281
294
  video: {
282
295
  activeSpeakerGroups: {},
283
296
  memberPanes: {},
@@ -291,7 +304,10 @@ export class RemoteMediaManager extends EventsScope {
291
304
  this.checkConfigValidity();
292
305
 
293
306
  this.slots = {
294
- audio: [],
307
+ audio: {
308
+ main: [],
309
+ si: undefined,
310
+ },
295
311
  screenShare: {
296
312
  audio: [],
297
313
  video: undefined,
@@ -389,8 +405,11 @@ export class RemoteMediaManager extends EventsScope {
389
405
  });
390
406
 
391
407
  // release all audio receive slots
392
- this.slots.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
393
- this.slots.audio.length = 0;
408
+ this.slots.audio.main.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
409
+ this.slots.audio.main.length = 0;
410
+ if (this.slots.audio.si) {
411
+ this.receiveSlotManager.releaseSlot(this.slots.audio.si);
412
+ }
394
413
 
395
414
  // release screen share slots
396
415
  this.slots.screenShare.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
@@ -473,6 +492,8 @@ export class RemoteMediaManager extends EventsScope {
473
492
  if (!this.started) {
474
493
  throw new Error('setLayout() called before start()');
475
494
  }
495
+ LoggerProxy.logger.log(`RemoteMediaManager#setLayout --> new layout selected: ${layoutId}`);
496
+
476
497
  this.currentLayoutId = layoutId;
477
498
  this.currentLayout = cloneDeep(this.config.video.layouts[this.currentLayoutId]);
478
499
 
@@ -523,22 +544,54 @@ export class RemoteMediaManager extends EventsScope {
523
544
  this.mediaRequestManagers.video.commit();
524
545
  }
525
546
 
547
+ /**
548
+ * Sets which named media group need receiving
549
+ * @param {MediaType} mediaType of the stream
550
+ * @param {number} languageCode of the stream. If the languageId is 0, the named media group request will be canceled,
551
+ * and only receive the main audio stream.
552
+ * @returns {void}
553
+ */
554
+ public async setReceiveNamedMediaGroup(mediaType: MediaType, languageId: number) {
555
+ if (mediaType !== MediaType.AudioMain) {
556
+ throw new Error(`cannot set receive named media group which media type is ${mediaType}`);
557
+ }
558
+
559
+ const value = languageId;
560
+ if (value === this.config.namedMediaGroup?.value) {
561
+ return;
562
+ }
563
+
564
+ this.config.namedMediaGroup = {
565
+ type: NAMED_MEDIA_GROUP_TYPE_AUDIO,
566
+ value,
567
+ };
568
+
569
+ if (!this.media.audio.si) {
570
+ await this.createInterpretationAudioMedia(true);
571
+ } else {
572
+ this.media.audio.si.setNamedMediaGroup(this.config.namedMediaGroup, true);
573
+ }
574
+ }
575
+
526
576
  /**
527
577
  * Creates the audio slots
528
578
  */
529
579
  private async createAudioMedia() {
530
- // create the audio receive slots
580
+ // create si audio request
581
+ await this.createInterpretationAudioMedia(false);
582
+
583
+ // create main audio receive slots
531
584
  for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
532
585
  // eslint-disable-next-line no-await-in-loop
533
586
  const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
534
587
 
535
- this.slots.audio.push(slot);
588
+ this.slots.audio.main.push(slot);
536
589
  }
537
590
 
538
- // create a remote media group
539
- this.media.audio = new RemoteMediaGroup(
591
+ // create a remote media group for main audio
592
+ this.media.audio.main = new RemoteMediaGroup(
540
593
  this.mediaRequestManagers.audio,
541
- this.slots.audio,
594
+ this.slots.audio.main,
542
595
  255,
543
596
  true
544
597
  );
@@ -546,10 +599,40 @@ export class RemoteMediaManager extends EventsScope {
546
599
  this.emit(
547
600
  {file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
548
601
  Event.AudioCreated,
549
- this.media.audio
602
+ this.media.audio.main
550
603
  );
551
604
  }
552
605
 
606
+ /**
607
+ * Creates the audio slots for named media
608
+ */
609
+ private async createInterpretationAudioMedia(commitRequest: boolean) {
610
+ // create slot for interpretation language audio
611
+ if (
612
+ this.config.namedMediaGroup?.type === NAMED_MEDIA_GROUP_TYPE_AUDIO &&
613
+ this.config.namedMediaGroup?.value
614
+ ) {
615
+ this.slots.audio.si = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
616
+
617
+ // create a remote media group for si audio
618
+ this.media.audio.si = new RemoteMediaGroup(
619
+ this.mediaRequestManagers.audio,
620
+ [this.slots.audio.si],
621
+ 255,
622
+ commitRequest,
623
+ {
624
+ namedMediaGroup: this.config.namedMediaGroup,
625
+ }
626
+ );
627
+
628
+ this.emit(
629
+ {file: 'multistream/remoteMediaManager', function: 'createInterpretationAudioMedia'},
630
+ Event.InterpretationAudioCreated,
631
+ this.media.audio.si
632
+ );
633
+ }
634
+ }
635
+
553
636
  /**
554
637
  * Creates receive slots required for receiving screen share audio and video
555
638
  */
@@ -725,10 +808,12 @@ export class RemoteMediaManager extends EventsScope {
725
808
  /**
726
809
  * Logs the state of the receive slots
727
810
  */
728
- private logReceieveSlots() {
811
+ private logMainVideoReceiveSlots() {
729
812
  let logMessage = '';
730
813
  forEach(this.receiveSlotAllocations.activeSpeaker, (group, groupName) => {
731
- logMessage += `group: ${groupName}\n${group.slots.map((slot) => slot.logString).join(' ')}`;
814
+ logMessage += `\ngroup: ${groupName}\n${group.slots
815
+ .map((slot) => slot.logString)
816
+ .join(', ')}`;
732
817
  });
733
818
 
734
819
  logMessage += '\nreceiverSelected:\n';
@@ -737,10 +822,43 @@ export class RemoteMediaManager extends EventsScope {
737
822
  });
738
823
 
739
824
  LoggerProxy.logger.log(
740
- `RemoteMediaManager#updateVideoReceiveSlots --> receive slots updated: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}\n${logMessage}`
825
+ `RemoteMediaManager#logMainVideoReceiveSlots --> MAIN VIDEO receive slots: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}${logMessage}`
826
+ );
827
+ }
828
+
829
+ /** logs main audio slots */
830
+ private logMainAudioReceiveSlots() {
831
+ LoggerProxy.logger.log(
832
+ `RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio.main
833
+ .map((slot) => slot.logString)
834
+ .join(', ')}`
741
835
  );
742
836
  }
743
837
 
838
+ /** logs slides video slots */
839
+ private logSlidesVideoReceiveSlots() {
840
+ LoggerProxy.logger.log(
841
+ `RemoteMediaManager#logSlidesVideoReceiveSlots --> SLIDES VIDEO receive slot: ${this.slots.screenShare.video?.logString}`
842
+ );
843
+ }
844
+
845
+ /** logs slides audio slots */
846
+ private logSlidesAudioReceiveSlots() {
847
+ LoggerProxy.logger.log(
848
+ `RemoteMediaManager#logSlidesAudioReceiveSlots --> SLIDES AUDIO receive slots: ${this.slots.screenShare.audio
849
+ .map((slot) => slot.logString)
850
+ .join(', ')}`
851
+ );
852
+ }
853
+
854
+ /** Logs all current receive slots */
855
+ public logAllReceiveSlots() {
856
+ this.logMainVideoReceiveSlots();
857
+ this.logMainAudioReceiveSlots();
858
+ this.logSlidesVideoReceiveSlots();
859
+ this.logSlidesAudioReceiveSlots();
860
+ }
861
+
744
862
  /**
745
863
  * Makes sure we have the right number of receive slots created for the current layout
746
864
  * and allocates them to the right video panes / pane groups
@@ -765,7 +883,7 @@ export class RemoteMediaManager extends EventsScope {
765
883
  // allocate receiver selected
766
884
  this.allocateSlotsToReceiverSelectedVideoPaneGroups();
767
885
 
768
- this.logReceieveSlots();
886
+ this.logMainVideoReceiveSlots();
769
887
 
770
888
  // If this is the initial layout, there may be some "unused" slots left because of the preallocation
771
889
  // done in this.preallocateVideoReceiveSlots(), so release them now
@@ -887,8 +1005,13 @@ export class RemoteMediaManager extends EventsScope {
887
1005
  }) {
888
1006
  const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
889
1007
 
890
- if (audio && this.media.audio) {
891
- this.media.audio.stop(commit);
1008
+ if (audio) {
1009
+ if (this.media.audio.main) {
1010
+ this.media.audio.main.stop(commit);
1011
+ }
1012
+ if (this.media.audio.si) {
1013
+ this.media.audio.si.stop(commit);
1014
+ }
892
1015
  }
893
1016
  if (video) {
894
1017
  Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
@@ -5,6 +5,8 @@ import {
5
5
  MultistreamRoapMediaConnection,
6
6
  } from '@webex/internal-media-core';
7
7
 
8
+ import {NamedMediaGroup} from '@webex/json-multistream';
9
+
8
10
  export default class SendSlotManager {
9
11
  private readonly slots: Map<MediaType, SendSlot> = new Map();
10
12
  private readonly LoggerProxy: any;
@@ -55,6 +57,33 @@ export default class SendSlotManager {
55
57
  return slot;
56
58
  }
57
59
 
60
+ /**
61
+ * Allow users to specify 'namedMediaGroups' to indicate which named media group its audio should be sent to.
62
+ * @param {MediaType} mediaType MediaType of the sendSlot to which the audio stream needs to be send to the media group
63
+ * @param {[]}namedMediaGroups - Allow users to specify 'namedMediaGroups'.If the value of 'namedMediaGroups' is zero,
64
+ * named media group will be canceled and the audio stream will be sent to the floor.
65
+ * @returns {void}
66
+ */
67
+ public setNamedMediaGroups(mediaType: MediaType, namedMediaGroups: NamedMediaGroup[]) {
68
+ if (mediaType !== MediaType.AudioMain) {
69
+ throw new Error(
70
+ `sendSlotManager cannot set named media group which media type is ${mediaType}`
71
+ );
72
+ }
73
+
74
+ const slot = this.slots.get(mediaType);
75
+
76
+ if (!slot) {
77
+ throw new Error(`Slot for ${mediaType} does not exist`);
78
+ }
79
+
80
+ slot.setNamedMediaGroups(namedMediaGroups);
81
+
82
+ this.LoggerProxy.logger.info(
83
+ `SendSlotsManager->setNamedMediaGroups#set named media group ${namedMediaGroups}`
84
+ );
85
+ }
86
+
58
87
  /**
59
88
  * This method publishes the given stream to the sendSlot for the given mediaType
60
89
  * @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
@@ -0,0 +1,320 @@
1
+ import {Defer} from '@webex/common';
2
+
3
+ import LoggerProxy from '../common/logs/logger-proxy';
4
+ import {ClusterNode} from './request';
5
+ import {convertStunUrlToTurn} from './util';
6
+
7
+ import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';
8
+
9
+ const DEFAULT_TIMEOUT = 3000;
10
+ const VIDEO_MESH_TIMEOUT = 1000;
11
+
12
+ // result for a specific transport protocol (like udp or tcp)
13
+ export type TransportResult = {
14
+ result: 'reachable' | 'unreachable' | 'untested';
15
+ latencyInMilliseconds?: number; // amount of time it took to get the first ICE candidate
16
+ clientMediaIPs?: string[];
17
+ };
18
+
19
+ // reachability result for a specific media cluster
20
+ export type ClusterReachabilityResult = {
21
+ udp: TransportResult;
22
+ tcp: TransportResult;
23
+ xtls: TransportResult;
24
+ };
25
+
26
+ /**
27
+ * A class that handles reachability checks for a single cluster.
28
+ */
29
+ export class ClusterReachability {
30
+ private numUdpUrls: number;
31
+ private numTcpUrls: number;
32
+ private result: ClusterReachabilityResult;
33
+ private pc?: RTCPeerConnection;
34
+ private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
35
+ private startTimestamp: number;
36
+ public readonly isVideoMesh: boolean;
37
+ public readonly name;
38
+
39
+ /**
40
+ * Constructor for ClusterReachability
41
+ * @param {string} name cluster name
42
+ * @param {ClusterNode} clusterInfo information about the media cluster
43
+ */
44
+ constructor(name: string, clusterInfo: ClusterNode) {
45
+ this.name = name;
46
+ this.isVideoMesh = clusterInfo.isVideoMesh;
47
+ this.numUdpUrls = clusterInfo.udp.length;
48
+ this.numTcpUrls = clusterInfo.tcp.length;
49
+
50
+ this.pc = this.createPeerConnection(clusterInfo);
51
+
52
+ this.defer = new Defer();
53
+ this.result = {
54
+ udp: {
55
+ result: 'untested',
56
+ },
57
+ tcp: {
58
+ result: 'untested',
59
+ },
60
+ xtls: {
61
+ result: 'untested',
62
+ },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Gets total elapsed time, can be called only after start() is called
68
+ * @returns {Number} Milliseconds
69
+ */
70
+ private getElapsedTime() {
71
+ return Math.round(performance.now() - this.startTimestamp);
72
+ }
73
+
74
+ /**
75
+ * Generate peerConnection config settings
76
+ * @param {ClusterNode} cluster
77
+ * @returns {RTCConfiguration} peerConnectionConfig
78
+ */
79
+ private buildPeerConnectionConfig(cluster: ClusterNode): RTCConfiguration {
80
+ const udpIceServers = cluster.udp.map((url) => ({
81
+ username: '',
82
+ credential: '',
83
+ urls: [url],
84
+ }));
85
+
86
+ // STUN servers are contacted only using UDP, so in order to test TCP reachability
87
+ // we pretend that Linus is a TURN server, because we can explicitly say "transport=tcp" in TURN urls.
88
+ // We then check for relay candidates to know if TURN-TCP worked (see registerIceCandidateListener()).
89
+ const tcpIceServers = cluster.tcp.map((urlString: string) => {
90
+ return {
91
+ username: 'webexturnreachuser',
92
+ credential: 'webexturnreachpwd',
93
+ urls: [convertStunUrlToTurn(urlString, 'tcp')],
94
+ };
95
+ });
96
+
97
+ return {
98
+ iceServers: [...udpIceServers, ...tcpIceServers],
99
+ iceCandidatePoolSize: 0,
100
+ iceTransportPolicy: 'all',
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Creates an RTCPeerConnection
106
+ * @param {ClusterNode} clusterInfo information about the media cluster
107
+ * @returns {RTCPeerConnection} peerConnection
108
+ */
109
+ private createPeerConnection(clusterInfo: ClusterNode) {
110
+ try {
111
+ const config = this.buildPeerConnectionConfig(clusterInfo);
112
+
113
+ const peerConnection = new RTCPeerConnection(config);
114
+
115
+ return peerConnection;
116
+ } catch (peerConnectionError) {
117
+ LoggerProxy.logger.warn(
118
+ `Reachability:index#createPeerConnection --> Error creating peerConnection:`,
119
+ peerConnectionError
120
+ );
121
+
122
+ return undefined;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @returns {ClusterReachabilityResult} reachability result for this cluster
128
+ */
129
+ getResult() {
130
+ return this.result;
131
+ }
132
+
133
+ /**
134
+ * Closes the peerConnection
135
+ *
136
+ * @returns {void}
137
+ */
138
+ private closePeerConnection() {
139
+ if (this.pc) {
140
+ this.pc.onicecandidate = null;
141
+ this.pc.onicegatheringstatechange = null;
142
+ this.pc.close();
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Resolves the defer, indicating that reachability checks for this cluster are completed
148
+ *
149
+ * @returns {void}
150
+ */
151
+ private finishReachabilityCheck() {
152
+ this.defer.resolve();
153
+ }
154
+
155
+ /**
156
+ * Adds public IP (client media IPs)
157
+ * @param {string} protocol
158
+ * @param {string} publicIP
159
+ * @returns {void}
160
+ */
161
+ private addPublicIP(protocol: 'udp' | 'tcp', publicIP?: string | null) {
162
+ const result = this.result[protocol];
163
+
164
+ if (publicIP) {
165
+ if (result.clientMediaIPs) {
166
+ if (!result.clientMediaIPs.includes(publicIP)) {
167
+ result.clientMediaIPs.push(publicIP);
168
+ }
169
+ } else {
170
+ result.clientMediaIPs = [publicIP];
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Registers a listener for the iceGatheringStateChange event
177
+ *
178
+ * @returns {void}
179
+ */
180
+ private registerIceGatheringStateChangeListener() {
181
+ this.pc.onicegatheringstatechange = () => {
182
+ const {COMPLETE} = ICE_GATHERING_STATE;
183
+
184
+ if (this.pc.iceConnectionState === COMPLETE) {
185
+ this.closePeerConnection();
186
+ this.finishReachabilityCheck();
187
+ }
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Checks if we have the results for all the protocols (UDP and TCP)
193
+ *
194
+ * @returns {boolean} true if we have all results, false otherwise
195
+ */
196
+ private haveWeGotAllResults(): boolean {
197
+ return ['udp', 'tcp'].every(
198
+ (protocol) =>
199
+ this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Stores the latency in the result for the given protocol and marks it as reachable
205
+ *
206
+ * @param {string} protocol
207
+ * @param {number} latency
208
+ * @returns {void}
209
+ */
210
+ private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
211
+ const result = this.result[protocol];
212
+
213
+ if (result.latencyInMilliseconds === undefined) {
214
+ LoggerProxy.logger.log(
215
+ // @ts-ignore
216
+ `Reachability:index#storeLatencyResult --> Successfully reached ${this.name} over ${protocol}: ${latency}ms`
217
+ );
218
+ result.latencyInMilliseconds = latency;
219
+ result.result = 'reachable';
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Registers a listener for the icecandidate event
225
+ *
226
+ * @returns {void}
227
+ */
228
+ private registerIceCandidateListener() {
229
+ this.pc.onicecandidate = (e) => {
230
+ const CANDIDATE_TYPES = {
231
+ SERVER_REFLEXIVE: 'srflx',
232
+ RELAY: 'relay',
233
+ };
234
+
235
+ if (e.candidate) {
236
+ if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
237
+ this.storeLatencyResult('udp', this.getElapsedTime());
238
+ this.addPublicIP('udp', e.candidate.address);
239
+ }
240
+
241
+ if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
242
+ this.storeLatencyResult('tcp', this.getElapsedTime());
243
+ // we don't add public IP for TCP, because in the case of relay candidates
244
+ // e.candidate.address is the TURN server address, not the client's public IP
245
+ }
246
+
247
+ if (this.haveWeGotAllResults()) {
248
+ this.closePeerConnection();
249
+ this.finishReachabilityCheck();
250
+ }
251
+ }
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Starts the process of doing UDP and TCP reachability checks on the media cluster.
257
+ * XTLS reachability checking is not supported.
258
+ *
259
+ * @returns {Promise}
260
+ */
261
+ async start(): Promise<ClusterReachabilityResult> {
262
+ if (!this.pc) {
263
+ LoggerProxy.logger.warn(
264
+ `Reachability:ClusterReachability#start --> Error: peerConnection is undefined`
265
+ );
266
+
267
+ return this.result;
268
+ }
269
+
270
+ // Initialize this.result as saying that nothing is reachable.
271
+ // It will get updated as we go along and successfully gather ICE candidates.
272
+ this.result.udp = {
273
+ result: this.numUdpUrls > 0 ? 'unreachable' : 'untested',
274
+ };
275
+ this.result.tcp = {
276
+ result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
277
+ };
278
+
279
+ try {
280
+ const offer = await this.pc.createOffer({offerToReceiveAudio: true});
281
+
282
+ this.startTimestamp = performance.now();
283
+
284
+ // not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
285
+ // we just need to make this call to trigger the ICE gathering process
286
+ this.pc.setLocalDescription(offer);
287
+
288
+ await this.gatherIceCandidates();
289
+ } catch (error) {
290
+ LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
291
+ }
292
+
293
+ return this.result;
294
+ }
295
+
296
+ /**
297
+ * Starts the process of gathering ICE candidates
298
+ *
299
+ * @returns {Promise} promise that's resolved once reachability checks for this cluster are completed or timeout is reached
300
+ */
301
+ private gatherIceCandidates() {
302
+ const timeout = this.isVideoMesh ? VIDEO_MESH_TIMEOUT : DEFAULT_TIMEOUT;
303
+
304
+ this.registerIceGatheringStateChangeListener();
305
+ this.registerIceCandidateListener();
306
+
307
+ // Set maximum timeout
308
+ setTimeout(() => {
309
+ const {CLOSED} = CONNECTION_STATE;
310
+
311
+ // Close any open peerConnections
312
+ if (this.pc.connectionState !== CLOSED) {
313
+ this.closePeerConnection();
314
+ this.finishReachabilityCheck();
315
+ }
316
+ }, timeout);
317
+
318
+ return this.defer.promise;
319
+ }
320
+ }