@webex/plugin-meetings 3.7.0-next.5 → 3.7.0-next.50

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 (134) hide show
  1. package/dist/annotation/index.js +17 -0
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  6. package/dist/common/errors/join-webinar-error.js.map +1 -0
  7. package/dist/common/errors/multistream-not-supported-error.js +53 -0
  8. package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +40 -5
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.js +16 -11
  14. package/dist/index.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/index.js +14 -3
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/selfUtils.js +30 -17
  20. package/dist/locus-info/selfUtils.js.map +1 -1
  21. package/dist/meeting/in-meeting-actions.js +4 -0
  22. package/dist/meeting/in-meeting-actions.js.map +1 -1
  23. package/dist/meeting/index.js +944 -832
  24. package/dist/meeting/index.js.map +1 -1
  25. package/dist/meeting/locusMediaRequest.js +9 -0
  26. package/dist/meeting/locusMediaRequest.js.map +1 -1
  27. package/dist/meeting/request.js +30 -0
  28. package/dist/meeting/request.js.map +1 -1
  29. package/dist/meeting/request.type.js.map +1 -1
  30. package/dist/meeting/util.js +16 -16
  31. package/dist/meeting/util.js.map +1 -1
  32. package/dist/meeting-info/meeting-info-v2.js +29 -17
  33. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  34. package/dist/meeting-info/utilv2.js +1 -1
  35. package/dist/meeting-info/utilv2.js.map +1 -1
  36. package/dist/meetings/index.js +106 -55
  37. package/dist/meetings/index.js.map +1 -1
  38. package/dist/meetings/meetings.types.js +2 -0
  39. package/dist/meetings/meetings.types.js.map +1 -1
  40. package/dist/meetings/util.js +1 -1
  41. package/dist/meetings/util.js.map +1 -1
  42. package/dist/member/index.js +9 -0
  43. package/dist/member/index.js.map +1 -1
  44. package/dist/member/types.js.map +1 -1
  45. package/dist/member/util.js +39 -28
  46. package/dist/member/util.js.map +1 -1
  47. package/dist/metrics/constants.js +1 -1
  48. package/dist/metrics/constants.js.map +1 -1
  49. package/dist/multistream/remoteMedia.js +30 -15
  50. package/dist/multistream/remoteMedia.js.map +1 -1
  51. package/dist/multistream/sendSlotManager.js +24 -0
  52. package/dist/multistream/sendSlotManager.js.map +1 -1
  53. package/dist/recording-controller/enums.js +8 -4
  54. package/dist/recording-controller/enums.js.map +1 -1
  55. package/dist/recording-controller/index.js +18 -9
  56. package/dist/recording-controller/index.js.map +1 -1
  57. package/dist/recording-controller/util.js +13 -9
  58. package/dist/recording-controller/util.js.map +1 -1
  59. package/dist/roap/index.js +10 -8
  60. package/dist/roap/index.js.map +1 -1
  61. package/dist/types/annotation/index.d.ts +5 -0
  62. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  63. package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
  64. package/dist/types/constants.d.ts +34 -1
  65. package/dist/types/index.d.ts +3 -3
  66. package/dist/types/locus-info/index.d.ts +2 -1
  67. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  68. package/dist/types/meeting/index.d.ts +19 -12
  69. package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
  70. package/dist/types/meeting/request.d.ts +12 -1
  71. package/dist/types/meeting/request.type.d.ts +6 -0
  72. package/dist/types/meeting/util.d.ts +1 -1
  73. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  74. package/dist/types/meetings/index.d.ts +19 -1
  75. package/dist/types/meetings/meetings.types.d.ts +8 -0
  76. package/dist/types/member/index.d.ts +1 -0
  77. package/dist/types/member/types.d.ts +7 -0
  78. package/dist/types/metrics/constants.d.ts +1 -1
  79. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  80. package/dist/types/recording-controller/enums.d.ts +5 -2
  81. package/dist/types/recording-controller/index.d.ts +1 -0
  82. package/dist/types/recording-controller/util.d.ts +2 -1
  83. package/dist/webinar/index.js +354 -3
  84. package/dist/webinar/index.js.map +1 -1
  85. package/package.json +23 -22
  86. package/src/annotation/index.ts +16 -0
  87. package/src/common/errors/join-webinar-error.ts +24 -0
  88. package/src/common/errors/multistream-not-supported-error.ts +30 -0
  89. package/src/config.ts +1 -1
  90. package/src/constants.ts +39 -3
  91. package/src/index.ts +5 -3
  92. package/src/locus-info/index.ts +20 -3
  93. package/src/locus-info/selfUtils.ts +19 -6
  94. package/src/meeting/in-meeting-actions.ts +8 -0
  95. package/src/meeting/index.ts +246 -80
  96. package/src/meeting/locusMediaRequest.ts +7 -0
  97. package/src/meeting/request.ts +26 -1
  98. package/src/meeting/request.type.ts +7 -0
  99. package/src/meeting/util.ts +8 -10
  100. package/src/meeting-info/meeting-info-v2.ts +23 -11
  101. package/src/meeting-info/utilv2.ts +3 -1
  102. package/src/meetings/index.ts +77 -20
  103. package/src/meetings/meetings.types.ts +10 -0
  104. package/src/meetings/util.ts +2 -1
  105. package/src/member/index.ts +9 -0
  106. package/src/member/types.ts +8 -0
  107. package/src/member/util.ts +34 -24
  108. package/src/metrics/constants.ts +1 -1
  109. package/src/multistream/remoteMedia.ts +28 -15
  110. package/src/multistream/sendSlotManager.ts +31 -0
  111. package/src/recording-controller/enums.ts +5 -2
  112. package/src/recording-controller/index.ts +17 -4
  113. package/src/recording-controller/util.ts +20 -5
  114. package/src/roap/index.ts +10 -8
  115. package/src/webinar/index.ts +197 -3
  116. package/test/unit/spec/annotation/index.ts +46 -1
  117. package/test/unit/spec/locus-info/index.js +292 -60
  118. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  119. package/test/unit/spec/locus-info/selfUtils.js +91 -1
  120. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  121. package/test/unit/spec/meeting/index.js +689 -105
  122. package/test/unit/spec/meeting/utils.js +22 -19
  123. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  124. package/test/unit/spec/meeting-info/utilv2.js +17 -0
  125. package/test/unit/spec/meetings/index.js +153 -18
  126. package/test/unit/spec/meetings/utils.js +10 -0
  127. package/test/unit/spec/member/util.js +52 -11
  128. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  129. package/test/unit/spec/recording-controller/index.js +61 -5
  130. package/test/unit/spec/recording-controller/util.js +39 -3
  131. package/test/unit/spec/roap/index.ts +47 -0
  132. package/test/unit/spec/webinar/index.ts +457 -0
  133. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  134. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -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,9 @@ 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';
