@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-multipleLLM.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +26 -2
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/locus-info/index.js +77 -95
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/parser.js +4 -1
  10. package/dist/locus-info/parser.js.map +1 -1
  11. package/dist/media/properties.js +53 -5
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/brbState.js +14 -12
  14. package/dist/meeting/brbState.js.map +1 -1
  15. package/dist/meeting/in-meeting-actions.js +8 -0
  16. package/dist/meeting/in-meeting-actions.js.map +1 -1
  17. package/dist/meeting/index.js +443 -225
  18. package/dist/meeting/index.js.map +1 -1
  19. package/dist/meeting/muteState.js +2 -5
  20. package/dist/meeting/muteState.js.map +1 -1
  21. package/dist/meeting/request.js +44 -0
  22. package/dist/meeting/request.js.map +1 -1
  23. package/dist/meeting/request.type.js.map +1 -1
  24. package/dist/meeting/type.js +7 -0
  25. package/dist/meeting/type.js.map +1 -0
  26. package/dist/meeting/util.js +98 -13
  27. package/dist/meeting/util.js.map +1 -1
  28. package/dist/meeting-info/meeting-info-v2.js +29 -21
  29. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  30. package/dist/meetings/index.js +18 -10
  31. package/dist/meetings/index.js.map +1 -1
  32. package/dist/member/index.js.map +1 -1
  33. package/dist/member/types.js.map +1 -1
  34. package/dist/members/collection.js +13 -0
  35. package/dist/members/collection.js.map +1 -1
  36. package/dist/members/index.js +53 -29
  37. package/dist/members/index.js.map +1 -1
  38. package/dist/members/request.js +3 -3
  39. package/dist/members/request.js.map +1 -1
  40. package/dist/members/util.js +25 -8
  41. package/dist/members/util.js.map +1 -1
  42. package/dist/metrics/constants.js +2 -1
  43. package/dist/metrics/constants.js.map +1 -1
  44. package/dist/multistream/mediaRequestManager.js +1 -1
  45. package/dist/multistream/mediaRequestManager.js.map +1 -1
  46. package/dist/multistream/remoteMedia.js +34 -5
  47. package/dist/multistream/remoteMedia.js.map +1 -1
  48. package/dist/multistream/remoteMediaGroup.js +42 -2
  49. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  50. package/dist/multistream/sendSlotManager.js +32 -2
  51. package/dist/multistream/sendSlotManager.js.map +1 -1
  52. package/dist/reachability/index.js +3 -3
  53. package/dist/reachability/index.js.map +1 -1
  54. package/dist/types/constants.d.ts +24 -0
  55. package/dist/types/locus-info/index.d.ts +54 -10
  56. package/dist/types/media/properties.d.ts +21 -0
  57. package/dist/types/meeting/brbState.d.ts +0 -1
  58. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  59. package/dist/types/meeting/index.d.ts +51 -20
  60. package/dist/types/meeting/request.d.ts +18 -1
  61. package/dist/types/meeting/request.type.d.ts +74 -0
  62. package/dist/types/meeting/type.d.ts +9 -0
  63. package/dist/types/meeting/util.d.ts +13 -3
  64. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  65. package/dist/types/meetings/index.d.ts +3 -1
  66. package/dist/types/member/types.d.ts +1 -0
  67. package/dist/types/members/collection.d.ts +6 -0
  68. package/dist/types/members/index.d.ts +22 -9
  69. package/dist/types/members/request.d.ts +1 -1
  70. package/dist/types/members/util.d.ts +13 -6
  71. package/dist/types/metrics/constants.d.ts +1 -0
  72. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  73. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  74. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  75. package/dist/webinar/index.js +1 -1
  76. package/package.json +23 -24
  77. package/src/constants.ts +25 -2
  78. package/src/locus-info/index.ts +133 -96
  79. package/src/locus-info/parser.ts +5 -1
  80. package/src/media/properties.ts +43 -0
  81. package/src/meeting/brbState.ts +9 -7
  82. package/src/meeting/in-meeting-actions.ts +17 -0
  83. package/src/meeting/index.ts +273 -42
  84. package/src/meeting/muteState.ts +2 -6
  85. package/src/meeting/request.ts +39 -0
  86. package/src/meeting/request.type.ts +64 -0
  87. package/src/meeting/type.ts +9 -0
  88. package/src/meeting/util.ts +114 -22
  89. package/src/meeting-info/meeting-info-v2.ts +24 -5
  90. package/src/meetings/index.ts +12 -5
  91. package/src/member/index.ts +1 -0
  92. package/src/member/types.ts +1 -0
  93. package/src/members/collection.ts +11 -0
  94. package/src/members/index.ts +51 -15
  95. package/src/members/request.ts +2 -2
  96. package/src/members/util.ts +34 -6
  97. package/src/metrics/constants.ts +1 -0
  98. package/src/multistream/mediaRequestManager.ts +7 -7
  99. package/src/multistream/remoteMedia.ts +34 -4
  100. package/src/multistream/remoteMediaGroup.ts +37 -2
  101. package/src/multistream/sendSlotManager.ts +34 -2
  102. package/src/reachability/index.ts +3 -3
  103. package/test/unit/spec/locus-info/index.js +229 -98
  104. package/test/unit/spec/locus-info/parser.js +3 -2
  105. package/test/unit/spec/media/properties.ts +137 -0
  106. package/test/unit/spec/meeting/brbState.ts +9 -9
  107. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  108. package/test/unit/spec/meeting/index.js +1022 -93
  109. package/test/unit/spec/meeting/muteState.js +32 -6
  110. package/test/unit/spec/meeting/request.js +92 -0
  111. package/test/unit/spec/meeting/utils.js +167 -17
  112. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  113. package/test/unit/spec/meetings/index.js +12 -1
  114. package/test/unit/spec/members/collection.js +120 -0
  115. package/test/unit/spec/members/index.js +140 -12
  116. package/test/unit/spec/members/request.js +57 -2
  117. package/test/unit/spec/members/utils.js +139 -17
  118. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  119. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  120. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  121. package/test/unit/spec/reachability/index.ts +158 -1
