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

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 +719 -490
  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 +398 -129
  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 +419 -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
@@ -2,7 +2,15 @@ import uuid from 'uuid';
2
2
  import {cloneDeep, isEqual, pick, isString, defer} from 'lodash';
3
3
  // @ts-ignore - Fix this
4
4
  import {StatelessWebexPlugin} from '@webex/webex-core';
5
- import {Media as WebRTCMedia, MediaConnection as MC} from '@webex/internal-media-core';
5
+ import {
6
+ ConnectionState,
7
+ Errors,
8
+ ErrorType,
9
+ Event,
10
+ Media as WebRTCMedia,
11
+ MediaType,
12
+ RemoteTrackType,
13
+ } from '@webex/internal-media-core';
6
14
 
7
15
  import {
8
16
  MeetingNotActiveError,
@@ -28,8 +36,10 @@ import ReconnectionManager from '../reconnection-manager';
28
36
  import MeetingRequest from './request';
29
37
  import Members from '../members/index';
30
38
  import MeetingUtil from './util';
39
+ import RecordingUtil from '../recording-controller/util';
31
40
  import MediaUtil from '../media/util';
32
41
  import Transcription from '../transcription';
42
+ import {Reactions, SkinTones} from '../reactions/reactions';
33
43
  import PasswordError from '../common/errors/password-error';
34
44
  import CaptchaError from '../common/errors/captcha-error';
35
45
  import ReconnectionError from '../common/errors/reconnection';
@@ -44,6 +54,7 @@ import {
44
54
  EVENT_TRIGGERS,
45
55
  EVENT_TYPES,
46
56
  EVENTS,
57
+ BREAKOUTS,
47
58
  FLOOR_ACTION,
48
59
  FULL_STATE,
49
60
  LAYOUT_TYPES,
@@ -84,10 +95,18 @@ import {
84
95
  Event as RemoteMediaManagerEvent,
85
96
  } from '../multistream/remoteMediaManager';
86
97
  import {MultistreamMedia} from '../multistream/multistreamMedia';
87
- import {SkinTones, Reactions} from '../reactions/reactions';
88
- import {Reaction, ReactionType, SkinToneType} from '../reactions/reactions.type';
98
+ import {
99
+ Reaction,
100
+ ReactionType,
101
+ SkinToneType,
102
+ ProcessedReaction,
103
+ RelayEvent,
104
+ } from '../reactions/reactions.type';
105
+ import Breakouts from '../breakouts';
89
106
 
90
107
  import InMeetingActions from './in-meeting-actions';
108
+ import {REACTION_RELAY_TYPES} from '../reactions/constants';
109
+ import RecordingController from '../recording-controller';
91
110
 
92
111
  const {isBrowser} = BrowserDetection();
93
112
 
@@ -111,6 +130,7 @@ export const MEDIA_UPDATE_TYPE = {
111
130
  AUDIO: 'AUDIO',
112
131
  VIDEO: 'VIDEO',
113
132
  SHARE: 'SHARE',
133
+ LAMBDA: 'LAMBDA',
114
134
  };
115
135
 
116
136
  /**
@@ -403,6 +423,7 @@ export const MEDIA_UPDATE_TYPE = {
403
423
  export default class Meeting extends StatelessWebexPlugin {
404
424
  attrs: any;
405
425
  audio: any;
426
+ breakouts: any;
406
427
  conversationUrl: string;
407
428
  correlationId: string;
408
429
  destination: string;
@@ -454,6 +475,8 @@ export default class Meeting extends StatelessWebexPlugin {
454
475
  mediaRequestManagers: {
455
476
  audio: MediaRequestManager;
456
477
  video: MediaRequestManager;
478
+ screenShareAudio: MediaRequestManager;
479
+ screenShareVideo: MediaRequestManager;
457
480
  };
458
481
 
459
482
  meetingInfoFailureReason: string;
@@ -463,6 +486,7 @@ export default class Meeting extends StatelessWebexPlugin {
463
486
  queuedMediaUpdates: any[];
464
487
  recording: any;
465
488
  remoteMediaManager: RemoteMediaManager | null;
489
+ recordingController: RecordingController;
466
490
  requiredCaptcha: any;
467
491
  receiveSlotManager: ReceiveSlotManager;
468
492
  shareStatus: string;
@@ -573,16 +597,25 @@ export default class Meeting extends StatelessWebexPlugin {
573
597
  */
574
598
  // TODO: needs to be defined as a class
575
599
  this.meetingInfo = {};
600
+ /**
601
+ * @instance
602
+ * @type {Breakouts}
603
+ * @public
604
+ * @memberof Meeting
605
+ */
606
+ // @ts-ignore
607
+ this.breakouts = new Breakouts({}, {parent: this.webex});
576
608
  /**
577
609
  * helper class for managing receive slots (for multistream media connections)
578
610
  */
579
611
  this.receiveSlotManager = new ReceiveSlotManager(this);
580
612
  /**
581
- * Helper class for managing media requests for main video (for multistream media connections)
582
- * All media requests sent out for main video for this meeting have to go through it.
613
+ * Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
614
+ * All multistream media requests sent out for this meeting have to go through them.
583
615
  */
584
616
  this.mediaRequestManagers = {
585
- audio: new MediaRequestManager((mediaRequests) => {
617
+ // @ts-ignore - config coming from registerPlugin
618
+ audio: new MediaRequestManager(this.config.degradationPreferences, (mediaRequests) => {
586
619
  if (!this.mediaProperties.webrtcMediaConnection) {
587
620
  LoggerProxy.logger.warn(
588
621
  'Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created'
@@ -590,12 +623,10 @@ export default class Meeting extends StatelessWebexPlugin {
590
623
 
591
624
  return;
592
625
  }
593
- this.mediaProperties.webrtcMediaConnection.requestMedia(
594
- MC.MediaType.AudioMain,
595
- mediaRequests
596
- );
626
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MediaType.AudioMain, mediaRequests);
597
627
  }),
598
- video: new MediaRequestManager((mediaRequests) => {
628
+ // @ts-ignore - config coming from registerPlugin
629
+ video: new MediaRequestManager(this.config.degradationPreferences, (mediaRequests) => {
599
630
  if (!this.mediaProperties.webrtcMediaConnection) {
600
631
  LoggerProxy.logger.warn(
601
632
  'Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created'
@@ -603,11 +634,42 @@ export default class Meeting extends StatelessWebexPlugin {
603
634
 
604
635
  return;
605
636
  }
606
- this.mediaProperties.webrtcMediaConnection.requestMedia(
607
- MC.MediaType.VideoMain,
608
- mediaRequests
609
- );
637
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MediaType.VideoMain, mediaRequests);
610
638
  }),
639
+ screenShareAudio: new MediaRequestManager(
640
+ // @ts-ignore - config coming from registerPlugin
641
+ this.config.degradationPreferences,
642
+ (mediaRequests) => {
643
+ if (!this.mediaProperties.webrtcMediaConnection) {
644
+ LoggerProxy.logger.warn(
645
+ 'Meeting:index#mediaRequestManager --> trying to send screenshare audio media request before media connection was created'
646
+ );
647
+
648
+ return;
649
+ }
650
+ this.mediaProperties.webrtcMediaConnection.requestMedia(
651
+ MediaType.AudioSlides,
652
+ mediaRequests
653
+ );
654
+ }
655
+ ),
656
+ screenShareVideo: new MediaRequestManager(
657
+ // @ts-ignore - config coming from registerPlugin
658
+ this.config.degradationPreferences,
659
+ (mediaRequests) => {
660
+ if (!this.mediaProperties.webrtcMediaConnection) {
661
+ LoggerProxy.logger.warn(
662
+ 'Meeting:index#mediaRequestManager --> trying to send screenshare video media request before media connection was created'
663
+ );
664
+
665
+ return;
666
+ }
667
+ this.mediaProperties.webrtcMediaConnection.requestMedia(
668
+ MediaType.VideoSlides,
669
+ mediaRequests
670
+ );
671
+ }
672
+ ),
611
673
  };
612
674
  /**
613
675
  * @instance
@@ -620,8 +682,8 @@ export default class Meeting extends StatelessWebexPlugin {
620
682
  locusUrl: attrs.locus && attrs.locus.url,
621
683
  receiveSlotManager: this.receiveSlotManager,
622
684
  mediaRequestManagers: this.mediaRequestManagers,
623
- // @ts-ignore - Fix type
624
685
  },
686
+ // @ts-ignore - Fix type
625
687
  {parent: this.webex}
626
688
  );
627
689
  /**
@@ -924,6 +986,7 @@ export default class Meeting extends StatelessWebexPlugin {
924
986
  */
925
987
  // @ts-ignore - Fix type
926
988
  this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
989
+
927
990
  // We had to add listeners first before setting up the locus instance
928
991
  /**
929
992
  * @instance
@@ -1023,6 +1086,20 @@ export default class Meeting extends StatelessWebexPlugin {
1023
1086
  */
1024
1087
  this.keepAliveTimerId = null;
1025
1088
 
1089
+ /**
1090
+ * The class that helps to control recording functions: start, stop, pause, resume, etc
1091
+ * @instance
1092
+ * @type {RecordingController}
1093
+ * @public
1094
+ * @memberof Meeting
1095
+ */
1096
+ this.recordingController = new RecordingController(this.meetingRequest, {
1097
+ serviceUrl: this.locusInfo?.links?.services?.record?.url,
1098
+ sessionId: this.locusInfo?.fullState?.sessionId,
1099
+ locusUrl: this.locusInfo?.url,
1100
+ displayHints: [],
1101
+ });
1102
+
1026
1103
  this.setUpLocusInfoListeners();
1027
1104
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
1028
1105
  this.hasJoinedOnce = false;
@@ -1108,8 +1185,8 @@ export default class Meeting extends StatelessWebexPlugin {
1108
1185
  return Promise.resolve();
1109
1186
  } catch (err) {
1110
1187
  if (err instanceof MeetingInfoV2PasswordError) {
1111
- // @ts-ignore
1112
1188
  LoggerProxy.logger.info(
1189
+ // @ts-ignore
1113
1190
  `Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`
1114
1191
  );
1115
1192
 
@@ -1128,8 +1205,8 @@ export default class Meeting extends StatelessWebexPlugin {
1128
1205
 
1129
1206
  throw new PasswordError();
1130
1207
  } else if (err instanceof MeetingInfoV2CaptchaError) {
1131
- // @ts-ignore
1132
1208
  LoggerProxy.logger.info(
1209
+ // @ts-ignore
1133
1210
  `Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
1134
1211
  );
1135
1212
 
@@ -1201,22 +1278,25 @@ export default class Meeting extends StatelessWebexPlugin {
1201
1278
  // we have to pass the wbxappapi hostname as the siteFullName parameter
1202
1279
  const {hostname} = new URL(this.requiredCaptcha.refreshURL);
1203
1280
 
1204
- return this.meetingRequest
1205
- .refreshCaptcha({
1206
- captchaRefreshUrl: `${this.requiredCaptcha.refreshURL}&siteFullName=${hostname}`,
1207
- captchaId: this.requiredCaptcha.captchaId,
1208
- })
1209
- .then((response) => {
1210
- this.requiredCaptcha.captchaId = response.body.captchaID;
1211
- this.requiredCaptcha.verificationImageURL = response.body.verificationImageURL;
1212
- this.requiredCaptcha.verificationAudioURL = response.body.verificationAudioURL;
1213
- })
1214
- .catch((error) => {
1215
- LoggerProxy.logger.error(
1216
- `Meeting:index#refreshCaptcha --> Error Unable to refresh captcha for ${this.destination} - ${error}`
1217
- );
1218
- throw error;
1219
- });
1281
+ return (
1282
+ this.meetingRequest
1283
+ // @ts-ignore
1284
+ .refreshCaptcha({
1285
+ captchaRefreshUrl: `${this.requiredCaptcha.refreshURL}&siteFullName=${hostname}`,
1286
+ captchaId: this.requiredCaptcha.captchaId,
1287
+ })
1288
+ .then((response) => {
1289
+ this.requiredCaptcha.captchaId = response.body.captchaID;
1290
+ this.requiredCaptcha.verificationImageURL = response.body.verificationImageURL;
1291
+ this.requiredCaptcha.verificationAudioURL = response.body.verificationAudioURL;
1292
+ })
1293
+ .catch((error) => {
1294
+ LoggerProxy.logger.error(
1295
+ `Meeting:index#refreshCaptcha --> Error Unable to refresh captcha for ${this.destination} - ${error}`
1296
+ );
1297
+ throw error;
1298
+ })
1299
+ );
1220
1300
  }
1221
1301
 
1222
1302
  /**
@@ -1229,6 +1309,7 @@ export default class Meeting extends StatelessWebexPlugin {
1229
1309
  // meeting update listeners
1230
1310
  this.setUpLocusInfoSelfListener();
1231
1311
  this.setUpLocusInfoMeetingListener();
1312
+ this.setUpLocusServicesListener();
1232
1313
  // members update listeners
1233
1314
  this.setUpLocusFullStateListener();
1234
1315
  this.setUpLocusUrlListener();
@@ -1241,6 +1322,49 @@ export default class Meeting extends StatelessWebexPlugin {
1241
1322
  this.setUpLocusInfoMeetingInfoListener();
1242
1323
  this.setUpLocusInfoAssignHostListener();
1243
1324
  this.setUpLocusInfoMediaInactiveListener();
1325
+ this.setUpBreakoutsListener();
1326
+ }
1327
+
1328
+ /**
1329
+ * Set up the listeners for breakouts
1330
+ * @returns {undefined}
1331
+ * @private
1332
+ * @memberof Meeting
1333
+ */
1334
+ setUpBreakoutsListener() {
1335
+ this.breakouts.on(BREAKOUTS.EVENTS.BREAKOUTS_CLOSING, () => {
1336
+ Trigger.trigger(
1337
+ this,
1338
+ {
1339
+ file: 'meeting/index',
1340
+ function: 'setUpBreakoutsListener',
1341
+ },
1342
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
1343
+ );
1344
+ });
1345
+
1346
+ this.breakouts.on(BREAKOUTS.EVENTS.MESSAGE, (messageEvent) => {
1347
+ Trigger.trigger(
1348
+ this,
1349
+ {
1350
+ file: 'meeting/index',
1351
+ function: 'setUpBreakoutsListener',
1352
+ },
1353
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
1354
+ messageEvent
1355
+ );
1356
+ });
1357
+
1358
+ this.breakouts.on(BREAKOUTS.EVENTS.MEMBERS_UPDATE, () => {
1359
+ Trigger.trigger(
1360
+ this,
1361
+ {
1362
+ file: 'meeting/index',
1363
+ function: 'setUpBreakoutsListener',
1364
+ },
1365
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
1366
+ );
1367
+ });
1244
1368
  }
1245
1369
 
1246
1370
  /**
@@ -1786,6 +1910,18 @@ export default class Meeting extends StatelessWebexPlugin {
1786
1910
  }
1787
1911
  );
1788
1912
 
1913
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED, ({breakout}) => {
1914
+ this.breakouts.updateBreakout(breakout);
1915
+ Trigger.trigger(
1916
+ this,
1917
+ {
1918
+ file: 'meeting/index',
1919
+ function: 'setupLocusControlsListener',
1920
+ },
1921
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
1922
+ );
1923
+ });
1924
+
1789
1925
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
1790
1926
  Trigger.trigger(
1791
1927
  this,
@@ -2041,8 +2177,26 @@ export default class Meeting extends StatelessWebexPlugin {
2041
2177
  private setUpLocusUrlListener() {
2042
2178
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
2043
2179
  this.members.locusUrlUpdate(payload);
2180
+ this.breakouts.locusUrlUpdate(payload);
2044
2181
  this.locusUrl = payload;
2045
2182
  this.locusId = this.locusUrl?.split('/').pop();
2183
+ this.recordingController.setLocusUrl(this.locusUrl);
2184
+ });
2185
+ }
2186
+
2187
+ /**
2188
+ * Set up the locus info service link listener
2189
+ * update the locusInfo for recording controller
2190
+ * does not currently re-emit the event as it's internal only
2191
+ * payload is unused
2192
+ * @returns {undefined}
2193
+ * @private
2194
+ * @memberof Meeting
2195
+ */
2196
+ private setUpLocusServicesListener() {
2197
+ this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
2198
+ this.recordingController.setServiceUrl(payload?.services?.record?.url);
2199
+ this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
2046
2200
  });
2047
2201
  }
2048
2202
 
@@ -2092,10 +2246,10 @@ export default class Meeting extends StatelessWebexPlugin {
2092
2246
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
2093
2247
  canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
2094
2248
  canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
2095
- canStartRecording: MeetingUtil.canUserRecord(payload.info.userDisplayHints),
2096
- canStopRecording: MeetingUtil.canUserStop(payload.info.userDisplayHints),
2097
- canPauseRecording: MeetingUtil.canUserPause(payload.info.userDisplayHints),
2098
- canResumeRecording: MeetingUtil.canUserResume(payload.info.userDisplayHints),
2249
+ canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
2250
+ canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
2251
+ canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
2252
+ canResumeRecording: RecordingUtil.canUserResume(payload.info.userDisplayHints),
2099
2253
  canRaiseHand: MeetingUtil.canUserRaiseHand(payload.info.userDisplayHints),
2100
2254
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(payload.info.userDisplayHints),
2101
2255
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(
@@ -2127,6 +2281,8 @@ export default class Meeting extends StatelessWebexPlugin {
2127
2281
  ),
2128
2282
  });
2129
2283
 
2284
+ this.recordingController.setDisplayHints(payload.info.userDisplayHints);
2285
+
2130
2286
  if (changed) {
2131
2287
  Trigger.trigger(
2132
2288
  this,
@@ -2150,6 +2306,7 @@ export default class Meeting extends StatelessWebexPlugin {
2150
2306
  * @returns {void}
2151
2307
  */
2152
2308
  handleDataChannelUrlChange(datachannelUrl) {
2309
+ // @ts-ignore - config coming from registerPlugin
2153
2310
  if (datachannelUrl && this.config.enableAutomaticLLM) {
2154
2311
  // Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
2155
2312
  // after the MEETING_INFO_UPDATED callback finishes
@@ -2330,6 +2487,18 @@ export default class Meeting extends StatelessWebexPlugin {
2330
2487
  );
2331
2488
  });
2332
2489
 
2490
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED, (payload) => {
2491
+ this.breakouts.updateBreakoutSessions(payload);
2492
+ Trigger.trigger(
2493
+ this,
2494
+ {
2495
+ file: 'meeting/index',
2496
+ function: 'setUpLocusInfoSelfListener',
2497
+ },
2498
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
2499
+ );
2500
+ });
2501
+
2333
2502
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_IS_SHARING_BLOCKED_CHANGE, (payload) => {
2334
2503
  Trigger.trigger(
2335
2504
  this,
@@ -2365,7 +2534,7 @@ export default class Meeting extends StatelessWebexPlugin {
2365
2534
  .catch((error) => {
2366
2535
  // @ts-ignore
2367
2536
  LoggerProxy.logger.error(
2368
- `Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`
2537
+ `Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
2369
2538
  );
2370
2539
  });
2371
2540
  }
@@ -2400,7 +2569,7 @@ export default class Meeting extends StatelessWebexPlugin {
2400
2569
  .catch((error) => {
2401
2570
  // @ts-ignore
2402
2571
  LoggerProxy.logger.error(
2403
- `Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`
2572
+ `Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
2404
2573
  );
2405
2574
  });
2406
2575
  } else {
@@ -2642,6 +2811,7 @@ export default class Meeting extends StatelessWebexPlugin {
2642
2811
  this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
2643
2812
  // @ts-ignore - config coming from registerPlugin
2644
2813
  this.setSipUri(
2814
+ // @ts-ignore
2645
2815
  this.config.experimental.enableUnifiedMeetings
2646
2816
  ? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
2647
2817
  : locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
@@ -3590,6 +3760,20 @@ export default class Meeting extends StatelessWebexPlugin {
3590
3760
  return false;
3591
3761
  }
3592
3762
 
3763
+ /**
3764
+ * Check if the meeting supports the Reactions
3765
+ * @returns {boolean}
3766
+ */
3767
+ isReactionsSupported() {
3768
+ if (this.locusInfo?.controls?.reactions.enabled) {
3769
+ return true;
3770
+ }
3771
+
3772
+ LoggerProxy.logger.error('Meeting:index#isReactionsSupported --> Reactions is not supported');
3773
+
3774
+ return false;
3775
+ }
3776
+
3593
3777
  /**
3594
3778
  * Monitor the Low-Latency Mercury (LLM) web socket connection on `onError` and `onClose` states
3595
3779
  * @private
@@ -3641,6 +3825,7 @@ export default class Meeting extends StatelessWebexPlugin {
3641
3825
  // @ts-ignore - fix type
3642
3826
  const {
3643
3827
  body: {webSocketUrl},
3828
+ // @ts-ignore
3644
3829
  } = await this.request({
3645
3830
  method: HTTP_VERBS.POST,
3646
3831
  uri: datachannelUrl,
@@ -3690,6 +3875,44 @@ export default class Meeting extends StatelessWebexPlugin {
3690
3875
  }
3691
3876
  }
3692
3877
 
3878
+ /**
3879
+ * Callback called when a relay event is received from meeting LLM Connection
3880
+ * @param {RelayEvent} e Event object coming from LLM Connection
3881
+ * @private
3882
+ * @returns {void}
3883
+ */
3884
+ private processRelayEvent = (e: RelayEvent): void => {
3885
+ switch (e.data.relayType) {
3886
+ case REACTION_RELAY_TYPES.REACTION:
3887
+ if (
3888
+ // @ts-ignore - config coming from registerPlugin
3889
+ (this.config.receiveReactions || options.receiveReactions) &&
3890
+ this.isReactionsSupported()
3891
+ ) {
3892
+ const {name} = this.members.membersCollection.get(e.data.sender.participantId);
3893
+ const processedReaction: ProcessedReaction = {
3894
+ reaction: e.data.reaction,
3895
+ sender: {
3896
+ id: e.data.sender.participantId,
3897
+ name,
3898
+ },
3899
+ };
3900
+ Trigger.trigger(
3901
+ this,
3902
+ {
3903
+ file: 'meeting/index',
3904
+ function: 'join',
3905
+ },
3906
+ EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
3907
+ processedReaction
3908
+ );
3909
+ }
3910
+ break;
3911
+ default:
3912
+ break;
3913
+ }
3914
+ };
3915
+
3693
3916
  /**
3694
3917
  * stop recieving Transcription by closing
3695
3918
  * the web socket connection properly
@@ -3863,8 +4086,12 @@ export default class Meeting extends StatelessWebexPlugin {
3863
4086
  return join;
3864
4087
  })
3865
4088
  .then(async (join) => {
4089
+ // @ts-ignore - config coming from registerPlugin
3866
4090
  if (this.config.enableAutomaticLLM) {
3867
4091
  await this.updateLLMConnection();
4092
+ // @ts-ignore - Fix type
4093
+ this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
4094
+ LoggerProxy.logger.info('Meeting:index#join --> enabled to receive relay events!');
3868
4095
  }
3869
4096
 
3870
4097
  return join;
@@ -3931,21 +4158,28 @@ export default class Meeting extends StatelessWebexPlugin {
3931
4158
  * @returns {Promise}
3932
4159
  */
3933
4160
  async updateLLMConnection() {
4161
+ // @ts-ignore - Fix type
3934
4162
  const {url, info: {datachannelUrl} = {}} = this.locusInfo;
3935
4163
 
3936
4164
  const isJoined = this.joinedWith && this.joinedWith.state === 'JOINED';
3937
4165
 
4166
+ // @ts-ignore - Fix type
3938
4167
  if (this.webex.internal.llm.isConnected()) {
4168
+ // @ts-ignore - Fix type
3939
4169
  if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
3940
4170
  return undefined;
3941
4171
  }
4172
+ // @ts-ignore - Fix type
3942
4173
  await this.webex.internal.llm.disconnectLLM();
4174
+ // @ts-ignore - Fix type
4175
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
3943
4176
  }
3944
4177
 
3945
4178
  if (!isJoined) {
3946
4179
  return undefined;
3947
4180
  }
3948
4181
 
4182
+ // @ts-ignore - Fix type
3949
4183
  return this.webex.internal.llm.registerAndConnect(url, datachannelUrl);
3950
4184
  }
3951
4185
 
@@ -3988,28 +4222,31 @@ export default class Meeting extends StatelessWebexPlugin {
3988
4222
 
3989
4223
  if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
3990
4224
 
3991
- return this.meetingRequest
3992
- .dialIn({
3993
- correlationId,
3994
- dialInUrl: this.dialInUrl,
3995
- locusUrl,
3996
- clientUrl: this.deviceUrl,
3997
- })
3998
- .then((res) => {
3999
- this.locusInfo.onFullLocus(res.body.locus);
4000
- })
4001
- .catch((error) => {
4002
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
4003
- correlation_id: this.correlationId,
4004
- dial_in_url: this.dialInUrl,
4005
- locus_id: locusUrl.split('/').pop(),
4006
- client_url: this.deviceUrl,
4007
- reason: error.error?.message,
4008
- stack: error.stack,
4009
- });
4225
+ return (
4226
+ this.meetingRequest
4227
+ // @ts-ignore
4228
+ .dialIn({
4229
+ correlationId,
4230
+ dialInUrl: this.dialInUrl,
4231
+ locusUrl,
4232
+ clientUrl: this.deviceUrl,
4233
+ })
4234
+ .then((res) => {
4235
+ this.locusInfo.onFullLocus(res.body.locus);
4236
+ })
4237
+ .catch((error) => {
4238
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
4239
+ correlation_id: this.correlationId,
4240
+ dial_in_url: this.dialInUrl,
4241
+ locus_id: locusUrl.split('/').pop(),
4242
+ client_url: this.deviceUrl,
4243
+ reason: error.error?.message,
4244
+ stack: error.stack,
4245
+ });
4010
4246
 
4011
- return Promise.reject(error);
4012
- });
4247
+ return Promise.reject(error);
4248
+ })
4249
+ );
4013
4250
  }
4014
4251
 
4015
4252
  /**
@@ -4026,29 +4263,32 @@ export default class Meeting extends StatelessWebexPlugin {
4026
4263
 
4027
4264
  if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
4028
4265
 
4029
- return this.meetingRequest
4030
- .dialOut({
4031
- correlationId,
4032
- dialOutUrl: this.dialOutUrl,
4033
- phoneNumber,
4034
- locusUrl,
4035
- clientUrl: this.deviceUrl,
4036
- })
4037
- .then((res) => {
4038
- this.locusInfo.onFullLocus(res.body.locus);
4039
- })
4040
- .catch((error) => {
4041
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
4042
- correlation_id: this.correlationId,
4043
- dial_out_url: this.dialOutUrl,
4044
- locus_id: locusUrl.split('/').pop(),
4045
- client_url: this.deviceUrl,
4046
- reason: error.error?.message,
4047
- stack: error.stack,
4048
- });
4266
+ return (
4267
+ this.meetingRequest
4268
+ // @ts-ignore
4269
+ .dialOut({
4270
+ correlationId,
4271
+ dialOutUrl: this.dialOutUrl,
4272
+ phoneNumber,
4273
+ locusUrl,
4274
+ clientUrl: this.deviceUrl,
4275
+ })
4276
+ .then((res) => {
4277
+ this.locusInfo.onFullLocus(res.body.locus);
4278
+ })
4279
+ .catch((error) => {
4280
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
4281
+ correlation_id: this.correlationId,
4282
+ dial_out_url: this.dialOutUrl,
4283
+ locus_id: locusUrl.split('/').pop(),
4284
+ client_url: this.deviceUrl,
4285
+ reason: error.error?.message,
4286
+ stack: error.stack,
4287
+ });
4049
4288
 
4050
- return Promise.reject(error);
4051
- });
4289
+ return Promise.reject(error);
4290
+ })
4291
+ );
4052
4292
  }
4053
4293
 
4054
4294
  /**
@@ -4395,7 +4635,7 @@ export default class Meeting extends StatelessWebexPlugin {
4395
4635
  Metrics.sendBehavioralMetric(metricName, data, metadata);
4396
4636
  };
4397
4637
 
4398
- if (error instanceof MC.Errors.SdpOfferCreationError) {
4638
+ if (error instanceof Errors.SdpOfferCreationError) {
4399
4639
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4400
4640
 
4401
4641
  Metrics.postEvent({
@@ -4409,8 +4649,8 @@ export default class Meeting extends StatelessWebexPlugin {
4409
4649
  },
4410
4650
  });
4411
4651
  } else if (
4412
- error instanceof MC.Errors.SdpOfferHandlingError ||
4413
- error instanceof MC.Errors.SdpAnswerHandlingError
4652
+ error instanceof Errors.SdpOfferHandlingError ||
4653
+ error instanceof Errors.SdpAnswerHandlingError
4414
4654
  ) {
4415
4655
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4416
4656
 
@@ -4424,8 +4664,8 @@ export default class Meeting extends StatelessWebexPlugin {
4424
4664
  ],
4425
4665
  },
4426
4666
  });
4427
- } else if (error instanceof MC.Errors.SdpError) {
4428
- // this covers also the case of MC.Errors.IceGatheringError which extends MC.Errors.SdpError
4667
+ } else if (error instanceof Errors.SdpError) {
4668
+ // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
4429
4669
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
4430
4670
 
4431
4671
  Metrics.postEvent({
@@ -4442,19 +4682,19 @@ export default class Meeting extends StatelessWebexPlugin {
4442
4682
  };
4443
4683
 
4444
4684
  setupMediaConnectionListeners = () => {
4445
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_STARTED, () => {
4685
+ this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_STARTED, () => {
4446
4686
  this.isRoapInProgress = true;
4447
4687
  });
4448
4688
 
4449
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_DONE, () => {
4689
+ this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
4450
4690
  this.mediaNegotiatedEvent();
4451
4691
  this.isRoapInProgress = false;
4452
4692
  this.processNextQueuedMediaUpdate();
4453
4693
  });
4454
4694
 
4455
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_FAILURE, this.handleRoapFailure);
4695
+ this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_FAILURE, this.handleRoapFailure);
4456
4696
 
4457
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_MESSAGE_TO_SEND, (event) => {
4697
+ this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, (event) => {
4458
4698
  const LOG_HEADER = 'Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND -->';
4459
4699
 
4460
4700
  switch (event.roapMessage.messageType) {
@@ -4536,8 +4776,8 @@ export default class Meeting extends StatelessWebexPlugin {
4536
4776
 
4537
4777
  case 'ERROR':
4538
4778
  if (
4539
- event.roapMessage.errorType === MC.ErrorType.CONFLICT ||
4540
- event.roapMessage.errorType === MC.ErrorType.DOUBLECONFLICT
4779
+ event.roapMessage.errorType === ErrorType.CONFLICT ||
4780
+ event.roapMessage.errorType === ErrorType.DOUBLECONFLICT
4541
4781
  ) {
4542
4782
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
4543
4783
  correlation_id: this.correlationId,
@@ -4569,7 +4809,7 @@ export default class Meeting extends StatelessWebexPlugin {
4569
4809
  });
4570
4810
 
4571
4811
  // eslint-disable-next-line no-param-reassign
4572
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.REMOTE_TRACK_ADDED, (event) => {
4812
+ this.mediaProperties.webrtcMediaConnection.on(Event.REMOTE_TRACK_ADDED, (event) => {
4573
4813
  LoggerProxy.logger.log(
4574
4814
  `Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(
4575
4815
  event
@@ -4582,15 +4822,15 @@ export default class Meeting extends StatelessWebexPlugin {
4582
4822
  let eventType;
4583
4823
 
4584
4824
  switch (event.type) {
4585
- case MC.RemoteTrackType.AUDIO:
4825
+ case RemoteTrackType.AUDIO:
4586
4826
  eventType = EVENT_TYPES.REMOTE_AUDIO;
4587
4827
  this.mediaProperties.setRemoteAudioTrack(event.track);
4588
4828
  break;
4589
- case MC.RemoteTrackType.VIDEO:
4829
+ case RemoteTrackType.VIDEO:
4590
4830
  eventType = EVENT_TYPES.REMOTE_VIDEO;
4591
4831
  this.mediaProperties.setRemoteVideoTrack(event.track);
4592
4832
  break;
4593
- case MC.RemoteTrackType.SCREENSHARE_VIDEO:
4833
+ case RemoteTrackType.SCREENSHARE_VIDEO:
4594
4834
  if (event.track) {
4595
4835
  eventType = EVENT_TYPES.REMOTE_SHARE;
4596
4836
  this.mediaProperties.setRemoteShare(event.track);
@@ -4623,7 +4863,7 @@ export default class Meeting extends StatelessWebexPlugin {
4623
4863
  }
4624
4864
  });
4625
4865
 
4626
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.CONNECTION_STATE_CHANGED, (event) => {
4866
+ this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
4627
4867
  const connectionFailed = () => {
4628
4868
  // we know the media connection failed and browser will not attempt to recover it any more
4629
4869
  // so reset the timer as it's not needed anymore, we want to reconnect immediately
@@ -4656,10 +4896,10 @@ export default class Meeting extends StatelessWebexPlugin {
4656
4896
  `Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`
4657
4897
  );
4658
4898
  switch (event.state) {
4659
- case MC.ConnectionState.Connecting:
4899
+ case ConnectionState.Connecting:
4660
4900
  Metrics.postEvent({event: eventType.ICE_START, meeting: this});
4661
4901
  break;
4662
- case MC.ConnectionState.Connected:
4902
+ case ConnectionState.Connected:
4663
4903
  Metrics.postEvent({event: eventType.ICE_END, meeting: this});
4664
4904
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
4665
4905
  correlation_id: this.correlationId,
@@ -4668,7 +4908,7 @@ export default class Meeting extends StatelessWebexPlugin {
4668
4908
  this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
4669
4909
  this.reconnectionManager.iceReconnected();
4670
4910
  break;
4671
- case MC.ConnectionState.Disconnected:
4911
+ case ConnectionState.Disconnected:
4672
4912
  this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
4673
4913
  this.reconnectionManager.waitForIceReconnect().catch(() => {
4674
4914
  LoggerProxy.logger.info(
@@ -4678,7 +4918,7 @@ export default class Meeting extends StatelessWebexPlugin {
4678
4918
  connectionFailed();
4679
4919
  });
4680
4920
  break;
4681
- case MC.ConnectionState.Failed:
4921
+ case ConnectionState.Failed:
4682
4922
  connectionFailed();
4683
4923
  break;
4684
4924
  default:
@@ -4686,7 +4926,7 @@ export default class Meeting extends StatelessWebexPlugin {
4686
4926
  }
4687
4927
  });
4688
4928
 
4689
- this.mediaProperties.webrtcMediaConnection.on(MC.Event.ACTIVE_SPEAKERS_CHANGED, (msg) => {
4929
+ this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (msg) => {
4690
4930
  Trigger.trigger(
4691
4931
  this,
4692
4932
  {
@@ -4697,6 +4937,7 @@ export default class Meeting extends StatelessWebexPlugin {
4697
4937
  {
4698
4938
  seqNum: msg.seqNum,
4699
4939
  memberIds: msg.csis
4940
+ // @ts-ignore
4700
4941
  .map((csi) => this.members.findMemberByCsi(csi)?.id)
4701
4942
  .filter((item) => item !== undefined),
4702
4943
  }
@@ -4704,7 +4945,7 @@ export default class Meeting extends StatelessWebexPlugin {
4704
4945
  });
4705
4946
 
4706
4947
  this.mediaProperties.webrtcMediaConnection.on(
4707
- MC.Event.VIDEO_SOURCES_COUNT_CHANGED,
4948
+ Event.VIDEO_SOURCES_COUNT_CHANGED,
4708
4949
  (numTotalSources, numLiveSources) => {
4709
4950
  Trigger.trigger(
4710
4951
  this,
@@ -4722,7 +4963,7 @@ export default class Meeting extends StatelessWebexPlugin {
4722
4963
  );
4723
4964
 
4724
4965
  this.mediaProperties.webrtcMediaConnection.on(
4725
- MC.Event.AUDIO_SOURCES_COUNT_CHANGED,
4966
+ Event.AUDIO_SOURCES_COUNT_CHANGED,
4726
4967
  (numTotalSources, numLiveSources) => {
4727
4968
  Trigger.trigger(
4728
4969
  this,
@@ -4752,6 +4993,7 @@ export default class Meeting extends StatelessWebexPlugin {
4752
4993
  // Add ip address info if geo hint is present
4753
4994
  // @ts-ignore fix type
4754
4995
  options.data.intervalMetadata.peerReflexiveIP =
4996
+ // @ts-ignore
4755
4997
  this.webex.meetings.geoHintInfo?.clientAddress ||
4756
4998
  options.data.intervalMetadata.peerReflexiveIP ||
4757
4999
  MQA_STATS.DEFAULT_IP;
@@ -5082,7 +5324,7 @@ export default class Meeting extends StatelessWebexPlugin {
5082
5324
  this
5083
5325
  );
5084
5326
 
5085
- if (error instanceof MC.Errors.SdpError) {
5327
+ if (error instanceof Errors.SdpError) {
5086
5328
  this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
5087
5329
  }
5088
5330
 
@@ -5110,7 +5352,14 @@ export default class Meeting extends StatelessWebexPlugin {
5110
5352
  * @private
5111
5353
  * @memberof Meeting
5112
5354
  */
5113
- private enqueueMediaUpdate(mediaUpdateType: string, options: object) {
5355
+ private enqueueMediaUpdate(mediaUpdateType: string, options: any) {
5356
+ if (mediaUpdateType === MEDIA_UPDATE_TYPE.LAMBDA && typeof options?.lambda !== 'function') {
5357
+ return Promise.reject(
5358
+ new Error('lambda must be specified when enqueuing MEDIA_UPDATE_TYPE.LAMBDA')
5359
+ );
5360
+ }
5361
+ const canUpdateMediaNow = this.canUpdateMedia();
5362
+
5114
5363
  return new Promise((resolve, reject) => {
5115
5364
  const queueItem = {
5116
5365
  pendingPromiseResolve: resolve,
@@ -5123,6 +5372,10 @@ export default class Meeting extends StatelessWebexPlugin {
5123
5372
  `Meeting:index#enqueueMediaUpdate --> enqueuing media update type=${mediaUpdateType}`
5124
5373
  );
5125
5374
  this.queuedMediaUpdates.push(queueItem);
5375
+
5376
+ if (canUpdateMediaNow) {
5377
+ this.processNextQueuedMediaUpdate();
5378
+ }
5126
5379
  });
5127
5380
  }
5128
5381
 
@@ -5175,6 +5428,9 @@ export default class Meeting extends StatelessWebexPlugin {
5175
5428
  case MEDIA_UPDATE_TYPE.SHARE:
5176
5429
  this.updateShare(options).then(pendingPromiseResolve, pendingPromiseReject);
5177
5430
  break;
5431
+ case MEDIA_UPDATE_TYPE.LAMBDA:
5432
+ options.lambda().then(pendingPromiseResolve, pendingPromiseReject);
5433
+ break;
5178
5434
  default:
5179
5435
  LoggerProxy.logger.error(
5180
5436
  `Peer-connection-manager:index#processNextQueuedMediaUpdate --> unsupported media update type ${mediaUpdateType} found in the queue`
@@ -5259,17 +5515,26 @@ export default class Meeting extends StatelessWebexPlugin {
5259
5515
  // now it's called independently from the roap message (so might be before it), check if that's OK
5260
5516
  // if not, ensure it's called after (now it's called after roap message is sent out, but we're not
5261
5517
  // waiting for sendRoapMediaRequest() to be resolved)
5262
- .then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
5263
- .then((startShare) => {
5264
- // This is a special case if we do an /floor grant followed by /media
5265
- // we actually get a OFFER from the server and a GLAR condition happens
5266
- if (startShare) {
5267
- // We are assuming that the clients are connected when doing an update
5268
- return this.requestScreenShareFloor();
5269
- }
5270
-
5271
- return Promise.resolve();
5272
- })
5518
+ .then(() =>
5519
+ this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
5520
+ lambda: () => {
5521
+ return Promise.resolve()
5522
+ .then(() =>
5523
+ this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus)
5524
+ )
5525
+ .then((startShare) => {
5526
+ // This is a special case if we do an /floor grant followed by /media
5527
+ // we actually get a OFFER from the server and a GLAR condition happens
5528
+ if (startShare) {
5529
+ // We are assuming that the clients are connected when doing an update
5530
+ return this.requestScreenShareFloor();
5531
+ }
5532
+
5533
+ return Promise.resolve();
5534
+ });
5535
+ },
5536
+ })
5537
+ )
5273
5538
  );
5274
5539
  }
5275
5540
 
@@ -5465,13 +5730,17 @@ export default class Meeting extends StatelessWebexPlugin {
5465
5730
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
5466
5731
  },
5467
5732
  })
5468
- .then(() => {
5469
- if (startShare) {
5470
- return this.requestScreenShareFloor();
5471
- }
5733
+ .then(() =>
5734
+ this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
5735
+ lambda: async () => {
5736
+ if (startShare) {
5737
+ return this.requestScreenShareFloor();
5738
+ }
5472
5739
 
5473
- return Promise.resolve();
5474
- })
5740
+ return undefined;
5741
+ },
5742
+ })
5743
+ )
5475
5744
  )
5476
5745
  .then(() => {
5477
5746
  this.mediaProperties.mediaDirection.sendShare = sendShare;
@@ -5852,7 +6121,7 @@ export default class Meeting extends StatelessWebexPlugin {
5852
6121
  * @memberof Meeting
5853
6122
  */
5854
6123
  public startRecording() {
5855
- return MeetingUtil.startRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
6124
+ return this.recordingController.startRecording();
5856
6125
  }
5857
6126
 
5858
6127
  /**
@@ -5862,7 +6131,7 @@ export default class Meeting extends StatelessWebexPlugin {
5862
6131
  * @memberof Meeting
5863
6132
  */
5864
6133
  public stopRecording() {
5865
- return MeetingUtil.stopRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
6134
+ return this.recordingController.stopRecording();
5866
6135
  }
5867
6136
 
5868
6137
  /**
@@ -5872,7 +6141,7 @@ export default class Meeting extends StatelessWebexPlugin {
5872
6141
  * @memberof Meeting
5873
6142
  */
5874
6143
  public pauseRecording() {
5875
- return MeetingUtil.pauseRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
6144
+ return this.recordingController.pauseRecording();
5876
6145
  }
5877
6146
 
5878
6147
  /**
@@ -5882,7 +6151,7 @@ export default class Meeting extends StatelessWebexPlugin {
5882
6151
  * @memberof Meeting
5883
6152
  */
5884
6153
  public resumeRecording() {
5885
- return MeetingUtil.resumeRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
6154
+ return this.recordingController.resumeRecording();
5886
6155
  }
5887
6156
 
5888
6157
  /**