164
+ import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
162
165
 
163
166
  // default callback so we don't call an undefined function, but in practice it should never be used
164
167
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -848,7 +851,7 @@ export default class Meeting extends StatelessWebexPlugin {
848
851
  * @memberof Meeting
849
852
  */
850
853
  // @ts-ignore
851
- this.webinar = new Webinar({}, {parent: this.webex});
854
+ this.webinar = new Webinar({meetingId: this.id}, {parent: this.webex});
852
855
  /**
853
856
  * helper class for managing receive slots (for multistream media connections)
854
857
  */
@@ -1767,15 +1770,20 @@ export default class Meeting extends StatelessWebexPlugin {
1767
1770
  this.meetingInfo = err.meetingInfo;
1768
1771
  }
1769
1772
  throw new PermissionError();
1770
- } else if (err instanceof MeetingInfoV2WebinarRegistrationError) {
1773
+ } else if (err instanceof MeetingInfoV2JoinWebinarError) {
1771
1774
  this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
1775
+ if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
1776
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
1777
+ } else if (WEBINAR_ERROR_REGISTRATIONID.includes(err.wbxAppApiCode)) {
1778
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID;
1779
+ }
1772
1780
  this.meetingInfoFailureCode = err.wbxAppApiCode;
1773
1781
 
1774
1782
  if (err.meetingInfo) {
1775
1783
  this.meetingInfo = err.meetingInfo;
1776
1784
  }
1777
1785
 
