@webex/plugin-meetings 3.7.0-next.3 → 3.7.0-next.30

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 (114) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  4. package/dist/common/errors/join-webinar-error.js.map +1 -0
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +31 -6
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.js +8 -15
  10. package/dist/index.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/index.js +13 -2
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/locus-info/selfUtils.js +30 -17
  16. package/dist/locus-info/selfUtils.js.map +1 -1
  17. package/dist/meeting/in-meeting-actions.js +11 -1
  18. package/dist/meeting/in-meeting-actions.js.map +1 -1
  19. package/dist/meeting/index.js +810 -779
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/request.js +30 -0
  22. package/dist/meeting/request.js.map +1 -1
  23. package/dist/meeting/request.type.js.map +1 -1
  24. package/dist/meeting/util.js +3 -8
  25. package/dist/meeting/util.js.map +1 -1
  26. package/dist/meeting-info/meeting-info-v2.js +29 -17
  27. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  28. package/dist/meetings/index.js +6 -3
  29. package/dist/meetings/index.js.map +1 -1
  30. package/dist/member/index.js +9 -0
  31. package/dist/member/index.js.map +1 -1
  32. package/dist/member/types.js.map +1 -1
  33. package/dist/member/util.js +39 -28
  34. package/dist/member/util.js.map +1 -1
  35. package/dist/members/util.js +4 -2
  36. package/dist/members/util.js.map +1 -1
  37. package/dist/metrics/constants.js +1 -1
  38. package/dist/metrics/constants.js.map +1 -1
  39. package/dist/multistream/remoteMedia.js +30 -15
  40. package/dist/multistream/remoteMedia.js.map +1 -1
  41. package/dist/multistream/sendSlotManager.js +24 -0
  42. package/dist/multistream/sendSlotManager.js.map +1 -1
  43. package/dist/reachability/clusterReachability.js +12 -11
  44. package/dist/reachability/clusterReachability.js.map +1 -1
  45. package/dist/recording-controller/enums.js +8 -4
  46. package/dist/recording-controller/enums.js.map +1 -1
  47. package/dist/recording-controller/index.js +18 -9
  48. package/dist/recording-controller/index.js.map +1 -1
  49. package/dist/recording-controller/util.js +13 -9
  50. package/dist/recording-controller/util.js.map +1 -1
  51. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  52. package/dist/types/constants.d.ts +23 -1
  53. package/dist/types/index.d.ts +3 -3
  54. package/dist/types/locus-info/index.d.ts +2 -1
  55. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  56. package/dist/types/meeting/index.d.ts +9 -10
  57. package/dist/types/meeting/request.d.ts +12 -1
  58. package/dist/types/meeting/request.type.d.ts +6 -0
  59. package/dist/types/meeting/util.d.ts +1 -1
  60. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  61. package/dist/types/meetings/index.d.ts +3 -0
  62. package/dist/types/member/index.d.ts +1 -0
  63. package/dist/types/member/types.d.ts +7 -0
  64. package/dist/types/members/util.d.ts +2 -0
  65. package/dist/types/metrics/constants.d.ts +1 -1
  66. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  67. package/dist/types/recording-controller/enums.d.ts +5 -2
  68. package/dist/types/recording-controller/index.d.ts +1 -0
  69. package/dist/types/recording-controller/util.d.ts +2 -1
  70. package/dist/webinar/index.js +390 -7
  71. package/dist/webinar/index.js.map +1 -1
  72. package/package.json +23 -22
  73. package/src/common/errors/join-webinar-error.ts +24 -0
  74. package/src/config.ts +1 -1
  75. package/src/constants.ts +28 -3
  76. package/src/index.ts +2 -3
  77. package/src/locus-info/index.ts +17 -2
  78. package/src/locus-info/selfUtils.ts +19 -6
  79. package/src/meeting/in-meeting-actions.ts +21 -0
  80. package/src/meeting/index.ts +147 -54
  81. package/src/meeting/request.ts +26 -1
  82. package/src/meeting/request.type.ts +7 -0
  83. package/src/meeting/util.ts +3 -9
  84. package/src/meeting-info/meeting-info-v2.ts +23 -11
  85. package/src/meetings/index.ts +8 -2
  86. package/src/member/index.ts +9 -0
  87. package/src/member/types.ts +8 -0
  88. package/src/member/util.ts +34 -24
  89. package/src/members/util.ts +1 -0
  90. package/src/metrics/constants.ts +1 -1
  91. package/src/multistream/remoteMedia.ts +28 -15
  92. package/src/multistream/sendSlotManager.ts +31 -0
  93. package/src/reachability/clusterReachability.ts +4 -1
  94. package/src/recording-controller/enums.ts +5 -2
  95. package/src/recording-controller/index.ts +17 -4
  96. package/src/recording-controller/util.ts +20 -5
  97. package/src/webinar/index.ts +235 -9
  98. package/test/unit/spec/locus-info/index.js +222 -0
  99. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  100. package/test/unit/spec/locus-info/selfUtils.js +91 -1
  101. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
  102. package/test/unit/spec/meeting/index.js +318 -81
  103. package/test/unit/spec/meeting/utils.js +11 -19
  104. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  105. package/test/unit/spec/meetings/index.js +9 -5
  106. package/test/unit/spec/member/util.js +52 -11
  107. package/test/unit/spec/members/utils.js +95 -0
  108. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  109. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  110. package/test/unit/spec/recording-controller/index.js +61 -5
  111. package/test/unit/spec/recording-controller/util.js +39 -3
  112. package/test/unit/spec/webinar/index.ts +504 -0
  113. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  114. package/src/common/errors/webinar-registration-error.ts +0 -27
