@webex/plugin-meetings 3.8.1-next.4 → 3.8.1-next.41
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.
- package/README.md +26 -13
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +24 -2
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +38 -84
- package/dist/locus-info/index.js.map +1 -1
- package/dist/media/index.js +2 -2
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/brbState.js +14 -12
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +6 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +213 -77
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +19 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/type.js +7 -0
- package/dist/meeting/type.js.map +1 -0
- package/dist/meeting/util.js +11 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +35 -33
- package/dist/meetings/index.js.map +1 -1
- package/dist/members/index.js +11 -9
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +3 -3
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js +18 -6
- package/dist/members/util.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +34 -5
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +42 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +32 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/index.js +5 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +22 -0
- package/dist/types/locus-info/index.d.ts +0 -9
- package/dist/types/meeting/brbState.d.ts +0 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +35 -18
- package/dist/types/meeting/request.d.ts +9 -1
- package/dist/types/meeting/request.type.d.ts +74 -0
- package/dist/types/meeting/type.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +3 -0
- package/dist/types/members/index.d.ts +10 -7
- package/dist/types/members/request.d.ts +1 -1
- package/dist/types/members/util.d.ts +7 -3
- package/dist/types/multistream/remoteMedia.d.ts +20 -1
- package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
- package/dist/types/multistream/sendSlotManager.d.ts +16 -0
- package/dist/types/reachability/index.d.ts +2 -2
- package/dist/webinar/index.js +1 -1
- package/package.json +24 -25
- package/src/constants.ts +23 -2
- package/src/locus-info/index.ts +47 -82
- package/src/media/index.ts +2 -2
- package/src/meeting/brbState.ts +9 -7
- package/src/meeting/in-meeting-actions.ts +13 -0
- package/src/meeting/index.ts +168 -42
- package/src/meeting/request.ts +16 -0
- package/src/meeting/request.type.ts +64 -0
- package/src/meeting/type.ts +9 -0
- package/src/meeting/util.ts +13 -0
- package/src/meetings/index.ts +3 -2
- package/src/members/index.ts +13 -10
- package/src/members/request.ts +2 -2
- package/src/members/util.ts +16 -4
- package/src/multistream/mediaRequestManager.ts +7 -7
- package/src/multistream/remoteMedia.ts +34 -4
- package/src/multistream/remoteMediaGroup.ts +37 -2
- package/src/multistream/sendSlotManager.ts +34 -2
- package/src/reachability/index.ts +5 -13
- package/test/unit/spec/locus-info/index.js +177 -83
- package/test/unit/spec/media/index.ts +107 -0
- package/test/unit/spec/meeting/brbState.ts +9 -9
- package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
- package/test/unit/spec/meeting/index.js +694 -60
- package/test/unit/spec/meeting/request.js +71 -0
- package/test/unit/spec/meeting/utils.js +21 -0
- package/test/unit/spec/meetings/index.js +2 -0
- package/test/unit/spec/members/index.js +68 -9
- package/test/unit/spec/members/request.js +2 -2
- package/test/unit/spec/members/utils.js +27 -7
- package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
- package/test/unit/spec/multistream/remoteMedia.ts +66 -2
- package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
- package/test/unit/spec/reachability/index.ts +2 -6
package/src/meeting/index.ts
CHANGED
@@ -121,6 +121,7 @@ import {
|
|
121
121
|
WEBINAR_ERROR_REGISTRATION_ID,
|
122
122
|
JOIN_BEFORE_HOST,
|
123
123
|
REGISTRATION_ID_STATUS,
|
124
|
+
STAGE_MANAGER_TYPE,
|
124
125
|
} from '../constants';
|
125
126
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
126
127
|
import ParameterError from '../common/errors/parameter';
|
@@ -164,6 +165,8 @@ import {BrbState, createBrbState} from './brbState';
|
|
164
165
|
import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
|
165
166
|
import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
166
167
|
import {ReachabilityMetrics} from '../reachability/reachability.types';
|
168
|
+
import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
|
169
|
+
import {Invitee} from './type';
|
167
170
|
|
168
171
|
// default callback so we don't call an undefined function, but in practice it should never be used
|
169
172
|
const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
|
@@ -231,6 +234,14 @@ export type AddMediaOptions = {
|
|
231
234
|
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
232
235
|
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
233
236
|
allowMediaInLobby?: boolean; // allows adding media when in the lobby
|
237
|
+
additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
|
238
|
+
};
|
239
|
+
|
240
|
+
export type AdditionalMediaOptions = {
|
241
|
+
sendVideo?: boolean; // if not specified, default value of videoEnabled is used
|
242
|
+
receiveVideo?: boolean; // if not specified, default value of videoEnabled is used
|
243
|
+
sendAudio?: boolean; // if not specified, default value of audioEnabled true is used
|
244
|
+
receiveAudio?: boolean; // if not specified, default value of audioEnabled true is used
|
234
245
|
};
|
235
246
|
|
236
247
|
export type CallStateForMetrics = {
|
@@ -263,8 +274,9 @@ type FetchMeetingInfoParams = {
|
|
263
274
|
};
|
264
275
|
|
265
276
|
type MediaReachabilityMetrics = ReachabilityMetrics & {
|
266
|
-
|
267
|
-
|
277
|
+
subnet_reachable: boolean;
|
278
|
+
selected_cluster: string | null;
|
279
|
+
selected_subnet: string | null;
|
268
280
|
};
|
269
281
|
|
270
282
|
/**
|
@@ -732,10 +744,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
732
744
|
/**
|
733
745
|
* @param {Object} attrs
|
734
746
|
* @param {Object} options
|
747
|
+
* @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
|
735
748
|
* @constructor
|
736
749
|
* @memberof Meeting
|
737
750
|
*/
|
738
|
-
constructor(attrs: any, options: object) {
|
751
|
+
constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
|
739
752
|
super({}, options);
|
740
753
|
/**
|
741
754
|
* @instance
|
@@ -761,6 +774,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
761
774
|
* @memberof Meeting
|
762
775
|
*/
|
763
776
|
this.id = uuid.v4();
|
777
|
+
|
778
|
+
if (callback) {
|
779
|
+
callback(this);
|
780
|
+
}
|
781
|
+
|
764
782
|
/**
|
765
783
|
* Call state used for metrics
|
766
784
|
* @instance
|
@@ -2757,9 +2775,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
2757
2775
|
LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
|
2758
2776
|
({spokenLanguage}) => {
|
2759
2777
|
if (spokenLanguage) {
|
2760
|
-
this.transcription
|
2778
|
+
if (this.transcription?.languageOptions) {
|
2779
|
+
this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
|
2780
|
+
}
|
2761
2781
|
// @ts-ignore
|
2762
|
-
this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
|
2782
|
+
this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage, this.id);
|
2763
2783
|
|
2764
2784
|
Trigger.trigger(
|
2765
2785
|
this,
|
@@ -2768,7 +2788,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
2768
2788
|
function: 'setupLocusControlsListener',
|
2769
2789
|
},
|
2770
2790
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
|
2771
|
-
{spokenLanguage}
|
2791
|
+
{spokenLanguage, meetingId: this.id}
|
2772
2792
|
);
|
2773
2793
|
}
|
2774
2794
|
}
|
@@ -3045,12 +3065,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
3045
3065
|
// There is no concept of local/remote share for whiteboard
|
3046
3066
|
// It does not matter who requested to share the whiteboard, everyone gets the same view
|
3047
3067
|
else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
|
3048
|
-
|
3049
|
-
|
3050
|
-
|
3051
|
-
|
3052
|
-
|
3053
|
-
|
3068
|
+
if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
|
3069
|
+
// WHITEBOARD - sharing whiteboard
|
3070
|
+
// Webinar attendee should receive whiteboard as remote share
|
3071
|
+
newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
|
3072
|
+
} else if (this.guest) {
|
3073
|
+
// If user is a guest to a meeting, they should receive whiteboard as remote share
|
3074
|
+
newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
|
3075
|
+
} else {
|
3076
|
+
newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
3077
|
+
}
|
3054
3078
|
}
|
3055
3079
|
// or if content share is either released or null and whiteboard share is either released or null, no one is sharing
|
3056
3080
|
else if (
|
@@ -3816,49 +3840,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
3816
3840
|
|
3817
3841
|
/**
|
3818
3842
|
* Invite a guest to the call that isn't normally part of this call
|
3819
|
-
* @param {
|
3843
|
+
* @param {Invitee} invitee
|
3820
3844
|
* @param {String} invitee.emailAddress
|
3821
3845
|
* @param {String} invitee.email
|
3822
3846
|
* @param {String} invitee.phoneNumber
|
3823
3847
|
* @param {Boolean} [alertIfActive]
|
3848
|
+
* @param {Boolean} [invitee.skipEmailValidation]
|
3849
|
+
* @param {Boolean} [invitee.isInternalNumber]
|
3824
3850
|
* @returns {Promise} see #members.addMember
|
3825
3851
|
* @public
|
3826
3852
|
* @memberof Meeting
|
3827
3853
|
*/
|
3828
|
-
public invite(
|
3829
|
-
invitee: {
|
3830
|
-
emailAddress: string;
|
3831
|
-
email: string;
|
3832
|
-
phoneNumber: string;
|
3833
|
-
roles: Array<string>;
|
3834
|
-
},
|
3835
|
-
alertIfActive = true
|
3836
|
-
) {
|
3854
|
+
public invite(invitee: Invitee, alertIfActive = true) {
|
3837
3855
|
return this.members.addMember(invitee, alertIfActive);
|
3838
3856
|
}
|
3839
3857
|
|
3840
3858
|
/**
|
3841
3859
|
* Cancel an outgoing phone call invitation made during a meeting
|
3842
|
-
* @param {
|
3860
|
+
* @param {Invitee} invitee
|
3843
3861
|
* @param {String} invitee.phoneNumber
|
3844
3862
|
* @returns {Promise} see #members.cancelPhoneInvite
|
3845
3863
|
* @public
|
3846
3864
|
* @memberof Meeting
|
3847
3865
|
*/
|
3848
|
-
public cancelPhoneInvite(invitee:
|
3866
|
+
public cancelPhoneInvite(invitee: Invitee) {
|
3849
3867
|
return this.members.cancelPhoneInvite(invitee);
|
3850
3868
|
}
|
3851
3869
|
|
3852
3870
|
/**
|
3853
|
-
* Cancel an SIP call invitation made during a meeting
|
3854
|
-
* @param {
|
3871
|
+
* Cancel an SIP/phone call invitation made during a meeting
|
3872
|
+
* @param {Invitee} invitee
|
3855
3873
|
* @param {String} invitee.memberId
|
3856
|
-
* @
|
3874
|
+
* @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
|
3875
|
+
* @returns {Promise} see #members.cancelInviteByMemberId
|
3857
3876
|
* @public
|
3858
3877
|
* @memberof Meeting
|
3859
3878
|
*/
|
3860
|
-
public
|
3861
|
-
return this.members.
|
3879
|
+
public cancelInviteByMemberId(invitee: Invitee) {
|
3880
|
+
return this.members.cancelInviteByMemberId(invitee);
|
3862
3881
|
}
|
3863
3882
|
|
3864
3883
|
/**
|
@@ -4156,6 +4175,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
4156
4175
|
isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(this.userDisplayHints),
|
4157
4176
|
canStartManualCaption: MeetingUtil.canStartManualCaption(this.userDisplayHints),
|
4158
4177
|
canStopManualCaption: MeetingUtil.canStopManualCaption(this.userDisplayHints),
|
4178
|
+
isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
|
4179
|
+
isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
|
4180
|
+
isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
|
4159
4181
|
isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
|
4160
4182
|
isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
|
4161
4183
|
isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
|
@@ -7754,8 +7776,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7754
7776
|
shareVideoEnabled = true,
|
7755
7777
|
remoteMediaManagerConfig,
|
7756
7778
|
bundlePolicy = 'max-bundle',
|
7779
|
+
additionalMediaOptions = {},
|
7757
7780
|
} = options;
|
7758
7781
|
|
7782
|
+
const {
|
7783
|
+
sendVideo: rawSendVideo,
|
7784
|
+
receiveVideo: rawReceiveVideo,
|
7785
|
+
sendAudio: rawSendAudio,
|
7786
|
+
receiveAudio: rawReceiveAudio,
|
7787
|
+
} = additionalMediaOptions;
|
7788
|
+
|
7789
|
+
const sendVideo = videoEnabled && (rawSendVideo ?? true);
|
7790
|
+
const receiveVideo = videoEnabled && (rawReceiveVideo ?? true);
|
7791
|
+
const sendAudio = audioEnabled && (rawSendAudio ?? true);
|
7792
|
+
const receiveAudio = audioEnabled && (rawReceiveAudio ?? true);
|
7793
|
+
|
7759
7794
|
this.allowMediaInLobby = options?.allowMediaInLobby;
|
7760
7795
|
|
7761
7796
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
@@ -7791,11 +7826,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7791
7826
|
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
|
7792
7827
|
// to avoid doing an extra SDP exchange when they are published for the first time
|
7793
7828
|
this.mediaProperties.setMediaDirection({
|
7794
|
-
sendAudio
|
7795
|
-
sendVideo
|
7829
|
+
sendAudio,
|
7830
|
+
sendVideo,
|
7796
7831
|
sendShare: false,
|
7797
|
-
receiveAudio
|
7798
|
-
receiveVideo
|
7832
|
+
receiveAudio,
|
7833
|
+
receiveVideo,
|
7799
7834
|
receiveShare: shareAudioEnabled || shareVideoEnabled,
|
7800
7835
|
});
|
7801
7836
|
|
@@ -8363,6 +8398,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
8363
8398
|
}
|
8364
8399
|
|
8365
8400
|
if (whiteboard) {
|
8401
|
+
// @ts-ignore
|
8402
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
|
8403
|
+
key: 'internal.client.share.initiated',
|
8404
|
+
});
|
8366
8405
|
// @ts-ignore
|
8367
8406
|
this.webex.internal.newMetrics.submitClientEvent({
|
8368
8407
|
name: 'client.share.initiated',
|
@@ -8422,11 +8461,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
8422
8461
|
const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
|
8423
8462
|
|
8424
8463
|
if (whiteboard) {
|
8464
|
+
// @ts-ignore
|
8465
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
|
8466
|
+
key: 'internal.client.share.stopped',
|
8467
|
+
});
|
8425
8468
|
// @ts-ignore
|
8426
8469
|
this.webex.internal.newMetrics.submitClientEvent({
|
8427
8470
|
name: 'client.share.stopped',
|
8428
8471
|
payload: {
|
8429
8472
|
mediaType: 'whiteboard',
|
8473
|
+
// @ts-ignore
|
8474
|
+
shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
|
8430
8475
|
},
|
8431
8476
|
options: {
|
8432
8477
|
meetingId: this.id,
|
@@ -8584,12 +8629,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
8584
8629
|
}
|
8585
8630
|
this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
|
8586
8631
|
if (content) {
|
8632
|
+
// @ts-ignore
|
8633
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
|
8634
|
+
key: 'internal.client.share.stopped',
|
8635
|
+
});
|
8587
8636
|
// @ts-ignore
|
8588
8637
|
this.webex.internal.newMetrics.submitClientEvent({
|
8589
8638
|
name: 'client.share.stopped',
|
8590
8639
|
payload: {
|
8591
8640
|
mediaType: 'share',
|
8592
8641
|
shareInstanceId: this.localShareInstanceId,
|
8642
|
+
// @ts-ignore
|
8643
|
+
shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
|
8593
8644
|
},
|
8594
8645
|
options: {meetingId: this.id},
|
8595
8646
|
});
|
@@ -9566,6 +9617,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
9566
9617
|
this.shareCAEventSentStatus.transmitStart = false;
|
9567
9618
|
this.shareCAEventSentStatus.transmitStop = false;
|
9568
9619
|
|
9620
|
+
// @ts-ignore
|
9621
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
|
9622
|
+
key: 'internal.client.share.initiated',
|
9623
|
+
});
|
9624
|
+
|
9569
9625
|
// @ts-ignore
|
9570
9626
|
this.webex.internal.newMetrics.submitClientEvent({
|
9571
9627
|
name: 'client.share.initiated',
|
@@ -9721,21 +9777,91 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
9721
9777
|
return total;
|
9722
9778
|
}, 0);
|
9723
9779
|
|
9780
|
+
const selectedSubnetFirstOctet = this.mediaServerIp?.split('.')[0];
|
9781
|
+
|
9724
9782
|
let isSubnetReachable = null;
|
9725
|
-
if (totalSuccessCases > 0) {
|
9726
|
-
|
9727
|
-
|
9783
|
+
if (totalSuccessCases > 0 && selectedSubnetFirstOctet) {
|
9784
|
+
isSubnetReachable =
|
9785
|
+
// @ts-ignore
|
9786
|
+
this.webex.meetings.reachability.isSubnetReachable(selectedSubnetFirstOctet);
|
9728
9787
|
}
|
9729
9788
|
|
9730
|
-
|
9731
|
-
if (this.mediaConnections && this.mediaConnections.length > 0) {
|
9732
|
-
selectedCluster = this.mediaConnections[0].mediaAgentCluster;
|
9733
|
-
}
|
9789
|
+
const selectedCluster = this.mediaConnections?.[0]?.mediaAgentCluster ?? null;
|
9734
9790
|
|
9735
9791
|
return {
|
9736
9792
|
...reachabilityMetrics,
|
9737
|
-
isSubnetReachable,
|
9738
|
-
selectedCluster,
|
9793
|
+
subnet_reachable: isSubnetReachable,
|
9794
|
+
selected_cluster: selectedCluster,
|
9795
|
+
selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
|
9739
9796
|
};
|
9740
9797
|
}
|
9798
|
+
|
9799
|
+
/**
|
9800
|
+
* Set the stage for the meeting
|
9801
|
+
*
|
9802
|
+
* @param {SetStageOptions} options Options to use when setting the stage
|
9803
|
+
* @returns {Promise} The locus request
|
9804
|
+
*/
|
9805
|
+
setStage({
|
9806
|
+
activeSpeakerProportion = 0.5,
|
9807
|
+
customBackground,
|
9808
|
+
customLogo,
|
9809
|
+
customNameLabel,
|
9810
|
+
importantParticipants,
|
9811
|
+
lockAttendeeViewOnStage = false,
|
9812
|
+
showActiveSpeaker = false,
|
9813
|
+
}: SetStageOptions = {}) {
|
9814
|
+
const videoLayout: SetStageVideoLayout = {
|
9815
|
+
overrideDefault: true,
|
9816
|
+
lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
|
9817
|
+
stageParameters: {
|
9818
|
+
activeSpeakerProportion,
|
9819
|
+
showActiveSpeaker: {show: showActiveSpeaker, order: 0},
|
9820
|
+
stageManagerType: 0,
|
9821
|
+
},
|
9822
|
+
};
|
9823
|
+
|
9824
|
+
if (importantParticipants?.length) {
|
9825
|
+
videoLayout.stageParameters.importantParticipants = importantParticipants.map(
|
9826
|
+
(importantParticipant, index) => ({...importantParticipant, order: index + 1})
|
9827
|
+
);
|
9828
|
+
}
|
9829
|
+
|
9830
|
+
if (customLogo) {
|
9831
|
+
if (!videoLayout.customLayouts) {
|
9832
|
+
videoLayout.customLayouts = {};
|
9833
|
+
}
|
9834
|
+
videoLayout.customLayouts.logo = customLogo;
|
9835
|
+
// eslint-disable-next-line no-bitwise
|
9836
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
|
9837
|
+
}
|
9838
|
+
|
9839
|
+
if (customBackground) {
|
9840
|
+
if (!videoLayout.customLayouts) {
|
9841
|
+
videoLayout.customLayouts = {};
|
9842
|
+
}
|
9843
|
+
videoLayout.customLayouts.background = customBackground;
|
9844
|
+
// eslint-disable-next-line no-bitwise
|
9845
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
|
9846
|
+
}
|
9847
|
+
|
9848
|
+
if (customNameLabel) {
|
9849
|
+
videoLayout.nameLabelStyle = customNameLabel;
|
9850
|
+
// eslint-disable-next-line no-bitwise
|
9851
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
|
9852
|
+
}
|
9853
|
+
|
9854
|
+
return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
|
9855
|
+
}
|
9856
|
+
|
9857
|
+
/**
|
9858
|
+
* Unset the stage for the meeting
|
9859
|
+
*
|
9860
|
+
* @returns {Promise} The locus request
|
9861
|
+
*/
|
9862
|
+
unsetStage() {
|
9863
|
+
const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
|
9864
|
+
|
9865
|
+
return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
|
9866
|
+
}
|
9741
9867
|
}
|
package/src/meeting/request.ts
CHANGED
@@ -32,6 +32,7 @@ import {
|
|
32
32
|
BrbOptions,
|
33
33
|
ToggleReactionsOptions,
|
34
34
|
PostMeetingDataConsentOptions,
|
35
|
+
SynchronizeVideoLayout,
|
35
36
|
} from './request.type';
|
36
37
|
import MeetingUtil from './util';
|
37
38
|
import {AnnotationInfo} from '../annotation/annotation.types';
|
@@ -969,4 +970,19 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
969
970
|
},
|
970
971
|
});
|
971
972
|
}
|
973
|
+
|
974
|
+
/**
|
975
|
+
* Synchronize the stage for a meeting
|
976
|
+
*
|
977
|
+
* @param {LocusUrl} locusUrl The locus URL
|
978
|
+
* @param {SetStageVideoLayout} videoLayout The video layout to synchronize
|
979
|
+
* @returns {Promise} The locus request
|
980
|
+
*/
|
981
|
+
synchronizeStage(locusUrl: string, videoLayout: SynchronizeVideoLayout) {
|
982
|
+
return this.locusDeltaRequest({
|
983
|
+
method: HTTP_VERBS.PATCH,
|
984
|
+
uri: `${locusUrl}/${CONTROLS}`,
|
985
|
+
body: {videoLayout},
|
986
|
+
});
|
987
|
+
}
|
972
988
|
}
|
@@ -25,3 +25,67 @@ export type PostMeetingDataConsentOptions = {
|
|
25
25
|
deviceUrl: string;
|
26
26
|
selfId: string;
|
27
27
|
};
|
28
|
+
|
29
|
+
export type StageCustomLogoPositions =
|
30
|
+
| 'LowerLeft'
|
31
|
+
| 'LowerMiddle'
|
32
|
+
| 'LowerRight'
|
33
|
+
| 'UpperLeft'
|
34
|
+
| 'UpperMiddle'
|
35
|
+
| 'UpperRight';
|
36
|
+
|
37
|
+
export type StageNameLabelType = 'Primary' | 'PrimaryInverted' | 'Secondary' | 'SecondaryInverted';
|
38
|
+
|
39
|
+
export type StageCustomBackground = {
|
40
|
+
url: string;
|
41
|
+
[others: string]: unknown;
|
42
|
+
};
|
43
|
+
|
44
|
+
export type StageCustomLogo = {
|
45
|
+
url: string;
|
46
|
+
position: StageCustomLogoPositions;
|
47
|
+
[others: string]: unknown;
|
48
|
+
};
|
49
|
+
|
50
|
+
export type StageCustomNameLabel = {
|
51
|
+
accentColor: string;
|
52
|
+
background: {color: string};
|
53
|
+
border: {color: string};
|
54
|
+
content: {displayName: {color: string}; subtitle: {color: string}};
|
55
|
+
decoration: {color: string};
|
56
|
+
fadeOut?: {delay: number};
|
57
|
+
type: StageNameLabelType;
|
58
|
+
[others: string]: unknown;
|
59
|
+
};
|
60
|
+
|
61
|
+
export type SetStageOptions = {
|
62
|
+
activeSpeakerProportion?: number;
|
63
|
+
customBackground?: StageCustomBackground;
|
64
|
+
customLogo?: StageCustomLogo;
|
65
|
+
customNameLabel?: StageCustomNameLabel;
|
66
|
+
importantParticipants?: {mainCsi: number; participantId: string}[];
|
67
|
+
lockAttendeeViewOnStage?: boolean;
|
68
|
+
showActiveSpeaker?: boolean;
|
69
|
+
};
|
70
|
+
|
71
|
+
export type SetStageVideoLayout = {
|
72
|
+
overrideDefault: true;
|
73
|
+
lockAttendeeViewOnStageOnly: boolean;
|
74
|
+
stageParameters: {
|
75
|
+
importantParticipants?: {participantId: string; mainCsi: number; order: number}[];
|
76
|
+
showActiveSpeaker: {show: boolean; order: number};
|
77
|
+
activeSpeakerProportion: number;
|
78
|
+
stageManagerType: number;
|
79
|
+
};
|
80
|
+
customLayouts?: {
|
81
|
+
background?: StageCustomBackground;
|
82
|
+
logo?: StageCustomLogo;
|
83
|
+
};
|
84
|
+
nameLabelStyle?: StageCustomNameLabel;
|
85
|
+
};
|
86
|
+
|
87
|
+
export type UnsetStageVideoLayout = {
|
88
|
+
overrideDefault: false;
|
89
|
+
};
|
90
|
+
|
91
|
+
export type SynchronizeVideoLayout = SetStageVideoLayout | UnsetStageVideoLayout;
|
package/src/meeting/util.ts
CHANGED
@@ -208,6 +208,10 @@ const MeetingUtil = {
|
|
208
208
|
meeting.simultaneousInterpretation.cleanUp();
|
209
209
|
meeting.locusMediaRequest = undefined;
|
210
210
|
|
211
|
+
meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
|
212
|
+
meeting.correlationId
|
213
|
+
);
|
214
|
+
|
211
215
|
// make sure we send last metrics before we close the peerconnection
|
212
216
|
const stopStatsAnalyzer = meeting.statsAnalyzer
|
213
217
|
? meeting.statsAnalyzer.stopAnalyzer()
|
@@ -542,6 +546,15 @@ const MeetingUtil = {
|
|
542
546
|
canStartManualCaption: (displayHints) =>
|
543
547
|
displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_START),
|
544
548
|
|
549
|
+
isLocalRecordingStarted: (displayHints) =>
|
550
|
+
displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STARTED),
|
551
|
+
|
552
|
+
isLocalRecordingStopped: (displayHints) =>
|
553
|
+
displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STOPPED),
|
554
|
+
|
555
|
+
isLocalRecordingPaused: (displayHints) =>
|
556
|
+
displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_PAUSED),
|
557
|
+
|
545
558
|
canStopManualCaption: (displayHints) => displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_STOP),
|
546
559
|
|
547
560
|
isManualCaptionActive: (displayHints) =>
|
package/src/meetings/index.ts
CHANGED
@@ -1560,11 +1560,12 @@ export default class Meetings extends WebexPlugin {
|
|
1560
1560
|
{
|
1561
1561
|
// @ts-ignore
|
1562
1562
|
parent: this.webex,
|
1563
|
+
},
|
1564
|
+
(newMeeting) => {
|
1565
|
+
this.meetingCollection.set(newMeeting);
|
1563
1566
|
}
|
1564
1567
|
);
|
1565
1568
|
|
1566
|
-
this.meetingCollection.set(meeting);
|
1567
|
-
|
1568
1569
|
try {
|
1569
1570
|
// if no participant has joined the scheduled meeting (meaning meeting is not active) and we get a locusEvent,
|
1570
1571
|
// it means the meeting will start in 5-6 min. In that case, we want to fetchMeetingInfo
|
package/src/members/index.ts
CHANGED
@@ -30,6 +30,7 @@ import MembersUtil from './util';
|
|
30
30
|
import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
31
31
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
32
32
|
import {ServerRoleShape} from './types';
|
33
|
+
import {Invitee} from '../meeting/type';
|
33
34
|
|
34
35
|
/**
|
35
36
|
* Members Update Event
|
@@ -800,18 +801,18 @@ export default class Members extends StatelessWebexPlugin {
|
|
800
801
|
|
801
802
|
/**
|
802
803
|
* Adds a guest Member to the associated meeting
|
803
|
-
* @param {
|
804
|
+
* @param {Invitee} invitee
|
804
805
|
* @param {Boolean} [alertIfActive]
|
805
806
|
* @returns {Promise}
|
806
807
|
* @memberof Members
|
807
808
|
*/
|
808
|
-
addMember(invitee:
|
809
|
+
addMember(invitee: Invitee, alertIfActive?: boolean) {
|
809
810
|
if (!this.locusUrl) {
|
810
811
|
return Promise.reject(
|
811
812
|
new ParameterError('The associated locus url for this meeting object must be defined.')
|
812
813
|
);
|
813
814
|
}
|
814
|
-
if (MembersUtil.isInvalidInvitee(invitee)) {
|
815
|
+
if (invitee?.skipEmailValidation !== true && MembersUtil.isInvalidInvitee(invitee)) {
|
815
816
|
return Promise.reject(
|
816
817
|
new ParameterError(
|
817
818
|
'The invitee must be defined with either a valid email, emailAddress or phoneNumber property.'
|
@@ -825,11 +826,11 @@ export default class Members extends StatelessWebexPlugin {
|
|
825
826
|
|
826
827
|
/**
|
827
828
|
* Cancels an outgoing PSTN call to the associated meeting
|
828
|
-
* @param {
|
829
|
+
* @param {Invitee} invitee
|
829
830
|
* @returns {Promise}
|
830
831
|
* @memberof Members
|
831
832
|
*/
|
832
|
-
cancelPhoneInvite(invitee:
|
833
|
+
cancelPhoneInvite(invitee: Invitee) {
|
833
834
|
if (!this.locusUrl) {
|
834
835
|
return Promise.reject(
|
835
836
|
new ParameterError('The associated locus url for this meeting object must be defined.')
|
@@ -846,12 +847,14 @@ export default class Members extends StatelessWebexPlugin {
|
|
846
847
|
}
|
847
848
|
|
848
849
|
/**
|
849
|
-
* Cancels an SIP call to the associated meeting
|
850
|
-
* @param {
|
850
|
+
* Cancels an SIP/phone call to the associated meeting
|
851
|
+
* @param {Invitee} invitee
|
852
|
+
* @param {String} invitee.memberId - The memberId of the invitee
|
853
|
+
* @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
|
851
854
|
* @returns {Promise}
|
852
855
|
* @memberof Members
|
853
856
|
*/
|
854
|
-
|
857
|
+
cancelInviteByMemberId(invitee: Invitee) {
|
855
858
|
if (!this.locusUrl) {
|
856
859
|
return Promise.reject(
|
857
860
|
new ParameterError('The associated locus url for this meeting object must be defined.')
|
@@ -862,9 +865,9 @@ export default class Members extends StatelessWebexPlugin {
|
|
862
865
|
new ParameterError('The invitee must be defined with a memberId property.')
|
863
866
|
);
|
864
867
|
}
|
865
|
-
const options = MembersUtil.
|
868
|
+
const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
|
866
869
|
|
867
|
-
return this.membersRequest.
|
870
|
+
return this.membersRequest.cancelInviteByMemberId(options);
|
868
871
|
}
|
869
872
|
|
870
873
|
/**
|
package/src/members/request.ts
CHANGED
@@ -285,14 +285,14 @@ export default class MembersRequest extends StatelessWebexPlugin {
|
|
285
285
|
* @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
|
286
286
|
* @memberof MembersRequest
|
287
287
|
*/
|
288
|
-
|
288
|
+
cancelInviteByMemberId(options: any) {
|
289
289
|
if (!options?.invitee?.memberId || !options?.locusUrl) {
|
290
290
|
throw new ParameterError(
|
291
291
|
'invitee must be passed and the associated locus url for this meeting object must be defined.'
|
292
292
|
);
|
293
293
|
}
|
294
294
|
|
295
|
-
const requestParams = MembersUtil.
|
295
|
+
const requestParams = MembersUtil.generateCancelInviteByMemberIdRequestParams(options);
|
296
296
|
|
297
297
|
return this.locusDeltaRequest(requestParams);
|
298
298
|
}
|
package/src/members/util.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import uuid from 'uuid';
|
2
|
+
import {has} from 'lodash';
|
2
3
|
import {
|
3
4
|
HTTP_VERBS,
|
4
5
|
CONTROLS,
|
@@ -13,6 +14,7 @@ import {
|
|
13
14
|
} from '../constants';
|
14
15
|
|
15
16
|
import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape} from './types';
|
17
|
+
import {Invitee} from '../meeting/type';
|
16
18
|
|
17
19
|
const MembersUtil = {
|
18
20
|
/**
|
@@ -47,6 +49,9 @@ const MembersUtil = {
|
|
47
49
|
address:
|
48
50
|
options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
|
49
51
|
...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
|
52
|
+
...(has(options.invitee, 'isInternalNumber')
|
53
|
+
? {isInternalNumber: options.invitee.isInternalNumber}
|
54
|
+
: {}),
|
50
55
|
},
|
51
56
|
],
|
52
57
|
alertIfActive: options.alertIfActive,
|
@@ -101,12 +106,16 @@ const MembersUtil = {
|
|
101
106
|
return requestParams;
|
102
107
|
},
|
103
108
|
|
104
|
-
isInvalidInvitee: (invitee) => {
|
109
|
+
isInvalidInvitee: (invitee: Invitee) => {
|
105
110
|
if (!(invitee && (invitee.email || invitee.emailAddress || invitee.phoneNumber))) {
|
106
111
|
return true;
|
107
112
|
}
|
108
113
|
|
109
114
|
if (invitee.phoneNumber) {
|
115
|
+
if (invitee.isInternalNumber) {
|
116
|
+
return !DIALER_REGEX.INTERNAL_NUMBER.test(invitee.phoneNumber);
|
117
|
+
}
|
118
|
+
|
110
119
|
return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);
|
111
120
|
}
|
112
121
|
|
@@ -371,17 +380,20 @@ const MembersUtil = {
|
|
371
380
|
return requestParams;
|
372
381
|
},
|
373
382
|
|
374
|
-
|
383
|
+
cancelInviteByMemberIdOptions: (invitee, locusUrl) => ({
|
375
384
|
invitee,
|
376
385
|
locusUrl,
|
377
386
|
}),
|
378
387
|
|
379
|
-
|
388
|
+
generateCancelInviteByMemberIdRequestParams: (options) => {
|
389
|
+
const {memberId, isInternalNumber} = options.invitee;
|
390
|
+
const hasIsInternalNumberProp = has(options.invitee, 'isInternalNumber');
|
380
391
|
const body = {
|
381
392
|
actionType: _REMOVE_,
|
382
393
|
invitees: [
|
383
394
|
{
|
384
|
-
address:
|
395
|
+
address: memberId,
|
396
|
+
...(hasIsInternalNumberProp ? {isInternalNumber} : {}),
|
385
397
|
},
|
386
398
|
],
|
387
399
|
};
|