@webex/plugin-meetings 3.0.0-stream-classes.5 → 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 (233) 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 +67 -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 +15 -10
  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 +37 -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 +2143 -1087
  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 +33 -18
  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 +13 -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 +177 -65
  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 +65 -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 +272 -35
  114. package/dist/types/meeting/muteState.d.ts +2 -8
  115. package/dist/types/meeting/request.d.ts +2 -0
  116. package/dist/types/meeting/util.d.ts +16 -0
  117. package/dist/types/meeting-info/index.d.ts +7 -0
  118. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  119. package/dist/types/meetings/collection.d.ts +9 -0
  120. package/dist/types/meetings/index.d.ts +42 -14
  121. package/dist/types/member/index.d.ts +1 -0
  122. package/dist/types/members/types.d.ts +1 -0
  123. package/dist/types/members/util.d.ts +5 -0
  124. package/dist/types/metrics/constants.d.ts +12 -0
  125. package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
  126. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
  127. package/dist/types/multistream/remoteMediaManager.d.ts +16 -0
  128. package/dist/types/multistream/sendSlotManager.d.ts +9 -0
  129. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  130. package/dist/types/reachability/index.d.ts +59 -106
  131. package/dist/types/reachability/util.d.ts +8 -0
  132. package/dist/types/reconnection-manager/index.d.ts +10 -0
  133. package/dist/types/roap/index.d.ts +2 -1
  134. package/dist/types/roap/turnDiscovery.d.ts +21 -4
  135. package/dist/types/rtcMetrics/index.d.ts +15 -1
  136. package/dist/types/statsAnalyzer/index.d.ts +28 -11
  137. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  138. package/dist/types/webinar/collection.d.ts +16 -0
  139. package/dist/types/webinar/index.d.ts +5 -0
  140. package/dist/webinar/collection.js +44 -0
  141. package/dist/webinar/collection.js.map +1 -0
  142. package/dist/webinar/index.js +69 -0
  143. package/dist/webinar/index.js.map +1 -0
  144. package/package.json +3 -2
  145. package/src/common/errors/no-meeting-info.ts +24 -0
  146. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  147. package/src/common/errors/webex-errors.ts +19 -2
  148. package/src/common/logs/request.ts +5 -1
  149. package/src/config.ts +1 -1
  150. package/src/constants.ts +70 -6
  151. package/src/index.ts +5 -0
  152. package/src/interceptors/index.ts +3 -0
  153. package/src/interceptors/locusRetry.ts +67 -0
  154. package/src/interpretation/index.ts +18 -1
  155. package/src/locus-info/index.ts +19 -14
  156. package/src/locus-info/mediaSharesUtils.ts +16 -0
  157. package/src/locus-info/parser.ts +40 -21
  158. package/src/media/index.ts +8 -6
  159. package/src/media/properties.ts +17 -2
  160. package/src/mediaQualityMetrics/config.ts +103 -238
  161. package/src/meeting/in-meeting-actions.ts +8 -0
  162. package/src/meeting/index.ts +1471 -533
  163. package/src/meeting/muteState.ts +34 -20
  164. package/src/meeting/request.ts +18 -0
  165. package/src/meeting/util.ts +97 -0
  166. package/src/meeting-info/index.ts +47 -20
  167. package/src/meeting-info/meeting-info-v2.ts +27 -5
  168. package/src/meeting-info/utilv2.ts +1 -1
  169. package/src/meetings/collection.ts +13 -0
  170. package/src/meetings/index.ts +112 -31
  171. package/src/meetings/util.ts +2 -8
  172. package/src/member/index.ts +9 -0
  173. package/src/member/util.ts +14 -0
  174. package/src/members/index.ts +29 -2
  175. package/src/members/types.ts +1 -0
  176. package/src/members/util.ts +15 -1
  177. package/src/metrics/constants.ts +12 -0
  178. package/src/multistream/mediaRequestManager.ts +4 -1
  179. package/src/multistream/remoteMediaGroup.ts +19 -0
  180. package/src/multistream/remoteMediaManager.ts +101 -15
  181. package/src/multistream/sendSlotManager.ts +29 -0
  182. package/src/reachability/clusterReachability.ts +320 -0
  183. package/src/reachability/index.ts +221 -382
  184. package/src/reachability/request.ts +1 -1
  185. package/src/reachability/util.ts +24 -0
  186. package/src/reconnection-manager/index.ts +87 -83
  187. package/src/roap/index.ts +60 -24
  188. package/src/roap/request.ts +3 -16
  189. package/src/roap/turnDiscovery.ts +112 -39
  190. package/src/rtcMetrics/index.ts +71 -5
  191. package/src/statsAnalyzer/index.ts +430 -427
  192. package/src/statsAnalyzer/mqaUtil.ts +317 -168
  193. package/src/webinar/collection.ts +31 -0
  194. package/src/webinar/index.ts +62 -0
  195. package/test/integration/spec/journey.js +22 -22
  196. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  197. package/test/unit/spec/interpretation/index.ts +36 -3
  198. package/test/unit/spec/locus-info/index.js +87 -11
  199. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  200. package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
  201. package/test/unit/spec/locus-info/parser.js +54 -13
  202. package/test/unit/spec/media/index.ts +20 -4
  203. package/test/unit/spec/media/properties.ts +2 -2
  204. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  205. package/test/unit/spec/meeting/index.js +4178 -1289
  206. package/test/unit/spec/meeting/muteState.js +219 -67
  207. package/test/unit/spec/meeting/request.js +63 -12
  208. package/test/unit/spec/meeting/utils.js +93 -0
  209. package/test/unit/spec/meeting-info/index.js +180 -61
  210. package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
  211. package/test/unit/spec/meetings/collection.js +12 -0
  212. package/test/unit/spec/meetings/index.js +617 -204
  213. package/test/unit/spec/meetings/utils.js +35 -12
  214. package/test/unit/spec/member/index.js +8 -7
  215. package/test/unit/spec/member/util.js +32 -0
  216. package/test/unit/spec/members/index.js +130 -17
  217. package/test/unit/spec/members/utils.js +26 -0
  218. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  219. package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
  220. package/test/unit/spec/multistream/remoteMediaManager.ts +200 -1
  221. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  222. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  223. package/test/unit/spec/reachability/index.ts +505 -135
  224. package/test/unit/spec/reachability/util.ts +40 -0
  225. package/test/unit/spec/reconnection-manager/index.js +74 -17
  226. package/test/unit/spec/roap/index.ts +181 -61
  227. package/test/unit/spec/roap/request.ts +27 -3
  228. package/test/unit/spec/roap/turnDiscovery.ts +362 -101
  229. package/test/unit/spec/rtcMetrics/index.ts +57 -3
  230. package/test/unit/spec/stats-analyzer/index.js +1225 -12
  231. package/test/unit/spec/webinar/collection.ts +13 -0
  232. package/test/unit/spec/webinar/index.ts +60 -0
  233. 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));