package/src/constants.ts CHANGED
@@ -198,6 +198,8 @@ export const RETRY_TIMEOUT = 3000;
198
198
 
199
199
  export const ICE_AND_DTLS_CONNECTION_TIMEOUT = 20000;
200
200
  export const ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT = 35000;
201
+ export const WEBINAR_ERROR_WEBCAST = [403026];
202
+ export const WEBINAR_ERROR_REGISTRATIONID = [403037, 403137];
201
203
 
202
204
  // ******************** REGEX **********************
203
205
  // Please alphabetize
@@ -303,6 +305,7 @@ export const EVENT_TRIGGERS = {
303
305
  MEETING_SELF_CANNOT_VIEW_PARTICIPANT_LIST: 'meeting:self:cannotViewParticipantList',
304
306
  MEETING_SELF_IS_SHARING_BLOCKED: 'meeting:self:isSharingBlocked',
305
307
  MEETING_SELF_ROLES_CHANGED: 'meeting:self:rolesChanged',
308
+ MEETING_SELF_BRB_UPDATE: 'meeting:self:brbUpdate',
306
309
  MEETING_CONTROLS_LAYOUT_UPDATE: 'meeting:layout:update',
307
310
  MEETING_ENTRY_EXIT_TONE_UPDATE: 'meeting:entryExitTone:update',
308
311
  MEETING_BREAKOUTS_UPDATE: 'meeting:breakouts:update',
@@ -325,6 +328,7 @@ export const EVENT_TRIGGERS = {
325
328
  MEETING_RECONNECTION_FAILURE: 'meeting:reconnectionFailure',
326
329
  MEETING_UNLOCKED: 'meeting:unlocked',
327
330
  MEETING_LOCKED: 'meeting:locked',
331
+ MEETING_RESOURCE_LINKS_UPDATE: 'meeting:resourceLinks:update',
328
332
  MEETING_INFO_AVAILABLE: 'meeting:meetingInfoAvailable',
329
333
  MEETING_INFO_UPDATED: 'meeting:meetingInfoUpdated',
330
334
  MEETING_LOG_UPLOAD_SUCCESS: 'meeting:logUpload:success',
@@ -383,6 +387,13 @@ export const EVENT_TYPES = {
383
387
  ERROR: 'error',
384
388
  };
385
389
 
390
+ export const HEADERS = {
391
+ CONTENT_TYPE: 'Content-Type',
392
+ CONTENT_TYPE_VALUE: {
393
+ APPLICATION_JSON: 'application/json',
394
+ },
395
+ };
396
+
386
397
  // Handles the reason when meeting gets destroyed
387
398
  // host removed you from the meeting
388
399
  // You are the host and you left the meeting
@@ -529,9 +540,9 @@ export const ERROR_DICTIONARY = {
529
540
  'Reconnection was not started, because there is one already in progress or reconnections are disabled in config.',
530
541
  CODE: 15,
531
542
  },
532
- WebinarRegistrationError: {
533
- NAME: 'WebinarRegistrationError',
534
- MESSAGE: 'An error occurred while the webinar required registration.',
543
+ JoinWebinarError: {
544
+ NAME: 'JoinWebinarError',
545
+ MESSAGE: 'An error occurred while the join webinar.',
535
546
  CODE: 16,
536
547
  },
537
548
  };
@@ -709,6 +720,7 @@ export const LOCUSINFO = {
709
720
  SELF_IS_SHARING_BLOCKED_CHANGE: 'SELF_IS_SHARING_BLOCKED_CHANGE',
710
721
  SELF_MEETING_BREAKOUTS_CHANGED: 'SELF_MEETING_BREAKOUTS_CHANGED',
711
722
  SELF_MEETING_INTERPRETATION_CHANGED: 'SELF_MEETING_INTERPRETATION_CHANGED',
723
+ SELF_MEETING_BRB_CHANGED: 'SELF_MEETING_BRB_CHANGED',
712
724
  MEDIA_INACTIVITY: 'MEDIA_INACTIVITY',
713
725
  LINKS_SERVICES: 'LINKS_SERVICES',
714
726
  LINKS_RESOURCES: 'LINKS_RESOURCES',
@@ -904,6 +916,10 @@ export const DISPLAY_HINTS = {
904
916
  RECORDING_CONTROL_PAUSE: 'RECORDING_CONTROL_PAUSE',
905
917
  RECORDING_CONTROL_STOP: 'RECORDING_CONTROL_STOP',
906
918
  RECORDING_CONTROL_RESUME: 'RECORDING_CONTROL_RESUME',
919
+ PREMISE_RECORDING_CONTROL_START: 'PREMISE_RECORDING_CONTROL_START',
920
+ PREMISE_RECORDING_CONTROL_PAUSE: 'PREMISE_RECORDING_CONTROL_PAUSE',
921
+ PREMISE_RECORDING_CONTROL_STOP: 'PREMISE_RECORDING_CONTROL_STOP',
922
+ PREMISE_RECORDING_CONTROL_RESUME: 'PREMISE_RECORDING_CONTROL_RESUME',
907
923
  LOCK_CONTROL_UNLOCK: 'LOCK_CONTROL_UNLOCK',
908
924
  LOCK_CONTROL_LOCK: 'LOCK_CONTROL_LOCK',
909
925
  LOCK_STATUS_LOCKED: 'LOCK_STATUS_LOCKED',
@@ -950,6 +966,7 @@ export const DISPLAY_HINTS = {
950
966
  DISABLE_ASK_FOR_HELP: 'DISABLE_ASK_FOR_HELP',
951
967
  DISABLE_BREAKOUT_PREASSIGNMENTS: 'DISABLE_BREAKOUT_PREASSIGNMENTS',
952
968
  DISABLE_LOBBY_TO_BREAKOUT: 'DISABLE_LOBBY_TO_BREAKOUT',
969
+ DISABLE_BREAKOUT_START: 'DISABLE_BREAKOUT_START',
953
970
 
954
971
  // participants list
955
972
  DISABLE_VIEW_THE_PARTICIPANT_LIST: 'DISABLE_VIEW_THE_PARTICIPANT_LIST',
@@ -988,6 +1005,12 @@ export const DISPLAY_HINTS = {
988
1005
  STAGE_VIEW_INACTIVE: 'STAGE_VIEW_INACTIVE',
989
1006
  ENABLE_STAGE_VIEW: 'ENABLE_STAGE_VIEW',
990
1007
  DISABLE_STAGE_VIEW: 'DISABLE_STAGE_VIEW',
1008
+
1009
+ // Practice Session
1010
+ PRACTICE_SESSION_ON: 'PRACTICE_SESSION_ON',
1011
+ PRACTICE_SESSION_OFF: 'PRACTICE_SESSION_OFF',
1012
+ SHOW_PRACTICE_SESSION_START: 'SHOW_PRACTICE_SESSION_START',
1013
+ SHOW_PRACTICE_SESSION_STOP: 'SHOW_PRACTICE_SESSION_STOP',
991
1014
  };
992
1015
 
993
1016
  export const INTERSTITIAL_DISPLAY_HINTS = [DISPLAY_HINTS.VOIP_IS_ENABLED];
@@ -1300,6 +1323,8 @@ export const MEETING_INFO_FAILURE_REASON = {
1300
1323
  WRONG_CAPTCHA: 'WRONG_CAPTCHA', // wbxappapi requires a captcha code or a wrong captcha code was provided
1301
1324
  POLICY: 'POLICY', // meeting info request violates some meeting policy
1302
1325
  WEBINAR_REGISTRATION: 'WEBINAR_REGISTRATION', // webinar need registration
1326
+ NEED_JOIN_WITH_WEBCAST: 'NEED_JOIN_WITH_WEBCAST', // webinar need using webcast join
1327
+ WEBINAR_NEED_REGISTRATIONID: 'WEBINAR_NEED_REGISTRATIONID', // webinar need registrationID
1303
1328
  OTHER: 'OTHER', // any other error (network, etc)
1304
1329
  };
1305
1330
 
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ import CaptchaError from './common/errors/captcha-error';
8
8
  import IntentToJoinError from './common/errors/intent-to-join';
9
9
  import PasswordError from './common/errors/password-error';
10
10
  import PermissionError from './common/errors/permission';
11
- import WebinarRegistrationError from './common/errors/webinar-registration-error';
11
+ import JoinWebinarError from './common/errors/join-webinar-error';
12
12
  import {
13
13
  ReclaimHostEmptyWrongKeyError,
14
14
  ReclaimHostIsHostAlreadyError,
@@ -27,7 +27,6 @@ registerPlugin('meetings', Meetings, {
27
27
  });
28
28
 
29
29
  export {
30
- getDevices,
31
30
  LocalStream,
32
31
  LocalDisplayStream,
33
32
  LocalSystemAudioStream,
@@ -69,7 +68,7 @@ export {
69
68
  ReclaimHostEmptyWrongKeyError,
70
69
  Meeting,
71
70
  MeetingInfoUtil,
72
- WebinarRegistrationError,
71
+ JoinWebinarError,
73
72
  };
74
73
 
75
74
  export {RemoteMedia} from './multistream/remoteMedia';
@@ -1283,12 +1283,13 @@ export default class LocusInfo extends EventsScope {
1283
1283
  /**
1284
1284
  * handles when the locus.mediaShares is updated
1285
1285
  * @param {Object} mediaShares the locus.mediaShares property
1286
+ * @param {boolean} forceUpdate force to update the mediaShares
1286
1287
  * @returns {undefined}
1287
1288
  * @memberof LocusInfo
1288
1289
  * emits internal event locus_info_update_media_shares
1289
1290
  */
1290
- updateMediaShares(mediaShares: object) {
1291
- if (mediaShares && !isEqual(this.mediaShares, mediaShares)) {
1291
+ updateMediaShares(mediaShares: object, forceUpdate = false) {
1292
+ if (mediaShares && (!isEqual(this.mediaShares, mediaShares) || forceUpdate)) {
1292
1293
  const parsedMediaShares = MediaSharesUtils.getMediaShares(this.mediaShares, mediaShares);
1293
1294
 
1294
1295
  this.updateMeeting(parsedMediaShares.current);
@@ -1303,6 +1304,7 @@ export default class LocusInfo extends EventsScope {
1303
1304
  {
1304
1305
  current: parsedMediaShares.current,
1305
1306
  previous: parsedMediaShares.previous,
1307
+ forceUpdate,
1306
1308
  }
1307
1309
  );
1308
1310
  }
@@ -1393,6 +1395,19 @@ export default class LocusInfo extends EventsScope {
1393
1395
  );
1394
1396
  }
1395
1397
 
1398
+ if (parsedSelves.updates.brbChanged) {
1399
+ this.emitScoped(
1400
+ {
1401
+ file: 'locus-info',
1402
+ function: 'updateSelf',
1403
+ },
1404
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
1405
+ {
1406
+ brb: parsedSelves.current.brb,
1407
+ }
1408
+ );
1409
+ }
1410
+
1396
1411
  if (parsedSelves.updates.interpretationChanged) {
1397
1412
  this.emitScoped(
1398
1413
  {
@@ -66,6 +66,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
66
66
  breakoutSessions: SelfUtils.getBreakoutSessions(self),
67
67
  breakout: SelfUtils.getBreakout(self),
68
68
  interpretation: SelfUtils.getInterpretation(self),
69
+ brb: SelfUtils.getBrb(self),
69
70
  };
70
71
  }
71
72
 
@@ -75,6 +76,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
75
76
  SelfUtils.getBreakoutSessions = (self) => self?.controls?.breakout?.sessions;
76
77
  SelfUtils.getBreakout = (self) => self?.controls?.breakout;
77
78
  SelfUtils.getInterpretation = (self) => self?.controls?.interpretation;
79
+ SelfUtils.getBrb = (self) => self?.controls?.brb;
78
80
 
79
81
  SelfUtils.getLayout = (self) =>
80
82
  Array.isArray(self?.controls?.layouts) ? self.controls.layouts[0].type : undefined;
@@ -97,7 +99,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
97
99
  const current = newSelf && SelfUtils.parse(newSelf, deviceId);
98
100
  const updates: any = {};
99
101
 
100
- updates.isUserUnadmitted = SelfUtils.isUserUnadmitted(current);
102
+ updates.isUserUnadmitted = SelfUtils.isUserUnadmitted(previous, current);
101
103
  updates.isUserAdmitted = SelfUtils.isUserAdmitted(previous, current);
102
104
  updates.isVideoMutedByOthersChanged = SelfUtils.videoMutedByOthersChanged(previous, current);
103
105
  updates.isMutedByOthersChanged = SelfUtils.mutedByOthersChanged(previous, current);
@@ -128,6 +130,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
128
130
  updates.isSharingBlockedChanged = previous?.isSharingBlocked !== current.isSharingBlocked;
129
131
  updates.breakoutsChanged = SelfUtils.breakoutsChanged(previous, current);
130
132
  updates.interpretationChanged = SelfUtils.interpretationChanged(previous, current);
133
+ updates.brbChanged = SelfUtils.brbChanged(previous, current);
131
134
 
132
135
  return {
133
136
  previous,
@@ -159,6 +162,9 @@ SelfUtils.breakoutsChanged = (previous, current) =>
159
162
  SelfUtils.interpretationChanged = (previous, current) =>
160
163
  !isEqual(previous?.interpretation, current?.interpretation) && !!current?.interpretation;
161
164
 
165
+ SelfUtils.brbChanged = (previous, current) =>
166
+ !isEqual(previous?.brb, current?.brb) && current?.brb !== undefined;
167
+
162
168
  SelfUtils.isMediaInactive = (previous, current) => {
163
169
  if (
164
170
  previous &&
@@ -324,16 +330,23 @@ SelfUtils.isLocusUserAdmitted = (check: any) =>
324
330
  check && check.joinedWith?.intent?.type !== _WAIT_ && check.state === _JOINED_;
325
331
 
326
332
  /**
327
- * @param {Object} self
333
+ * @param {Object} oldSelf
334
+ * @param {Object} changedSelf
328
335
  * @returns {Boolean}
329
336
  * @throws {Error} when self is undefined
330
337
  */
331
- SelfUtils.isUserUnadmitted = (self: object) => {
332
- if (!self) {
333
- throw new ParameterError('self must be defined to determine if self is unadmitted as guest.');
338
+ SelfUtils.isUserUnadmitted = (oldSelf: object, changedSelf: object) => {
339
+ if (!changedSelf) {
340
+ throw new ParameterError(
341
+ 'changedSelf must be defined to determine if self is unadmitted as guest.'
342
+ );
343
+ }
344
+
345
+ if (SelfUtils.isLocusUserUnadmitted(oldSelf)) {
346
+ return false;
334
347
  }
335
348
 
336
- return SelfUtils.isLocusUserUnadmitted(self);
349
+ return SelfUtils.isLocusUserUnadmitted(changedSelf);
337
350
  };
338
351
 
339
352
  SelfUtils.moderatorChanged = (oldSelf, changedSelf) => {
@@ -26,6 +26,7 @@ interface IInMeetingActions {
26
26
  canStartRecording?: boolean;
27
27
  canPauseRecording?: boolean;
28
28
  canResumeRecording?: boolean;
29
+ isPremiseRecordingEnabled?: boolean;
29
30
  canStopRecording?: boolean;
30
31
  canRaiseHand?: boolean;
31
32
  canLowerAllHands?: boolean;
@@ -93,6 +94,10 @@ interface IInMeetingActions {
93
94
  canShowStageView?: boolean;
94
95
  canEnableStageView?: boolean;
95
96
  canDisableStageView?: boolean;
97
+ isPracticeSessionOn?: boolean;
98
+ isPracticeSessionOff?: boolean;
99
+ canStartPracticeSession?: boolean;
100
+ canStopPracticeSession?: boolean;
96
101
  }
97
102
 
98
103
  /**
@@ -117,6 +122,8 @@ export default class InMeetingActions implements IInMeetingActions {
117
122
 
118
123
  canResumeRecording = null;
119
124
 
125
+ isPremiseRecordingEnabled = null;
126
+
120
127
  canStopRecording = null;
121
128
 
122
129
  canSetMuteOnEntry = null;
@@ -266,6 +273,15 @@ export default class InMeetingActions implements IInMeetingActions {
266
273
  canEnableStageView = null;
267
274
 
268
275
  canDisableStageView = null;
276
+
277
+ isPracticeSessionOn = null;
278
+
279
+ isPracticeSessionOff = null;
280
+
281
+ canStartPracticeSession = null;
282
+
283
+ canStopPracticeSession = null;
284
+
269
285
  /**
270
286
  * Returns all meeting action options
271
287
  * @returns {Object}
@@ -288,6 +304,7 @@ export default class InMeetingActions implements IInMeetingActions {
288
304
  canPauseRecording: this.canPauseRecording,
289
305
  canResumeRecording: this.canResumeRecording,
290
306
  canStopRecording: this.canStopRecording,
307
+ isPremiseRecordingEnabled: this.isPremiseRecordingEnabled,
291
308
  canRaiseHand: this.canRaiseHand,
292
309
  canLowerAllHands: this.canLowerAllHands,
293
310
  canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand,
@@ -354,6 +371,10 @@ export default class InMeetingActions implements IInMeetingActions {
354
371
  canShowStageView: this.canShowStageView,
355
372
  canEnableStageView: this.canEnableStageView,
356
373
  canDisableStageView: this.canDisableStageView,
374
+ isPracticeSessionOn: this.isPracticeSessionOn,
375
+ isPracticeSessionOff: this.isPracticeSessionOff,
376
+ canStartPracticeSession: this.canStartPracticeSession,
377
+ canStopPracticeSession: this.canStopPracticeSession,
357
378
  });
358
379
 
359
380
  /**
@@ -31,7 +31,6 @@ import {
31
31
  } from '@webex/internal-media-core';
32
32
 
33
33
  import {
34
- getDevices,
35
34
  LocalStream,
36
35
  LocalCameraStream,
37
36
  LocalDisplayStream,
@@ -122,6 +121,8 @@ import {
122
121
  MEETING_PERMISSION_TOKEN_REFRESH_REASON,
123
122
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
124
123
  NAMED_MEDIA_GROUP_TYPE_AUDIO,
124
+ WEBINAR_ERROR_WEBCAST,
125
+ WEBINAR_ERROR_REGISTRATIONID,
125
126
  } from '../constants';
126
127
  import BEHAVIORAL_METRICS from '../metrics/constants';
127
128
  import ParameterError from '../common/errors/parameter';
@@ -129,7 +130,7 @@ import {
129
130
  MeetingInfoV2PasswordError,
130
131
  MeetingInfoV2CaptchaError,
131
132
  MeetingInfoV2PolicyError,
132
- MeetingInfoV2WebinarRegistrationError,
133
+ MeetingInfoV2JoinWebinarError,
133
134
  } from '../meeting-info/meeting-info-v2';
134
135
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
135
136
  import SendSlotManager from '../multistream/sendSlotManager';
@@ -158,7 +159,8 @@ import ControlsOptionsManager from '../controls-options-manager';
158
159
  import PermissionError from '../common/errors/permission';
159
160
  import {LocusMediaRequest} from './locusMediaRequest';
160
161
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
161
- import WebinarRegistrationError from '../common/errors/webinar-registration-error';
162
+ import JoinWebinarError from '../common/errors/join-webinar-error';
163
+ import Member from '../member';
162
164
 
163
165
  // default callback so we don't call an undefined function, but in practice it should never be used
164
166
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -848,7 +850,7 @@ export default class Meeting extends StatelessWebexPlugin {
848
850
  * @memberof Meeting
849
851
  */
850
852
  // @ts-ignore
851
- this.webinar = new Webinar({}, {parent: this.webex});
853
+ this.webinar = new Webinar({meetingId: this.id}, {parent: this.webex});
852
854
  /**
853
855
  * helper class for managing receive slots (for multistream media connections)
854
856
  */
@@ -1767,15 +1769,20 @@ export default class Meeting extends StatelessWebexPlugin {
1767
1769
  this.meetingInfo = err.meetingInfo;
1768
1770
  }
1769
1771
  throw new PermissionError();
1770
- } else if (err instanceof MeetingInfoV2WebinarRegistrationError) {
1772
+ } else if (err instanceof MeetingInfoV2JoinWebinarError) {
1771
1773
  this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
1774
+ if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
1775
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
1776
+ } else if (WEBINAR_ERROR_REGISTRATIONID.includes(err.wbxAppApiCode)) {
1777
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID;
1778
+ }
1772
1779
  this.meetingInfoFailureCode = err.wbxAppApiCode;
1773
1780
 
1774
1781
  if (err.meetingInfo) {
1775
1782
  this.meetingInfo = err.meetingInfo;
1776
1783
  }
1777
1784
 
1778
- throw new WebinarRegistrationError();
1785
+ throw new JoinWebinarError();
1779
1786
  } else if (err instanceof MeetingInfoV2PasswordError) {
1780
1787
  LoggerProxy.logger.info(
1781
1788
  // @ts-ignore
@@ -2660,6 +2667,7 @@ export default class Meeting extends StatelessWebexPlugin {
2660
2667
  });
2661
2668
 
2662
2669
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_PRACTICE_SESSION_STATUS_UPDATED, ({state}) => {
2670
+ this.webinar.updatePracticeSessionStatus(state);
2663
2671
  Trigger.trigger(
2664
2672
  this,
2665
2673
  {file: 'meeting/index', function: 'setupLocusControlsListener'},
@@ -2733,6 +2741,7 @@ export default class Meeting extends StatelessWebexPlugin {
2733
2741
  this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
2734
2742
 
2735
2743
  if (
2744
+ !payload.forceUpdate &&
2736
2745
  contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
2737
2746
  contentShare.disposition === previousContentShare?.disposition &&
2738
2747
  contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
@@ -2779,7 +2788,11 @@ export default class Meeting extends StatelessWebexPlugin {
2779
2788
  // It does not matter who requested to share the whiteboard, everyone gets the same view
2780
2789
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
2781
2790
  // WHITEBOARD - sharing whiteboard
2782
- newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
2791
+ // Webinar attendee should receive whiteboard as remote share
2792
+ newShareStatus =
2793
+ this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
2794
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
2795
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
2783
2796
  }
2784
2797
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
2785
2798
  else if (
@@ -2794,6 +2807,7 @@ export default class Meeting extends StatelessWebexPlugin {
2794
2807
  LoggerProxy.logger.info(
2795
2808
  `Meeting:index#setUpLocusInfoMediaInactiveListener --> this.shareStatus=${this.shareStatus} newShareStatus=${newShareStatus}`
2796
2809
  );
2810
+
2797
2811
  if (newShareStatus !== this.shareStatus) {
2798
2812
  const oldShareStatus = this.shareStatus;
2799
2813
 
@@ -3051,7 +3065,20 @@ export default class Meeting extends StatelessWebexPlugin {
3051
3065
  */
3052
3066
  private setUpLocusResourcesListener() {
3053
3067
  this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_RESOURCES, (payload) => {
3054
- this.webinar.updateWebcastUrl(payload);
3068
+ if (payload) {
3069
+ this.webinar.updateWebcastUrl(payload);
3070
+ Trigger.trigger(
3071
+ this,
3072
+ {
3073
+ file: 'meeting/index',
3074
+ function: 'setUpLocusInfoMeetingInfoListener',
3075
+ },
3076
+ EVENT_TRIGGERS.MEETING_RESOURCE_LINKS_UPDATE,
3077
+ {
3078
+ payload,
3079
+ }
3080
+ );
3081
+ }
3055
3082
  });
3056
3083
  }
3057
3084
 
@@ -3361,6 +3388,20 @@ export default class Meeting extends StatelessWebexPlugin {
3361
3388
  }
3362
3389
  });
3363
3390
 
3391
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
3392
+ Trigger.trigger(
3393
+ this,
3394
+ {
3395
+ file: 'meeting/index',
3396
+ function: 'setUpLocusInfoSelfListener',
3397
+ },
3398
+ EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
3399
+ {
3400
+ payload,
3401
+ }
3402
+ );
3403
+ });
3404
+
3364
3405
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
3365
3406
  const isModeratorOrCohost =
3366
3407
  payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
@@ -3370,6 +3411,7 @@ export default class Meeting extends StatelessWebexPlugin {
3370
3411
  payload.newRoles?.includes(SELF_ROLES.MODERATOR)
3371
3412
  );
3372
3413
  this.webinar.updateRoleChanged(payload);
3414
+
3373
3415
  Trigger.trigger(
3374
3416
  this,
3375
3417
  {
@@ -3516,6 +3558,7 @@ export default class Meeting extends StatelessWebexPlugin {
3516
3558
  emailAddress: string;
3517
3559
  email: string;
3518
3560
  phoneNumber: string;
3561
+ roles: Array<string>;
3519
3562
  },
3520
3563
  alertIfActive = true
3521
3564
  ) {
@@ -3563,6 +3606,50 @@ export default class Meeting extends StatelessWebexPlugin {
3563
3606
  return this.members.admitMembers(memberIds, locusUrls);
3564
3607
  }
3565
3608
 
3609
+ /**
3610
+ * Manages be right back status updates for the current participant.
3611
+ *
3612
+ * @param {boolean} enabled - Indicates whether the user enabled brb or not.
3613
+ * @returns {Promise<void>} resolves when the brb status is updated or does nothing if not in a multistream meeting.
3614
+ * @throws {Error} - Throws an error if the request fails.
3615
+ */
3616
+ public async beRightBack(enabled: boolean): Promise<void> {
3617
+ if (!this.isMultistream) {
3618
+ const errorMessage = 'Meeting:index#beRightBack --> Not a multistream meeting';
3619
+ const error = new Error(errorMessage);
3620
+
3621
+ LoggerProxy.logger.error(error);
3622
+
3623
+ return Promise.reject(error);
3624
+ }
3625
+
3626
+ if (!this.mediaProperties.webrtcMediaConnection) {
3627
+ const errorMessage = 'Meeting:index#beRightBack --> WebRTC media connection is not defined';
3628
+ const error = new Error(errorMessage);
3629
+
3630
+ LoggerProxy.logger.error(error);
3631
+
3632
+ return Promise.reject(error);
3633
+ }
3634
+
3635
+ // this logic should be applied only to multistream meetings
3636
+ return this.meetingRequest
3637
+ .setBrb({
3638
+ enabled,
3639
+ locusUrl: this.locusUrl,
3640
+ deviceUrl: this.deviceUrl,
3641
+ selfId: this.selfId,
3642
+ })
3643
+ .then(() => {
3644
+ this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
3645
+ })
3646
+ .catch((error) => {
3647
+ LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
3648
+
3649
+ return Promise.reject(error);
3650
+ });
3651
+ }
3652
+
3566
3653
  /**
3567
3654
  * Remove the member from the meeting, boot them
3568
3655
  * @param {String} memberId
@@ -3772,6 +3859,10 @@ export default class Meeting extends StatelessWebexPlugin {
3772
3859
  this.userDisplayHints,
3773
3860
  this.selfUserPolicies
3774
3861
  ),
3862
+ isPremiseRecordingEnabled: RecordingUtil.isPremiseRecordingEnabled(
3863
+ this.userDisplayHints,
3864
+ this.selfUserPolicies
3865
+ ),
3775
3866
  canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints),
3776
3867
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints),
3777
3868
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints),
@@ -3915,6 +4006,22 @@ export default class Meeting extends StatelessWebexPlugin {
3915
4006
  requiredHints: [DISPLAY_HINTS.DISABLE_STAGE_VIEW],
3916
4007
  displayHints: this.userDisplayHints,
3917
4008
  }),
4009
+ isPracticeSessionOn: ControlsOptionsUtil.hasHints({
4010
+ requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_ON],
4011
+ displayHints: this.userDisplayHints,
4012
+ }),
4013
+ isPracticeSessionOff: ControlsOptionsUtil.hasHints({
4014
+ requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_OFF],
4015
+ displayHints: this.userDisplayHints,
4016
+ }),
4017
+ canStartPracticeSession: ControlsOptionsUtil.hasHints({
4018
+ requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_START],
4019
+ displayHints: this.userDisplayHints,
4020
+ }),
4021
+ canStopPracticeSession: ControlsOptionsUtil.hasHints({
4022
+ requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_STOP],
4023
+ displayHints: this.userDisplayHints,
4024
+ }),
3918
4025
  canShareFile:
3919
4026
  (ControlsOptionsUtil.hasHints({
3920
4027
  requiredHints: [DISPLAY_HINTS.SHARE_FILE],
@@ -4077,10 +4184,11 @@ export default class Meeting extends StatelessWebexPlugin {
4077
4184
  */
4078
4185
  private setLogUploadTimer() {
4079
4186
  // start with short timeouts and increase them later on so in case users have very long multi-hour meetings we don't get too fragmented logs
4080
- const LOG_UPLOAD_INTERVALS = [0.1, 1, 15, 15, 30, 30, 30, 60];
4187
+ const LOG_UPLOAD_INTERVALS = [0.1, 15, 30, 60]; // in minutes
4081
4188
 
4082
4189
  const delay =
4083
4190
  1000 *
4191
+ 60 *
4084
4192
  // @ts-ignore - config coming from registerPlugin
4085
4193
  this.config.logUploadIntervalMultiplicationFactor *
4086
4194
  LOG_UPLOAD_INTERVALS[this.logUploadIntervalIndex];
@@ -5351,16 +5459,19 @@ export default class Meeting extends StatelessWebexPlugin {
5351
5459
  this.meetingFiniteStateMachine.reset();
5352
5460
  }
5353
5461
 
5354
- // @ts-ignore
5355
- this.webex.internal.newMetrics.submitClientEvent({
5356
- name: 'client.call.initiated',
5357
- payload: {
5358
- trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5359
- isRoapCallEnabled: true,
5360
- pstnAudioType: options?.pstnAudioType,
5361
- },
5362
- options: {meetingId: this.id},
5363
- });
5462
+ // send client.call.initiated unless told not to
5463
+ if (options.sendCallInitiated !== false) {
5464
+ // @ts-ignore
5465
+ this.webex.internal.newMetrics.submitClientEvent({
5466
+ name: 'client.call.initiated',
5467
+ payload: {
5468
+ trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5469
+ isRoapCallEnabled: true,
5470
+ pstnAudioType: options?.pstnAudioType,
5471
+ },
5472
+ options: {meetingId: this.id},
5473
+ });
5474
+ }
5364
5475
 
5365
5476
  LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
5366
5477
 
@@ -5548,17 +5659,23 @@ export default class Meeting extends StatelessWebexPlugin {
5548
5659
  */
5549
5660
  async updateLLMConnection() {
5550
5661
  // @ts-ignore - Fix type
5551
- const {url, info: {datachannelUrl} = {}} = this.locusInfo;
5662
+ const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
5552
5663
 
5553
5664
  const isJoined = this.isJoined();
5554
5665
 
5666
+ // webinar panelist should use new data channel in practice session
5667
+ const dataChannelUrl =
5668
+ this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
5669
+ ? practiceSessionDatachannelUrl
5670
+ : datachannelUrl;
5671
+
5555
5672
  // @ts-ignore - Fix type
5556
5673
  if (this.webex.internal.llm.isConnected()) {
5557
5674
  if (
5558
5675
  // @ts-ignore - Fix type
5559
5676
  url === this.webex.internal.llm.getLocusUrl() &&
5560
5677
  // @ts-ignore - Fix type
5561
- datachannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
5678
+ dataChannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
5562
5679
  isJoined
5563
5680
  ) {
5564
5681
  return undefined;
@@ -5575,7 +5692,7 @@ export default class Meeting extends StatelessWebexPlugin {
5575
5692
 
5576
5693
  // @ts-ignore - Fix type
5577
5694
  return this.webex.internal.llm
5578
- .registerAndConnect(url, datachannelUrl)
5695
+ .registerAndConnect(url, dataChannelUrl)
5579
5696
  .then((registerAndConnectResult) => {
5580
5697
  // @ts-ignore - Fix type
5581
5698
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -6414,6 +6531,14 @@ export default class Meeting extends StatelessWebexPlugin {
6414
6531
  this.webex.meetings.geoHintInfo?.clientAddress ||
6415
6532
  options.data.intervalMetadata.peerReflexiveIP ||
6416
6533
  MQA_STATS.DEFAULT_IP;
6534
+
6535
+ const {members} = this.getMembers().membersCollection;
6536
+
6537
+ // Count members that are in the meeting
6538
+ options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6539
+ (member: Member) => member.isInMeeting
6540
+ ).length;
6541
+
6417
6542
  // @ts-ignore
6418
6543
  this.webex.internal.newMetrics.submitMQE({
6419
6544
  name: 'client.mediaquality.event',
@@ -6745,32 +6870,6 @@ export default class Meeting extends StatelessWebexPlugin {
6745
6870
  }
6746
6871
  }
6747
6872
 
6748
- /**
6749
- * Handles device logging
6750
- *
6751
- * @private
6752
- * @static
6753
- * @param {boolean} isAudioEnabled
6754
- * @param {boolean} isVideoEnabled
6755
- * @returns {Promise<void>}
6756
- */
6757
-
6758
- private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
6759
- try {
6760
- let devices = [];
6761
- if (isVideoEnabled && isAudioEnabled) {
6762
- devices = await getDevices();
6763
- } else if (isVideoEnabled) {
6764
- devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
6765
- } else if (isAudioEnabled) {
6766
- devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
6767
- }
6768
- MeetingUtil.handleDeviceLogging(devices);
6769
- } catch {
6770
- // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
6771
- }
6772
- }
6773
-
6774
6873
  /**
6775
6874
  * Returns a promise. This promise is created once the local sdp offer has been successfully created and is resolved
6776
6875
  * once the remote sdp answer has been received.
@@ -7273,12 +7372,6 @@ export default class Meeting extends StatelessWebexPlugin {
7273
7372
  turnServerInfo
7274
7373
  );
7275
7374
 
7276
- if (audioEnabled || videoEnabled) {
7277
- await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
7278
- } else {
7279
- LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
7280
- }
7281
-
7282
7375
  if (this.mediaProperties.hasLocalShareStream()) {
7283
7376
  await this.enqueueScreenShareFloorRequest();
7284
7377
  }