1778
- throw new WebinarRegistrationError();
1786
+ throw new JoinWebinarError();
1779
1787
  } else if (err instanceof MeetingInfoV2PasswordError) {
1780
1788
  LoggerProxy.logger.info(
1781
1789
  // @ts-ignore
@@ -2734,6 +2742,7 @@ export default class Meeting extends StatelessWebexPlugin {
2734
2742
  this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
2735
2743
 
2736
2744
  if (
2745
+ !payload.forceUpdate &&
2737
2746
  contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
2738
2747
  contentShare.disposition === previousContentShare?.disposition &&
2739
2748
  contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
@@ -2780,7 +2789,11 @@ export default class Meeting extends StatelessWebexPlugin {
2780
2789
  // It does not matter who requested to share the whiteboard, everyone gets the same view
2781
2790
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
2782
2791
  // WHITEBOARD - sharing whiteboard
2783
- newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
2792
+ // Webinar attendee should receive whiteboard as remote share
2793
+ newShareStatus =
2794
+ this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
2795
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
2796
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
2784
2797
  }
2785
2798
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
2786
2799
  else if (
@@ -2795,6 +2808,7 @@ export default class Meeting extends StatelessWebexPlugin {
2795
2808
  LoggerProxy.logger.info(
2796
2809
  `Meeting:index#setUpLocusInfoMediaInactiveListener --> this.shareStatus=${this.shareStatus} newShareStatus=${newShareStatus}`
2797
2810
  );
2811
+
2798
2812
  if (newShareStatus !== this.shareStatus) {
2799
2813
  const oldShareStatus = this.shareStatus;
2800
2814
 
@@ -3052,7 +3066,20 @@ export default class Meeting extends StatelessWebexPlugin {
3052
3066
  */
3053
3067
  private setUpLocusResourcesListener() {
3054
3068
  this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_RESOURCES, (payload) => {
3055
- this.webinar.updateWebcastUrl(payload);
3069
+ if (payload) {
3070
+ this.webinar.updateWebcastUrl(payload);
3071
+ Trigger.trigger(
3072
+ this,
3073
+ {
3074
+ file: 'meeting/index',
3075
+ function: 'setUpLocusInfoMeetingInfoListener',
3076
+ },
3077
+ EVENT_TRIGGERS.MEETING_RESOURCE_LINKS_UPDATE,
3078
+ {
3079
+ payload,
3080
+ }
3081
+ );
3082
+ }
3056
3083
  });
3057
3084
  }
3058
3085
 
@@ -3362,6 +3389,20 @@ export default class Meeting extends StatelessWebexPlugin {
3362
3389
  }
3363
3390
  });
3364
3391
 
3392
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
3393
+ Trigger.trigger(
3394
+ this,
3395
+ {
3396
+ file: 'meeting/index',
3397
+ function: 'setUpLocusInfoSelfListener',
3398
+ },
3399
+ EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
3400
+ {
3401
+ payload,
3402
+ }
3403
+ );
3404
+ });
3405
+
3365
3406
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
3366
3407
  const isModeratorOrCohost =
3367
3408
  payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
@@ -3371,6 +3412,7 @@ export default class Meeting extends StatelessWebexPlugin {
3371
3412
  payload.newRoles?.includes(SELF_ROLES.MODERATOR)
3372
3413
  );
3373
3414
  this.webinar.updateRoleChanged(payload);
3415
+
3374
3416
  Trigger.trigger(
3375
3417
  this,
3376
3418
  {
@@ -3565,6 +3607,50 @@ export default class Meeting extends StatelessWebexPlugin {
3565
3607
  return this.members.admitMembers(memberIds, locusUrls);
3566
3608
  }
3567
3609
 
3610
+ /**
3611
+ * Manages be right back status updates for the current participant.
3612
+ *
3613
+ * @param {boolean} enabled - Indicates whether the user enabled brb or not.
3614
+ * @returns {Promise<void>} resolves when the brb status is updated or does nothing if not in a multistream meeting.
3615
+ * @throws {Error} - Throws an error if the request fails.
3616
+ */
3617
+ public async beRightBack(enabled: boolean): Promise<void> {
3618
+ if (!this.isMultistream) {
3619
+ const errorMessage = 'Meeting:index#beRightBack --> Not a multistream meeting';
3620
+ const error = new Error(errorMessage);
3621
+
3622
+ LoggerProxy.logger.error(error);
3623
+
3624
+ return Promise.reject(error);
3625
+ }
3626
+
3627
+ if (!this.mediaProperties.webrtcMediaConnection) {
3628
+ const errorMessage = 'Meeting:index#beRightBack --> WebRTC media connection is not defined';
3629
+ const error = new Error(errorMessage);
3630
+
3631
+ LoggerProxy.logger.error(error);
3632
+
3633
+ return Promise.reject(error);
3634
+ }
3635
+
3636
+ // this logic should be applied only to multistream meetings
3637
+ return this.meetingRequest
3638
+ .setBrb({
3639
+ enabled,
3640
+ locusUrl: this.locusUrl,
3641
+ deviceUrl: this.deviceUrl,
3642
+ selfId: this.selfId,
3643
+ })
3644
+ .then(() => {
3645
+ this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
3646
+ })
3647
+ .catch((error) => {
3648
+ LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
3649
+
3650
+ return Promise.reject(error);
3651
+ });
3652
+ }
3653
+
3568
3654
  /**
3569
3655
  * Remove the member from the meeting, boot them
3570
3656
  * @param {String} memberId
@@ -3774,6 +3860,10 @@ export default class Meeting extends StatelessWebexPlugin {
3774
3860
  this.userDisplayHints,
3775
3861
  this.selfUserPolicies
3776
3862
  ),
3863
+ isPremiseRecordingEnabled: RecordingUtil.isPremiseRecordingEnabled(
3864
+ this.userDisplayHints,
3865
+ this.selfUserPolicies
3866
+ ),
3777
3867
  canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints),
3778
3868
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints),
3779
3869
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints),
@@ -3800,6 +3890,7 @@ export default class Meeting extends StatelessWebexPlugin {
3800
3890
  this.userDisplayHints
3801
3891
  ),
3802
3892
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
3893
+ canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
3803
3894
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
3804
3895
  this.userDisplayHints,
3805
3896
  this.selfUserPolicies
@@ -4095,10 +4186,11 @@ export default class Meeting extends StatelessWebexPlugin {
4095
4186
  */
4096
4187
  private setLogUploadTimer() {
4097
4188
  // 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
4098
- const LOG_UPLOAD_INTERVALS = [0.1, 1, 15, 15, 30, 30, 30, 60];
4189
+ const LOG_UPLOAD_INTERVALS = [0.1, 15, 30, 60]; // in minutes
4099
4190
 
4100
4191
  const delay =
4101
4192
  1000 *
4193
+ 60 *
4102
4194
  // @ts-ignore - config coming from registerPlugin
4103
4195
  this.config.logUploadIntervalMultiplicationFactor *
4104
4196
  LOG_UPLOAD_INTERVALS[this.logUploadIntervalIndex];
@@ -4537,11 +4629,12 @@ export default class Meeting extends StatelessWebexPlugin {
4537
4629
  * Close the peer connections and remove them from the class.
4538
4630
  * Cleanup any media connection related things.
4539
4631
  *
4632
+ * @param {boolean} resetMuteStates whether to also reset the audio/video mute state information
4540
4633
  * @returns {Promise}
4541
4634
  * @public
4542
4635
  * @memberof Meeting
4543
4636
  */
4544
- public closePeerConnections() {
4637
+ public closePeerConnections(resetMuteStates = true) {
4545
4638
  if (this.mediaProperties.webrtcMediaConnection) {
4546
4639
  if (this.remoteMediaManager) {
4547
4640
  this.remoteMediaManager.stop();
@@ -4554,12 +4647,15 @@ export default class Meeting extends StatelessWebexPlugin {
4554
4647
 
4555
4648
  this.receiveSlotManager.reset();
4556
4649
  this.mediaProperties.webrtcMediaConnection.close();
4650
+ this.mediaProperties.unsetPeerConnection();
4557
4651
  this.sendSlotManager.reset();
4558
4652
  this.setNetworkStatus(undefined);
4559
4653
  }
4560
4654
 
4561
- this.audio = null;
4562
- this.video = null;
4655
+ if (resetMuteStates) {
4656
+ this.audio = null;
4657
+ this.video = null;
4658
+ }
4563
4659
 
4564
4660
  return Promise.resolve();
4565
4661
  }
@@ -4819,7 +4915,7 @@ export default class Meeting extends StatelessWebexPlugin {
4819
4915
  * @param {Object} options - options to join with media
4820
4916
  * @param {JoinOptions} [options.joinOptions] - see #join()
4821
4917
  * @param {AddMediaOptions} [options.mediaOptions] - see #addMedia()
4822
- * @returns {Promise} -- {join: see join(), media: see addMedia()}
4918
+ * @returns {Promise} -- {join: see join(), media: see addMedia(), multistreamEnabled: flag to indicate if we managed to join in multistream mode}
4823
4919
  * @public
4824
4920
  * @memberof Meeting
4825
4921
  * @example
@@ -4909,6 +5005,7 @@ export default class Meeting extends StatelessWebexPlugin {
4909
5005
  return {
4910
5006
  join: joinResponse,
4911
5007
  media: mediaResponse,
5008
+ multistreamEnabled: this.isMultistream,
4912
5009
  };
4913
5010
  } catch (error) {
4914
5011
  LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
@@ -4917,7 +5014,17 @@ export default class Meeting extends StatelessWebexPlugin {
4917
5014
 
4918
5015
  this.roap.abortTurnDiscovery();
4919
5016
 
4920
- if (joined && isRetry) {
5017
+ // if this was the first attempt, let's do a retry
5018
+ let shouldRetry = !isRetry;
5019
+
5020
+ if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
5021
+ // errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
5022
+ // so there is no point doing a retry
5023
+ shouldRetry = false;
5024
+ }
5025
+
5026
+ // we only want to call leave if join was successful and this was a retry or we won't be doing any more retries
5027
+ if (joined && (isRetry || !shouldRetry)) {
4921
5028
  try {
4922
5029
  await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
4923
5030
  } catch (e) {
@@ -4941,15 +5048,6 @@ export default class Meeting extends StatelessWebexPlugin {
4941
5048
  }
4942
5049
  );
4943
5050
 
4944
- // if this was the first attempt, let's do a retry
4945
- let shouldRetry = !isRetry;
4946
-
4947
- if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
4948
- // errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
4949
- // so there is no point doing a retry
4950
- shouldRetry = false;
4951
- }
4952
-
4953
5051
  if (shouldRetry) {
4954
5052
  LoggerProxy.logger.warn('Meeting:index#joinWithMedia --> retrying call to joinWithMedia');
4955
5053
  this.joinWithMediaRetryInfo.isRetry = true;
@@ -5205,7 +5303,16 @@ export default class Meeting extends StatelessWebexPlugin {
5205
5303
  (this.config.receiveReactions || options.receiveReactions) &&
5206
5304
  this.isReactionsSupported()
5207
5305
  ) {
5208
- const {name} = this.members.membersCollection.get(e.data.sender.participantId);
5306
+ const member = this.members.membersCollection.get(e.data.sender.participantId);
5307
+ if (!member) {
5308
+ // @ts-ignore -- fix type
5309
+ LoggerProxy.logger.warn(
5310
+ `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
5311
+ );
5312
+ break;
5313
+ }
5314
+
5315
+ const {name} = member;
5209
5316
  const processedReaction: ProcessedReaction = {
5210
5317
  reaction: e.data.reaction,
5211
5318
  sender: {
@@ -5259,6 +5366,9 @@ export default class Meeting extends StatelessWebexPlugin {
5259
5366
  this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5260
5367
  );
5261
5368
 
5369
+ // @ts-ignore
5370
+ this.webex.internal.voicea.deregisterEvents();
5371
+
5262
5372
  this.areVoiceaEventsSetup = false;
5263
5373
  this.triggerStopReceivingTranscriptionEvent();
5264
5374
  }
@@ -5369,16 +5479,19 @@ export default class Meeting extends StatelessWebexPlugin {
5369
5479
  this.meetingFiniteStateMachine.reset();
5370
5480
  }
5371
5481
 
5372
- // @ts-ignore
5373
- this.webex.internal.newMetrics.submitClientEvent({
5374
- name: 'client.call.initiated',
5375
- payload: {
5376
- trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5377
- isRoapCallEnabled: true,
5378
- pstnAudioType: options?.pstnAudioType,
5379
- },
5380
- options: {meetingId: this.id},
5381
- });
5482
+ // send client.call.initiated unless told not to
5483
+ if (options.sendCallInitiated !== false) {
5484
+ // @ts-ignore
5485
+ this.webex.internal.newMetrics.submitClientEvent({
5486
+ name: 'client.call.initiated',
5487
+ payload: {
5488
+ trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5489
+ isRoapCallEnabled: true,
5490
+ pstnAudioType: options?.pstnAudioType,
5491
+ },
5492
+ options: {meetingId: this.id},
5493
+ });
5494
+ }
5382
5495
 
5383
5496
  LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
5384
5497
 
@@ -5566,17 +5679,23 @@ export default class Meeting extends StatelessWebexPlugin {
5566
5679
  */
5567
5680
  async updateLLMConnection() {
5568
5681
  // @ts-ignore - Fix type
5569
- const {url, info: {datachannelUrl} = {}} = this.locusInfo;
5682
+ const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
5570
5683
 
5571
5684
  const isJoined = this.isJoined();
5572
5685
 
5686
+ // webinar panelist should use new data channel in practice session
5687
+ const dataChannelUrl =
5688
+ this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
5689
+ ? practiceSessionDatachannelUrl
5690
+ : datachannelUrl;
5691
+
5573
5692
  // @ts-ignore - Fix type
5574
5693
  if (this.webex.internal.llm.isConnected()) {
5575
5694
  if (
5576
5695
  // @ts-ignore - Fix type
5577
5696
  url === this.webex.internal.llm.getLocusUrl() &&
5578
5697
  // @ts-ignore - Fix type
5579
- datachannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
5698
+ dataChannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
5580
5699
  isJoined
5581
5700
  ) {
5582
5701
  return undefined;
@@ -5593,7 +5712,7 @@ export default class Meeting extends StatelessWebexPlugin {
5593
5712
 
5594
5713
  // @ts-ignore - Fix type
5595
5714
  return this.webex.internal.llm
5596
- .registerAndConnect(url, datachannelUrl)
5715
+ .registerAndConnect(url, dataChannelUrl)
5597
5716
  .then((registerAndConnectResult) => {
5598
5717
  // @ts-ignore - Fix type
5599
5718
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -5965,6 +6084,11 @@ export default class Meeting extends StatelessWebexPlugin {
5965
6084
  public roapMessageReceived = (roapMessage: RoapMessage) => {
5966
6085
  const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
5967
6086
 
6087
+ if (this.isMultistream && mediaServer !== 'homer') {
6088
+ throw new MultistreamNotSupportedError(
6089
+ `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
6090
+ );
6091
+ }
5968
6092
  this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
5969
6093
 
5970
6094
  if (mediaServer) {
@@ -6087,16 +6211,20 @@ export default class Meeting extends StatelessWebexPlugin {
6087
6211
  logText: `${LOG_HEADER} Roap Offer`,
6088
6212
  }
6089
6213
  ).catch((error) => {
6214
+ const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
6215
+
6090
6216
  // @ts-ignore
6091
6217
  this.webex.internal.newMetrics.submitClientEvent({
6092
6218
  name: 'client.media-engine.remote-sdp-received',
6093
6219
  payload: {
6094
- canProceed: false,
6220
+ canProceed: multistreamNotSupported,
6095
6221
  errors: [
6096
6222
  // @ts-ignore
6097
6223
  this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
6098
6224
  {
6099
- clientErrorCode: CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
6225
+ clientErrorCode: multistreamNotSupported
6226
+ ? CALL_DIAGNOSTIC_CONFIG.MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE
6227
+ : CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
6100
6228
  }
6101
6229
  ),
6102
6230
  ],
@@ -6104,7 +6232,7 @@ export default class Meeting extends StatelessWebexPlugin {
6104
6232
  options: {meetingId: this.id, rawError: error},
6105
6233
  });
6106
6234
 
6107
- this.deferSDPAnswer.reject(new Error('failed to send ROAP SDP offer'));
6235
+ this.deferSDPAnswer.reject(error);
6108
6236
  clearTimeout(this.sdpResponseTimer);
6109
6237
  this.sdpResponseTimer = undefined;
6110
6238
  });
@@ -6432,6 +6560,14 @@ export default class Meeting extends StatelessWebexPlugin {
6432
6560
  this.webex.meetings.geoHintInfo?.clientAddress ||
6433
6561
  options.data.intervalMetadata.peerReflexiveIP ||
6434
6562
  MQA_STATS.DEFAULT_IP;
6563
+
6564
+ const {members} = this.getMembers().membersCollection;
6565
+
6566
+ // Count members that are in the meeting
6567
+ options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6568
+ (member: Member) => member.isInMeeting
6569
+ ).length;
6570
+
6435
6571
  // @ts-ignore
6436
6572
  this.webex.internal.newMetrics.submitMQE({
6437
6573
  name: 'client.mediaquality.event',
@@ -6763,32 +6899,6 @@ export default class Meeting extends StatelessWebexPlugin {
6763
6899
  }
6764
6900
  }
6765
6901
 
6766
- /**
6767
- * Handles device logging
6768
- *
6769
- * @private
6770
- * @static
6771
- * @param {boolean} isAudioEnabled
6772
- * @param {boolean} isVideoEnabled
6773
- * @returns {Promise<void>}
6774
- */
6775
-
6776
- private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
6777
- try {
6778
- let devices = [];
6779
- if (isVideoEnabled && isAudioEnabled) {
6780
- devices = await getDevices();
6781
- } else if (isVideoEnabled) {
6782
- devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
6783
- } else if (isAudioEnabled) {
6784
- devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
6785
- }
6786
- MeetingUtil.handleDeviceLogging(devices);
6787
- } catch {
6788
- // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
6789
- }
6790
- }
6791
-
6792
6902
  /**
6793
6903
  * Returns a promise. This promise is created once the local sdp offer has been successfully created and is resolved
6794
6904
  * once the remote sdp answer has been received.
@@ -7012,7 +7122,9 @@ export default class Meeting extends StatelessWebexPlugin {
7012
7122
 
7013
7123
  const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
7014
7124
 
7015
- LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
7125
+ LoggerProxy.logger.info(
7126
+ `${LOG_HEADER} media connection created this.isMultistream=${this.isMultistream}`
7127
+ );
7016
7128
 
7017
7129
  if (this.isMultistream) {
7018
7130
  this.remoteMediaManager = new RemoteMediaManager(
@@ -7090,6 +7202,33 @@ export default class Meeting extends StatelessWebexPlugin {
7090
7202
  }
7091
7203
  }
7092
7204
 
7205
+ /**
7206
+ * Cleans up stats analyzer, peer connection and other things before
7207
+ * we can create a new transcoded media connection
7208
+ *
7209
+ * @private
7210
+ * @returns {Promise<void>}
7211
+ */
7212
+ private async downgradeFromMultistreamToTranscoded(): Promise<void> {
7213
+ if (this.statsAnalyzer) {
7214
+ await this.statsAnalyzer.stopAnalyzer();
7215
+ }
7216
+ this.statsAnalyzer = null;
7217
+
7218
+ this.isMultistream = false;
7219
+
7220
+ if (this.mediaProperties.webrtcMediaConnection) {
7221
+ // close peer connection, but don't reset mute state information, because we will want to use it on the retry
7222
+ this.closePeerConnections(false);
7223
+
7224
+ this.mediaProperties.unsetPeerConnection();
7225
+ }
7226
+
7227
+ this.locusMediaRequest?.downgradeFromMultistreamToTranscoded();
7228
+
7229
+ this.createStatsAnalyzer();
7230
+ }
7231
+
7093
7232
  /**
7094
7233
  * Sends stats report, closes peer connection and cleans up any media connection
7095
7234
  * related things before trying to establish media connection again with turn server
@@ -7284,19 +7423,33 @@ export default class Meeting extends StatelessWebexPlugin {
7284
7423
 
7285
7424
  this.createStatsAnalyzer();
7286
7425
 
7287
- await this.establishMediaConnection(
7288
- remoteMediaManagerConfig,
7289
- bundlePolicy,
7290
- forceTurnDiscovery,
7291
- turnServerInfo
7292
- );
7426
+ try {
7427
+ await this.establishMediaConnection(
7428
+ remoteMediaManagerConfig,
7429
+ bundlePolicy,
7430
+ forceTurnDiscovery,
7431
+ turnServerInfo
7432
+ );
7433
+ } catch (error) {
7434
+ if (error instanceof MultistreamNotSupportedError) {
7435
+ LoggerProxy.logger.warn(
7436
+ `${LOG_HEADER} we asked for multistream backend (Homer), but got transcoded backend, recreating media connection...`
7437
+ );
7293
7438
 
7294
- if (audioEnabled || videoEnabled) {
7295
- await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
7296
- } else {
7297
- LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
7298
- }
7439
+ await this.downgradeFromMultistreamToTranscoded();
7299
7440
 
7441
+ // Establish new media connection with forced TURN discovery
7442
+ // We need to do TURN discovery again, because backend will be creating a new confluence, so it might land on a different node or cluster
7443
+ await this.establishMediaConnection(
7444
+ remoteMediaManagerConfig,
7445
+ bundlePolicy,
7446
+ true,
7447
+ undefined
7448
+ );
7449
+ } else {
7450
+ throw error;
7451
+ }
7452
+ }
7300
7453
  if (this.mediaProperties.hasLocalShareStream()) {
7301
7454
  await this.enqueueScreenShareFloorRequest();
7302
7455
  }
@@ -8266,7 +8419,7 @@ export default class Meeting extends StatelessWebexPlugin {
8266
8419
  if (layoutType) {
8267
8420
  if (!LAYOUT_TYPES.includes(layoutType)) {
8268
8421
  return this.rejectWithErrorLog(
8269
- 'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType received.'
8422
+ `Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType "${layoutType}" received.`
8270
8423
  );
8271
8424
  }
8272
8425
 
@@ -8422,6 +8575,12 @@ export default class Meeting extends StatelessWebexPlugin {
8422
8575
  correlationId: this.correlationId,
8423
8576
  muted,
8424
8577
  encoderImplementation: this.statsAnalyzer?.shareVideoEncoderImplementation,
8578
+ // TypeScript 4 does not recognize the `displaySurface` property. Instead of upgrading the
8579
+ // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8580
+ // all we're doing here is adding metrics.
8581
+ // eslint-disable-next-line dot-notation
8582
+ displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8583
+ isMultistream: this.isMultistream,
8425
8584
  });
8426
8585
  };
8427
8586
 
@@ -8624,6 +8783,11 @@ export default class Meeting extends StatelessWebexPlugin {
8624
8783
  this.stopTranscription();
8625
8784
  this.transcription = undefined;
8626
8785
  }
8786
+
8787
+ this.annotation.deregisterEvents();
8788
+
8789
+ // @ts-ignore - fix types
8790
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
8627
8791
  };
8628
8792
 
8629
8793
  /**
@@ -8661,10 +8825,12 @@ export default class Meeting extends StatelessWebexPlugin {
8661
8825
 
8662
8826
  return;
8663
8827
  }
8664
- const {keepAliveUrl} = this.joinedWith;
8828
+
8665
8829
  const keepAliveInterval = (this.joinedWith.keepAliveSecs - 1) * 750; // taken from UCF
8666
8830
 
8667
8831
  this.keepAliveTimerId = setInterval(() => {
8832
+ const {keepAliveUrl} = this.joinedWith;
8833
+
8668
8834
  this.meetingRequest.keepAlive({keepAliveUrl}).catch((error) => {
8669
8835
  LoggerProxy.logger.warn(
8670
8836
  `Meeting:index#startKeepAlive --> Stopping sending keepAlives to ${keepAliveUrl} after error ${error}`
@@ -342,4 +342,11 @@ export class LocusMediaRequest extends WebexPlugin {
342
342
  public isConfluenceCreated() {
343
343
  return this.confluenceState === 'created';
344
344
  }
345
+
346
+ /**
347
+ * This method needs to be called when we downgrade from multistream to transcoded connection.
348
+ */
349
+ public downgradeFromMultistreamToTranscoded() {
350
+ this.config.preferTranscoding = true;
351
+ }
345
352
  }
@@ -27,7 +27,7 @@ import {
27
27
  _SLIDES_,
28
28
  ANNOTATION,
29
29
  } from '../constants';
30
- import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
30
+ import {SendReactionOptions, BrbOptions, ToggleReactionsOptions} from './request.type';
31
31
  import MeetingUtil from './util';
32
32
  import {AnnotationInfo} from '../annotation/annotation.types';
33
33
  import {ClientMediaPreferences} from '../reachability/reachability.types';
@@ -909,4 +909,29 @@ export default class MeetingRequest extends StatelessWebexPlugin {
909
909
  uri: locusUrl,
910
910
  });
911
911
  }
912
+
913
+ /**
914
+ * Sends a request to set be right back status.
915
+ *
916
+ * @param {Object} options - The options for brb request.
917
+ * @param {boolean} options.enabled - Whether brb status is enabled.
918
+ * @param {string} options.locusUrl - The URL of the locus.
919
+ * @param {string} options.deviceUrl - The URL of the device.
920
+ * @param {string} options.selfId - The ID of the participant.
921
+ * @returns {Promise}
922
+ */
923
+ setBrb({enabled, locusUrl, deviceUrl, selfId}: BrbOptions) {
924
+ const uri = `${locusUrl}/${PARTICIPANT}/${selfId}/${CONTROLS}`;
925
+
926
+ return this.locusDeltaRequest({
927
+ method: HTTP_VERBS.PATCH,
928
+ uri,
929
+ body: {
930
+ brb: {
931
+ enabled,
932
+ deviceUrl,
933
+ },
934
+ },
935
+ });
936
+ }
912
937
  }
@@ -11,3 +11,10 @@ export type ToggleReactionsOptions = {
11
11
  locusUrl: string;
12
12
  requestingParticipantId: string;
13
13
  };
14
+
15
+ export type BrbOptions = {
16
+ enabled: boolean;
17
+ locusUrl: string;
18
+ deviceUrl: string;
19
+ selfId: string;
20
+ };