@@ -525,22 +544,54 @@ export class RemoteMediaManager extends EventsScope {
525
544
  this.mediaRequestManagers.video.commit();
526
545
  }
527
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
+
528
576
  /**
529
577
  * Creates the audio slots
530
578
  */
531
579
  private async createAudioMedia() {
532
- // create the audio receive slots
580
+ // create si audio request
581
+ await this.createInterpretationAudioMedia(false);
582
+
583
+ // create main audio receive slots
533
584
  for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
534
585
  // eslint-disable-next-line no-await-in-loop
535
586
  const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
536
587
 
537
- this.slots.audio.push(slot);
588
+ this.slots.audio.main.push(slot);
538
589
  }
539
590
 
540
- // create a remote media group
541
- this.media.audio = new RemoteMediaGroup(
591
+ // create a remote media group for main audio
592
+ this.media.audio.main = new RemoteMediaGroup(
542
593
  this.mediaRequestManagers.audio,
543
- this.slots.audio,
594
+ this.slots.audio.main,
544
595
  255,
545
596
  true
546
597
  );
@@ -548,10 +599,40 @@ export class RemoteMediaManager extends EventsScope {
548
599
  this.emit(
549
600
  {file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
550
601
  Event.AudioCreated,
551
- this.media.audio
602
+ this.media.audio.main
552
603
  );
553
604
  }
554
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
+
555
636
  /**
556
637
  * Creates receive slots required for receiving screen share audio and video
557
638
  */
@@ -748,7 +829,7 @@ export class RemoteMediaManager extends EventsScope {
748
829
  /** logs main audio slots */
749
830
  private logMainAudioReceiveSlots() {
750
831
  LoggerProxy.logger.log(
751
- `RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
832
+ `RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio.main
752
833
  .map((slot) => slot.logString)
753
834
  .join(', ')}`
754
835
  );
@@ -924,8 +1005,13 @@ export class RemoteMediaManager extends EventsScope {
924
1005
  }) {
925
1006
  const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
926
1007
 
927
- if (audio && this.media.audio) {
928
- 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
+ }
929
1015
  }
930
1016
  if (video) {
931
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
+ }