@@ -28,6 +28,8 @@ import {
28
28
  StatsAnalyzerEventNames,
29
29
  NetworkQualityEventNames,
30
30
  NetworkQualityMonitor,
31
+ StatsMonitor,
32
+ StatsMonitorEventNames,
31
33
  } from '@webex/internal-media-core';
32
34
 
33
35
  import {
@@ -121,6 +123,7 @@ import {
121
123
  WEBINAR_ERROR_REGISTRATION_ID,
122
124
  JOIN_BEFORE_HOST,
123
125
  REGISTRATION_ID_STATUS,
126
+ STAGE_MANAGER_TYPE,
124
127
  } from '../constants';
125
128
  import BEHAVIORAL_METRICS from '../metrics/constants';
126
129
  import ParameterError from '../common/errors/parameter';
@@ -164,6 +167,8 @@ import {BrbState, createBrbState} from './brbState';
164
167
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
165
168
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
166
169
  import {ReachabilityMetrics} from '../reachability/reachability.types';
170
+ import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
171
+ import {Invitee} from './type';
167
172
 
168
173
  // default callback so we don't call an undefined function, but in practice it should never be used
169
174
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -248,6 +253,7 @@ export type CallStateForMetrics = {
248
253
  loginType?: string;
249
254
  userNameInput?: string;
250
255
  emailInput?: string;
256
+ pstnCorrelationId?: string;
251
257
  };
252
258
 
253
259
  export const MEDIA_UPDATE_TYPE = {
@@ -265,6 +271,7 @@ export enum ScreenShareFloorStatus {
265
271
  type FetchMeetingInfoParams = {
266
272
  password?: string;
267
273
  registrationId?: string;
274
+ classificationId?: string;
268
275
  captchaCode?: string;
269
276
  extraParams?: Record<string, any>;
270
277
  sendCAevents?: boolean;
@@ -629,6 +636,7 @@ export default class Meeting extends StatelessWebexPlugin {
629
636
  shareStatus: string;
630
637
  screenShareFloorState: ScreenShareFloorStatus;
631
638
  statsAnalyzer: StatsAnalyzer;
639
+ statsMonitor: StatsMonitor;
632
640
  transcription: Transcription;
633
641
  updateMediaConnections: (mediaConnections: any[]) => void;
634
642
  userDisplayHints: any;
@@ -741,10 +749,11 @@ export default class Meeting extends StatelessWebexPlugin {
741
749
  /**
742
750
  * @param {Object} attrs
743
751
  * @param {Object} options
752
+ * @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
744
753
  * @constructor
745
754
  * @memberof Meeting
746
755
  */
747
- constructor(attrs: any, options: object) {
756
+ constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
748
757
  super({}, options);
749
758
  /**
750
759
  * @instance
@@ -770,6 +779,11 @@ export default class Meeting extends StatelessWebexPlugin {
770
779
  * @memberof Meeting
771
780
  */
772
781
  this.id = uuid.v4();
782
+
783
+ if (callback) {
784
+ callback(this);
785
+ }
786
+
773
787
  /**
774
788
  * Call state used for metrics
775
789
  * @instance
@@ -1276,6 +1290,13 @@ export default class Meeting extends StatelessWebexPlugin {
1276
1290
  * @memberof Meeting
1277
1291
  */
1278
1292
  this.networkQualityMonitor = null;
1293
+ /**
1294
+ * @instance
1295
+ * @type {StatsMonitor}
1296
+ * @private
1297
+ * @memberof Meeting
1298
+ */
1299
+ this.statsMonitor = null;
1279
1300
  /**
1280
1301
  * Indicates network status of the webrtc media connection
1281
1302
  * @instance
@@ -1674,6 +1695,22 @@ export default class Meeting extends StatelessWebexPlugin {
1674
1695
  this.callStateForMetrics.correlationId = correlationId;
1675
1696
  }
1676
1697
 
1698
+ /**
1699
+ * Getter - Returns callStateForMetrics.pstnCorrelationId
1700
+ * @returns {string | undefined}
1701
+ */
1702
+ get pstnCorrelationId(): string | undefined {
1703
+ return this.callStateForMetrics.pstnCorrelationId;
1704
+ }
1705
+
1706
+ /**
1707
+ * Setter - sets callStateForMetrics.pstnCorrelationId
1708
+ * @param {string | undefined} correlationId
1709
+ */
1710
+ set pstnCorrelationId(correlationId: string | undefined) {
1711
+ this.callStateForMetrics.pstnCorrelationId = correlationId;
1712
+ }
1713
+
1677
1714
  /**
1678
1715
  * Getter - Returns callStateForMetrics.userNameInput
1679
1716
  * @returns {string}
@@ -1876,6 +1913,7 @@ export default class Meeting extends StatelessWebexPlugin {
1876
1913
  extraParams = {},
1877
1914
  sendCAevents = false,
1878
1915
  registrationId = null,
1916
+ classificationId = null,
1879
1917
  }): Promise<void> {
1880
1918
  try {
1881
1919
  const captchaInfo = captchaCode
@@ -1892,7 +1930,9 @@ export default class Meeting extends StatelessWebexPlugin {
1892
1930
  this.locusId,
1893
1931
  extraParams,
1894
1932
  {meetingId: this.id, sendCAevents},
1895
- registrationId
1933
+ registrationId,
1934
+ null,
1935
+ classificationId
1896
1936
  );
1897
1937
 
1898
1938
  this.parseMeetingInfo(info?.body, this.destination, info?.errors);
@@ -3056,12 +3096,16 @@ export default class Meeting extends StatelessWebexPlugin {
3056
3096
  // There is no concept of local/remote share for whiteboard
3057
3097
  // It does not matter who requested to share the whiteboard, everyone gets the same view
3058
3098
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
3059
- // WHITEBOARD - sharing whiteboard
3060
- // Webinar attendee should receive whiteboard as remote share
3061
- newShareStatus =
3062
- this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
3063
- ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
3064
- : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3099
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3100
+ // WHITEBOARD - sharing whiteboard
3101
+ // Webinar attendee should receive whiteboard as remote share
3102
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3103
+ } else if (this.guest) {
3104
+ // If user is a guest to a meeting, they should receive whiteboard as remote share
3105
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3106
+ } else {
3107
+ newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3108
+ }
3065
3109
  }
3066
3110
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
3067
3111
  else if (
@@ -3119,6 +3163,23 @@ export default class Meeting extends StatelessWebexPlugin {
3119
3163
  },
3120
3164
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD
3121
3165
  );
3166
+ // @ts-ignore
3167
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
3168
+ key: 'internal.client.share.stopped',
3169
+ });
3170
+ // @ts-ignore
3171
+ this.webex.internal.newMetrics.submitClientEvent({
3172
+ name: 'client.share.stopped',
3173
+ payload: {
3174
+ mediaType: 'whiteboard',
3175
+ shareDuration:
3176
+ // @ts-ignore
3177
+ this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
3178
+ },
3179
+ options: {
3180
+ meetingId: this.id,
3181
+ },
3182
+ });
3122
3183
  break;
3123
3184
 
3124
3185
  case SHARE_STATUS.NO_SHARE:
@@ -3137,6 +3198,14 @@ export default class Meeting extends StatelessWebexPlugin {
3137
3198
  this.shareCAEventSentStatus.receiveStart = false;
3138
3199
  this.shareCAEventSentStatus.receiveStop = false;
3139
3200
 
3201
+ let finalBeneficiaryId = contentShare.beneficiaryId;
3202
+ // In case of attendee in webinar, the whiteboard is shared by other participants
3203
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3204
+ if (!finalBeneficiaryId && whiteboardShare.beneficiaryId) {
3205
+ finalBeneficiaryId = whiteboardShare.beneficiaryId;
3206
+ }
3207
+ }
3208
+
3140
3209
  Trigger.trigger(
3141
3210
  this,
3142
3211
  {
@@ -3145,7 +3214,7 @@ export default class Meeting extends StatelessWebexPlugin {
3145
3214
  },
3146
3215
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
3147
3216
  {
3148
- memberId: contentShare.beneficiaryId,
3217
+ memberId: finalBeneficiaryId,
3149
3218
  url: contentShare.url,
3150
3219
  shareInstanceId: this.remoteShareInstanceId,
3151
3220
  annotationInfo: contentShare.annotation,
@@ -3827,49 +3896,44 @@ export default class Meeting extends StatelessWebexPlugin {
3827
3896
 
3828
3897
  /**
3829
3898
  * Invite a guest to the call that isn't normally part of this call
3830
- * @param {Object} invitee
3899
+ * @param {Invitee} invitee
3831
3900
  * @param {String} invitee.emailAddress
3832
3901
  * @param {String} invitee.email
3833
3902
  * @param {String} invitee.phoneNumber
3834
3903
  * @param {Boolean} [alertIfActive]
3904
+ * @param {Boolean} [invitee.skipEmailValidation]
3905
+ * @param {Boolean} [invitee.isInternalNumber]
3835
3906
  * @returns {Promise} see #members.addMember
3836
3907
  * @public
3837
3908
  * @memberof Meeting
3838
3909
  */
3839
- public invite(
3840
- invitee: {
3841
- emailAddress: string;
3842
- email: string;
3843
- phoneNumber: string;
3844
- roles: Array<string>;
3845
- },
3846
- alertIfActive = true
3847
- ) {
3910
+ public invite(invitee: Invitee, alertIfActive = true) {
3848
3911
  return this.members.addMember(invitee, alertIfActive);
3849
3912
  }
3850
3913
 
3851
3914
  /**
3852
3915
  * Cancel an outgoing phone call invitation made during a meeting
3853
- * @param {Object} invitee
3916
+ * @param {Invitee} invitee
3854
3917
  * @param {String} invitee.phoneNumber
3855
3918
  * @returns {Promise} see #members.cancelPhoneInvite
3856
3919
  * @public
3857
3920
  * @memberof Meeting
3858
3921
  */
3859
- public cancelPhoneInvite(invitee: {phoneNumber: string}) {
3922
+ public cancelPhoneInvite(invitee: Invitee) {
3860
3923
  return this.members.cancelPhoneInvite(invitee);
3861
3924
  }
3862
3925
 
3863
3926
  /**
3864
- * Cancel an SIP call invitation made during a meeting
3865
- * @param {Object} invitee
3927
+ * Cancel an SIP/phone call invitation made during a meeting
3928
+ * @param {Invitee} invitee
3866
3929
  * @param {String} invitee.memberId
3867
- * @returns {Promise} see #members.cancelSIPInvite
3930
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3931
+ * @returns {Promise} see #members.cancelInviteByMemberId
3868
3932
  * @public
3869
3933
  * @memberof Meeting
3870
3934
  */
3871
- public cancelSIPInvite(invitee: {memberId: string}) {
3872
- return this.members.cancelSIPInvite(invitee);
3935
+ public cancelInviteByMemberId(invitee: Invitee) {
3936
+ return this.members.cancelInviteByMemberId(invitee);
3873
3937
  }
3874
3938
 
3875
3939
  /**
@@ -4167,8 +4231,14 @@ export default class Meeting extends StatelessWebexPlugin {
4167
4231
  isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(this.userDisplayHints),
4168
4232
  canStartManualCaption: MeetingUtil.canStartManualCaption(this.userDisplayHints),
4169
4233
  canStopManualCaption: MeetingUtil.canStopManualCaption(this.userDisplayHints),
4234
+ isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
4235
+ isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
4236
+ isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4170
4237
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4171
4238
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4239
+ isSpokenLanguageAutoDetectionEnabled: MeetingUtil.isSpokenLanguageAutoDetectionEnabled(
4240
+ this.userDisplayHints
4241
+ ),
4172
4242
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
4173
4243
  canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(this.userDisplayHints),
4174
4244
  isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
@@ -5924,15 +5994,6 @@ export default class Meeting extends StatelessWebexPlugin {
5924
5994
  this.meetingFiniteStateMachine.fail(error);
5925
5995
  LoggerProxy.logger.error('Meeting:index#join --> Failed', error);
5926
5996
 
5927
- // @ts-ignore
5928
- this.webex.internal.newMetrics.submitClientEvent({
5929
- name: 'client.locus.join.response',
5930
- payload: {
5931
- identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
5932
- },
5933
- options: {meetingId: this.id, rawError: error},
5934
- });
5935
-
5936
5997
  // TODO: change this to error codes and pre defined dictionary
5937
5998
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_FAILURE, {
5938
5999
  correlation_id: this.correlationId,
@@ -6082,8 +6143,9 @@ export default class Meeting extends StatelessWebexPlugin {
6082
6143
  */
6083
6144
  private dialInPstn() {
6084
6145
  if (this.isPhoneProvisioned(this.dialInDeviceStatus)) return Promise.resolve(); // prevent multiple dial in devices from being provisioned
6146
+ this.pstnCorrelationId = uuid.v4();
6085
6147
 
6086
- const {correlationId, locusUrl} = this;
6148
+ const {pstnCorrelationId, locusUrl} = this;
6087
6149
 
6088
6150
  if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
6089
6151
 
@@ -6091,7 +6153,7 @@ export default class Meeting extends StatelessWebexPlugin {
6091
6153
  this.meetingRequest
6092
6154
  // @ts-ignore
6093
6155
  .dialIn({
6094
- correlationId,
6156
+ correlationId: pstnCorrelationId,
6095
6157
  dialInUrl: this.dialInUrl,
6096
6158
  locusUrl,
6097
6159
  clientUrl: this.deviceUrl,
@@ -6100,12 +6162,17 @@ export default class Meeting extends StatelessWebexPlugin {
6100
6162
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
6101
6163
  correlation_id: this.correlationId,
6102
6164
  dial_in_url: this.dialInUrl,
6165
+ dial_in_correlation_id: pstnCorrelationId,
6103
6166
  locus_id: locusUrl.split('/').pop(),
6104
6167
  client_url: this.deviceUrl,
6105
6168
  reason: error.error?.message,
6106
6169
  stack: error.stack,
6107
6170
  });
6108
6171
 
6172
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6173
+ this.pstnCorrelationId = undefined;
6174
+ }
6175
+
6109
6176
  return Promise.reject(error);
6110
6177
  })
6111
6178
  );
@@ -6120,8 +6187,9 @@ export default class Meeting extends StatelessWebexPlugin {
6120
6187
  */
6121
6188
  private dialOutPstn(phoneNumber: string) {
6122
6189
  if (this.isPhoneProvisioned(this.dialOutDeviceStatus)) return Promise.resolve(); // prevent multiple dial out devices from being provisioned
6190
+ this.pstnCorrelationId = uuid.v4();
6123
6191
 
6124
- const {correlationId, locusUrl} = this;
6192
+ const {locusUrl, pstnCorrelationId} = this;
6125
6193
 
6126
6194
  if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
6127
6195
 
@@ -6129,7 +6197,7 @@ export default class Meeting extends StatelessWebexPlugin {
6129
6197
  this.meetingRequest
6130
6198
  // @ts-ignore
6131
6199
  .dialOut({
6132
- correlationId,
6200
+ correlationId: pstnCorrelationId,
6133
6201
  dialOutUrl: this.dialOutUrl,
6134
6202
  phoneNumber,
6135
6203
  locusUrl,
@@ -6139,12 +6207,17 @@ export default class Meeting extends StatelessWebexPlugin {
6139
6207
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
6140
6208
  correlation_id: this.correlationId,
6141
6209
  dial_out_url: this.dialOutUrl,
6210
+ dial_out_correlation_id: pstnCorrelationId,
6142
6211
  locus_id: locusUrl.split('/').pop(),
6143
6212
  client_url: this.deviceUrl,
6144
6213
  reason: error.error?.message,
6145
6214
  stack: error.stack,
6146
6215
  });
6147
6216
 
6217
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6218
+ this.pstnCorrelationId = undefined;
6219
+ }
6220
+
6148
6221
  return Promise.reject(error);
6149
6222
  })
6150
6223
  );
@@ -6158,6 +6231,8 @@ export default class Meeting extends StatelessWebexPlugin {
6158
6231
  * @returns {Promise}
6159
6232
  */
6160
6233
  public disconnectPhoneAudio() {
6234
+ const correlationToClear = this.pstnCorrelationId;
6235
+
6161
6236
  return Promise.all([
6162
6237
  this.isPhoneProvisioned(this.dialInDeviceStatus)
6163
6238
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialInUrl)
@@ -6165,7 +6240,11 @@ export default class Meeting extends StatelessWebexPlugin {
6165
6240
  this.isPhoneProvisioned(this.dialOutDeviceStatus)
6166
6241
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialOutUrl)
6167
6242
  : Promise.resolve(),
6168
- ]);
6243
+ ]).then(() => {
6244
+ if (this.pstnCorrelationId === correlationToClear) {
6245
+ this.pstnCorrelationId = undefined;
6246
+ }
6247
+ });
6169
6248
  }
6170
6249
 
6171
6250
  /**
@@ -6742,6 +6821,10 @@ export default class Meeting extends StatelessWebexPlugin {
6742
6821
  // @ts-ignore
6743
6822
  this.webex.internal.newMetrics.submitClientEvent({
6744
6823
  name: 'client.ice.start',
6824
+ payload: {
6825
+ // @ts-ignore
6826
+ labels: MeetingUtil.getCaEventLabelsForIpVersion(this.webex),
6827
+ },
6745
6828
  options: {
6746
6829
  meetingId: this.id,
6747
6830
  },
@@ -6911,10 +6994,10 @@ export default class Meeting extends StatelessWebexPlugin {
6911
6994
  }
6912
6995
  }
6913
6996
 
6914
- // Count members that are in the meeting.
6997
+ // Count members that are in the meeting or in the lobby.
6915
6998
  const {members} = this.getMembers().membersCollection;
6916
6999
  event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6917
- (member: Member) => member.isInMeeting
7000
+ (member: Member) => member.isInMeeting || member.isInLobby
6918
7001
  ).length;
6919
7002
 
6920
7003
  // @ts-ignore
@@ -7273,10 +7356,12 @@ export default class Meeting extends StatelessWebexPlugin {
7273
7356
  if (this.config.stats.enableStatsAnalyzer) {
7274
7357
  // @ts-ignore - config coming from registerPlugin
7275
7358
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
7359
+ this.statsMonitor = new StatsMonitor();
7276
7360
  this.statsAnalyzer = new StatsAnalyzer({
7277
7361
  // @ts-ignore - config coming from registerPlugin
7278
7362
  config: this.config.stats,
7279
7363
  networkQualityMonitor: this.networkQualityMonitor,
7364
+ statsMonitor: this.statsMonitor,
7280
7365
  isMultistream: this.isMultistream,
7281
7366
  });
7282
7367
  this.shareCAEventSentStatus = {
@@ -7290,6 +7375,33 @@ export default class Meeting extends StatelessWebexPlugin {
7290
7375
  NetworkQualityEventNames.NETWORK_QUALITY,
7291
7376
  this.sendNetworkQualityEvent.bind(this)
7292
7377
  );
7378
+
7379
+ this.statsMonitor.on(StatsMonitorEventNames.INBOUND_AUDIO_ISSUE, (data) => {
7380
+ // Before forwarding any inbound audio issues to the app, make sure that we have at least one other
7381
+ // participant in the meeting with unmuted audio.
7382
+ // We don't check this.mediaProperties.mediaDirection here, because that's already handled in statsAnalyzer,
7383
+ // so we won't get this event if we are not setup to receive any audio
7384
+ const atLeastOneUnmutedOtherMember = Object.values(
7385
+ this.members.membersCollection.getAll()
7386
+ ).find((member) => {
7387
+ return !member.isSelf && !member.isPairedWithSelf && !member.isAudioMuted;
7388
+ });
7389
+
7390
+ if (atLeastOneUnmutedOtherMember) {
7391
+ this.mediaProperties.sendMediaIssueMetric(
7392
+ 'inbound_audio',
7393
+ data.issueSubType,
7394
+ this.correlationId
7395
+ );
7396
+
7397
+ Trigger.trigger(
7398
+ this,
7399
+ {file: 'meeting/index', function: 'createStatsAnalyzer'},
7400
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
7401
+ data
7402
+ );
7403
+ }
7404
+ });
7293
7405
  }
7294
7406
  }
7295
7407
 
@@ -7588,6 +7700,10 @@ export default class Meeting extends StatelessWebexPlugin {
7588
7700
  }
7589
7701
 
7590
7702
  this.statsAnalyzer = null;
7703
+ this.networkQualityMonitor?.removeAllListeners();
7704
+ this.networkQualityMonitor = null;
7705
+ this.statsMonitor?.removeAllListeners();
7706
+ this.statsMonitor = null;
7591
7707
 
7592
7708
  // when media fails, we want to upload a webrtc dump to see whats going on
7593
7709
  // this function is async, but returns once the stats have been gathered
@@ -7611,6 +7727,10 @@ export default class Meeting extends StatelessWebexPlugin {
7611
7727
  await this.statsAnalyzer.stopAnalyzer();
7612
7728
  }
7613
7729
  this.statsAnalyzer = null;
7730
+ this.networkQualityMonitor?.removeAllListeners();
7731
+ this.networkQualityMonitor = null;
7732
+ this.statsMonitor?.removeAllListeners();
7733
+ this.statsMonitor = null;
7614
7734
 
7615
7735
  this.isMultistream = false;
7616
7736
 
@@ -7782,6 +7902,9 @@ export default class Meeting extends StatelessWebexPlugin {
7782
7902
 
7783
7903
  this.allowMediaInLobby = options?.allowMediaInLobby;
7784
7904
 
7905
+ // @ts-ignore
7906
+ const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
7907
+
7785
7908
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
7786
7909
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
7787
7910
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
@@ -7880,6 +8003,7 @@ export default class Meeting extends StatelessWebexPlugin {
7880
8003
  locus_id: this.locusUrl.split('/').pop(),
7881
8004
  connectionType,
7882
8005
  ipVersion,
8006
+ ipver,
7883
8007
  selectedCandidatePairChanges,
7884
8008
  numTransports,
7885
8009
  isMultistream: this.isMultistream,
@@ -7948,6 +8072,7 @@ export default class Meeting extends StatelessWebexPlugin {
7948
8072
  ...reachabilityMetrics,
7949
8073
  ...iceCandidateErrors,
7950
8074
  iceCandidatesCount: this.iceCandidatesCount,
8075
+ ipver,
7951
8076
  });
7952
8077
 
7953
8078
  await this.cleanUpOnAddMediaFailure();
@@ -8387,6 +8512,10 @@ export default class Meeting extends StatelessWebexPlugin {
8387
8512
  }
8388
8513
 
8389
8514
  if (whiteboard) {
8515
+ // @ts-ignore
8516
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8517
+ key: 'internal.client.share.initiated',
8518
+ });
8390
8519
  // @ts-ignore
8391
8520
  this.webex.internal.newMetrics.submitClientEvent({
8392
8521
  name: 'client.share.initiated',
@@ -8446,11 +8575,17 @@ export default class Meeting extends StatelessWebexPlugin {
8446
8575
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
8447
8576
 
8448
8577
  if (whiteboard) {
8578
+ // @ts-ignore
8579
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8580
+ key: 'internal.client.share.stopped',
8581
+ });
8449
8582
  // @ts-ignore
8450
8583
  this.webex.internal.newMetrics.submitClientEvent({
8451
8584
  name: 'client.share.stopped',
8452
8585
  payload: {
8453
8586
  mediaType: 'whiteboard',
8587
+ // @ts-ignore
8588
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8454
8589
  },
8455
8590
  options: {
8456
8591
  meetingId: this.id,
@@ -8608,12 +8743,18 @@ export default class Meeting extends StatelessWebexPlugin {
8608
8743
  }
8609
8744
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
8610
8745
  if (content) {
8746
+ // @ts-ignore
8747
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8748
+ key: 'internal.client.share.stopped',
8749
+ });
8611
8750
  // @ts-ignore
8612
8751
  this.webex.internal.newMetrics.submitClientEvent({
8613
8752
  name: 'client.share.stopped',
8614
8753
  payload: {
8615
8754
  mediaType: 'share',
8616
8755
  shareInstanceId: this.localShareInstanceId,
8756
+ // @ts-ignore
8757
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8617
8758
  },
8618
8759
  options: {meetingId: this.id},
8619
8760
  });
@@ -9590,6 +9731,11 @@ export default class Meeting extends StatelessWebexPlugin {
9590
9731
  this.shareCAEventSentStatus.transmitStart = false;
9591
9732
  this.shareCAEventSentStatus.transmitStop = false;
9592
9733
 
9734
+ // @ts-ignore
9735
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
9736
+ key: 'internal.client.share.initiated',
9737
+ });
9738
+
9593
9739
  // @ts-ignore
9594
9740
  this.webex.internal.newMetrics.submitClientEvent({
9595
9741
  name: 'client.share.initiated',
@@ -9763,4 +9909,89 @@ export default class Meeting extends StatelessWebexPlugin {
9763
9909
  selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9764
9910
  };
9765
9911
  }
9912
+
9913
+ /**
9914
+ * Set the stage for the meeting
9915
+ *
9916
+ * @param {SetStageOptions} options Options to use when setting the stage
9917
+ * @returns {Promise} The locus request
9918
+ */
9919
+ setStage({
9920
+ activeSpeakerProportion = 0.5,
9921
+ customBackground,
9922
+ customLogo,
9923
+ customNameLabel,
9924
+ importantParticipants,
9925
+ lockAttendeeViewOnStage = false,
9926
+ showActiveSpeaker = false,
9927
+ }: SetStageOptions = {}) {
9928
+ const videoLayout: SetStageVideoLayout = {
9929
+ overrideDefault: true,
9930
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
9931
+ stageParameters: {
9932
+ activeSpeakerProportion,
9933
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
9934
+ stageManagerType: 0,
9935
+ },
9936
+ };
9937
+
9938
+ if (importantParticipants?.length) {
9939
+ videoLayout.stageParameters.importantParticipants = importantParticipants.map(
9940
+ (importantParticipant, index) => ({...importantParticipant, order: index + 1})
9941
+ );
9942
+ }
9943
+
9944
+ if (customLogo) {
9945
+ if (!videoLayout.customLayouts) {
9946
+ videoLayout.customLayouts = {};
9947
+ }
9948
+ videoLayout.customLayouts.logo = customLogo;
9949
+ // eslint-disable-next-line no-bitwise
9950
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
9951
+ }
9952
+
9953
+ if (customBackground) {
9954
+ if (!videoLayout.customLayouts) {
9955
+ videoLayout.customLayouts = {};
9956
+ }
9957
+ videoLayout.customLayouts.background = customBackground;
9958
+ // eslint-disable-next-line no-bitwise
9959
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
9960
+ }
9961
+
9962
+ if (customNameLabel) {
9963
+ videoLayout.nameLabelStyle = customNameLabel;
9964
+ // eslint-disable-next-line no-bitwise
9965
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
9966
+ }
9967
+
9968
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9969
+ }
9970
+
9971
+ /**
9972
+ * Unset the stage for the meeting
9973
+ *
9974
+ * @returns {Promise} The locus request
9975
+ */
9976
+ unsetStage() {
9977
+ const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
9978
+
9979
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9980
+ }
9981
+
9982
+ /**
9983
+ * Notifies the host with the given meeting UUID and display names.
9984
+ *
9985
+ * @param {string} meetingUuid - The UUID of the meeting.
9986
+ * @param {string[]} displayName - An array of display names to notify the host with.
9987
+ * @returns {Promise<any>} The result of the notifyHost request.
9988
+ */
9989
+ notifyHost(meetingUuid: string, displayName: string[]) {
9990
+ return this.meetingRequest.notifyHost(
9991
+ this.meetingInfo.siteFullUrl,
9992
+ this.locusId,
9993
+ meetingUuid,
9994
+ displayName
9995
+ );
9996
+ }
9766
9997
  }
@@ -291,18 +291,14 @@ export class MuteState {
291
291
  );
292
292
 
293
293
  return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
294
- .then((locus) => {
294
+ .then((response) => {
295
295
  LoggerProxy.logger.info(
296
296
  `Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
297
297
  );
298
298
 
299
299
  this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
300
300
 
301
- if (locus) {
302
- meeting.locusInfo.handleLocusDelta(locus, meeting);
303
- }
304
-
305
- return locus;
301
+ return MeetingUtil.updateLocusFromApiResponse(meeting, response);
306
302
  })
307
303
  .catch((remoteUpdateError) => {
308
304
  LoggerProxy.logger.warn(