@webex/plugin-meetings 3.8.1-next.3 → 3.8.1-next.31
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 +21 -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/index.js +169 -66
- 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/meetings/index.js +35 -33
- package/dist/meetings/index.js.map +1 -1
- package/dist/members/index.js +8 -6
- 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 +19 -0
- package/dist/types/locus-info/index.d.ts +0 -9
- package/dist/types/meeting/brbState.d.ts +0 -1
- package/dist/types/meeting/index.d.ts +28 -4
- package/dist/types/meeting/request.d.ts +9 -1
- package/dist/types/meeting/request.type.d.ts +74 -0
- package/dist/types/members/index.d.ts +8 -3
- package/dist/types/members/request.d.ts +1 -1
- package/dist/types/members/util.d.ts +5 -2
- 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 -24
- package/src/constants.ts +20 -0
- 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/index.ts +126 -23
- package/src/meeting/request.ts +16 -0
- package/src/meeting/request.type.ts +64 -0
- package/src/meetings/index.ts +3 -2
- package/src/members/index.ts +7 -5
- package/src/members/request.ts +2 -2
- package/src/members/util.ts +14 -3
- 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/index.js +606 -55
- package/test/unit/spec/meeting/request.js +71 -0
- package/test/unit/spec/meetings/index.js +1 -0
- package/test/unit/spec/members/index.js +32 -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/locus-info/index.ts
CHANGED
@@ -48,7 +48,6 @@ export default class LocusInfo extends EventsScope {
|
|
48
48
|
aclUrl: any;
|
49
49
|
baseSequence: any;
|
50
50
|
created: any;
|
51
|
-
deltaParticipants: any;
|
52
51
|
identities: any;
|
53
52
|
membership: any;
|
54
53
|
participants: any;
|
@@ -99,6 +98,7 @@ export default class LocusInfo extends EventsScope {
|
|
99
98
|
private doLocusSync(meeting: any) {
|
100
99
|
let isDelta;
|
101
100
|
let url;
|
101
|
+
let meetingDestroyed = false;
|
102
102
|
|
103
103
|
if (this.locusParser.workingCopy.syncUrl) {
|
104
104
|
url = this.locusParser.workingCopy.syncUrl;
|
@@ -134,32 +134,56 @@ export default class LocusInfo extends EventsScope {
|
|
134
134
|
|
135
135
|
isDelta = false;
|
136
136
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
137
|
+
// Locus sometimes returns 403, for example if meeting has ended, no point trying the fallback to full sync in that case
|
138
|
+
if (e.statusCode !== 403) {
|
139
|
+
return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
|
140
|
+
LoggerProxy.logger.info(
|
141
|
+
'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
|
142
|
+
);
|
143
|
+
this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
|
144
|
+
meetingDestroyed = true;
|
145
|
+
throw err;
|
146
|
+
});
|
147
|
+
}
|
148
|
+
LoggerProxy.logger.info(
|
149
|
+
'Locus-info:index#doLocusSync --> got 403 from Locus, skipping fallback to full sync, destroying the meeting'
|
150
|
+
);
|
151
|
+
} else {
|
152
|
+
LoggerProxy.logger.info(
|
153
|
+
'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
|
154
|
+
);
|
144
155
|
}
|
145
|
-
LoggerProxy.logger.info(
|
146
|
-
'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
|
147
|
-
);
|
148
156
|
this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
|
157
|
+
meetingDestroyed = true;
|
149
158
|
throw e;
|
150
159
|
})
|
151
160
|
.then((res) => {
|
152
|
-
if (
|
153
|
-
if (
|
154
|
-
meeting.locusInfo.handleLocusDelta(res.body, meeting);
|
155
|
-
} else {
|
161
|
+
if (isEmpty(res.body)) {
|
162
|
+
if (isDelta) {
|
156
163
|
LoggerProxy.logger.info(
|
157
164
|
'Locus-info:index#doLocusSync --> received empty body from syncUrl, so we already have latest Locus DTO'
|
158
165
|
);
|
166
|
+
} else {
|
167
|
+
LoggerProxy.logger.info(
|
168
|
+
'Locus-info:index#doLocusSync --> received empty body from full DTO sync request'
|
169
|
+
);
|
159
170
|
}
|
160
|
-
|
161
|
-
|
171
|
+
|
172
|
+
return;
|
173
|
+
}
|
174
|
+
|
175
|
+
if (isDelta) {
|
176
|
+
if (res.body.baseSequence) {
|
177
|
+
meeting.locusInfo.handleLocusDelta(res.body, meeting);
|
178
|
+
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
// in some cases Locus might return us full DTO even when we asked for a delta
|
182
|
+
LoggerProxy.logger.info(
|
183
|
+
'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
|
184
|
+
);
|
162
185
|
}
|
186
|
+
meeting.locusInfo.onFullLocus(res.body);
|
163
187
|
})
|
164
188
|
.catch((e) => {
|
165
189
|
LoggerProxy.logger.info(
|
@@ -176,9 +200,11 @@ export default class LocusInfo extends EventsScope {
|
|
176
200
|
});
|
177
201
|
})
|
178
202
|
.finally(() => {
|
179
|
-
|
180
|
-
|
181
|
-
|
203
|
+
if (!meetingDestroyed) {
|
204
|
+
// Notify parser to resume processing delta events.
|
205
|
+
// Any deltas in the queue that have now been superseded by this sync will simply be ignored
|
206
|
+
this.locusParser.resume();
|
207
|
+
}
|
182
208
|
});
|
183
209
|
}
|
184
210
|
|
@@ -257,17 +283,6 @@ export default class LocusInfo extends EventsScope {
|
|
257
283
|
* @property {Object} person - Contains person data.
|
258
284
|
*/
|
259
285
|
|
260
|
-
/**
|
261
|
-
* Stored participant changes between the last event and the current event.
|
262
|
-
* All previously stored events are overwritten between events.
|
263
|
-
*
|
264
|
-
* @instance
|
265
|
-
* @type {Array<DeltaParticipant>}
|
266
|
-
* @private
|
267
|
-
* @member LocusInfo
|
268
|
-
*/
|
269
|
-
this.deltaParticipants = [];
|
270
|
-
|
271
286
|
this.updateLocusCache(locus);
|
272
287
|
// above section only updates the locusInfo object
|
273
288
|
// The below section makes sure it updates the locusInfo as well as updates the meeting object
|
@@ -373,7 +388,6 @@ export default class LocusInfo extends EventsScope {
|
|
373
388
|
return;
|
374
389
|
}
|
375
390
|
|
376
|
-
this.updateParticipantDeltas(locus.participants);
|
377
391
|
this.scheduledMeeting = locus.meeting || null;
|
378
392
|
this.participants = locus.participants;
|
379
393
|
const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
@@ -462,12 +476,12 @@ export default class LocusInfo extends EventsScope {
|
|
462
476
|
this.updateCreated(locus.created);
|
463
477
|
this.updateFullState(locus.fullState);
|
464
478
|
this.updateHostInfo(locus.host);
|
479
|
+
this.updateLocusUrl(locus.url);
|
465
480
|
this.updateMeetingInfo(locus.info, locus.self);
|
466
481
|
this.updateMediaShares(locus.mediaShares);
|
467
482
|
this.updateParticipantsUrl(locus.participantsUrl);
|
468
483
|
this.updateReplace(locus.replace);
|
469
484
|
this.updateSelf(locus.self);
|
470
|
-
this.updateLocusUrl(locus.url);
|
471
485
|
this.updateAclUrl(locus.aclUrl);
|
472
486
|
this.updateBasequence(locus.baseSequence);
|
473
487
|
this.updateSequence(locus.sequence);
|
@@ -728,55 +742,6 @@ export default class LocusInfo extends EventsScope {
|
|
728
742
|
}
|
729
743
|
}
|
730
744
|
|
731
|
-
/**
|
732
|
-
* Update the deltaParticipants property of this object based on a list of
|
733
|
-
* provided participants.
|
734
|
-
*
|
735
|
-
* @param {Array} [participants] - The participants to update against.
|
736
|
-
* @returns {void}
|
737
|
-
*/
|
738
|
-
updateParticipantDeltas(participants: Array<any> = []) {
|
739
|
-
// Used to find a participant within a participants collection.
|
740
|
-
const findParticipant = (participant, collection) =>
|
741
|
-
collection.find((item) => item.person.id === participant.person.id);
|
742
|
-
|
743
|
-
// Generates an object that indicates which state properties have changed.
|
744
|
-
const generateDelta = (prevState: any = {}, newState: any = {}) => {
|
745
|
-
// Setup deltas.
|
746
|
-
const deltas = {
|
747
|
-
audioStatus: prevState.audioStatus !== newState.audioStatus,
|
748
|
-
videoSlidesStatus: prevState.videoSlidesStatus !== newState.videoSlidesStatus,
|
749
|
-
videoStatus: prevState.videoStatus !== newState.videoStatus,
|
750
|
-
};
|
751
|
-
|
752
|
-
// Clean the object
|
753
|
-
Object.keys(deltas).forEach((key) => {
|
754
|
-
if (deltas[key] !== true) {
|
755
|
-
delete deltas[key];
|
756
|
-
}
|
757
|
-
});
|
758
|
-
|
759
|
-
return deltas;
|
760
|
-
};
|
761
|
-
|
762
|
-
this.deltaParticipants = participants.reduce((collection, participant) => {
|
763
|
-
const existingParticipant = findParticipant(participant, this.participants || []) || {};
|
764
|
-
|
765
|
-
const delta = generateDelta(existingParticipant.status, participant.status);
|
766
|
-
|
767
|
-
const changed = Object.keys(delta).length > 0;
|
768
|
-
|
769
|
-
if (changed) {
|
770
|
-
collection.push({
|
771
|
-
person: participant.person,
|
772
|
-
delta,
|
773
|
-
});
|
774
|
-
}
|
775
|
-
|
776
|
-
return collection;
|
777
|
-
}, []);
|
778
|
-
}
|
779
|
-
|
780
745
|
/**
|
781
746
|
* update meeting's members
|
782
747
|
* @param {Object} participants new participants object
|
package/src/media/index.ts
CHANGED
@@ -239,8 +239,8 @@ Media.createMediaConnection = (
|
|
239
239
|
screenShareAudio: shareAudioStream?.outputStream?.getTracks()[0], // TODO: add type for screenShareAudio in internal-media-core SPARK-446923
|
240
240
|
} as unknown,
|
241
241
|
direction: {
|
242
|
-
audio: Media.getDirection(
|
243
|
-
video: Media.getDirection(
|
242
|
+
audio: Media.getDirection(false, mediaDirection.receiveAudio, mediaDirection.sendAudio),
|
243
|
+
video: Media.getDirection(false, mediaDirection.receiveVideo, mediaDirection.sendVideo),
|
244
244
|
screenShareVideo: Media.getDirection(
|
245
245
|
false,
|
246
246
|
mediaDirection.receiveShare,
|
package/src/meeting/brbState.ts
CHANGED
@@ -58,7 +58,13 @@ export class BrbState {
|
|
58
58
|
public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
|
59
59
|
this.state.client.enabled = enabled;
|
60
60
|
|
61
|
-
|
61
|
+
// Don't set the source state override if enabling brb fails
|
62
|
+
return this.applyClientStateToServer(sendSlotManager).then(() => {
|
63
|
+
sendSlotManager.setSourceStateOverride(
|
64
|
+
MediaType.VideoMain,
|
65
|
+
this.state.client.enabled ? 'away' : null
|
66
|
+
);
|
67
|
+
});
|
62
68
|
}
|
63
69
|
|
64
70
|
/**
|
@@ -92,7 +98,7 @@ export class BrbState {
|
|
92
98
|
|
93
99
|
this.state.syncToServerInProgress = true;
|
94
100
|
|
95
|
-
return this.sendLocalBrbStateToServer(
|
101
|
+
return this.sendLocalBrbStateToServer()
|
96
102
|
.then(() => {
|
97
103
|
this.state.syncToServerInProgress = false;
|
98
104
|
|
@@ -120,10 +126,9 @@ export class BrbState {
|
|
120
126
|
/**
|
121
127
|
* Send the local brb state to the server
|
122
128
|
*
|
123
|
-
* @param {SendSlotManager} sendSlotManager
|
124
129
|
* @returns {Promise}
|
125
130
|
*/
|
126
|
-
private async sendLocalBrbStateToServer(
|
131
|
+
private async sendLocalBrbStateToServer() {
|
127
132
|
const {enabled} = this.state.client;
|
128
133
|
|
129
134
|
if (!this.meeting.isMultistream) {
|
@@ -153,9 +158,6 @@ export class BrbState {
|
|
153
158
|
deviceUrl: this.meeting.deviceUrl,
|
154
159
|
selfId: this.meeting.selfId,
|
155
160
|
})
|
156
|
-
.then(() => {
|
157
|
-
sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
|
158
|
-
})
|
159
161
|
.catch((error) => {
|
160
162
|
LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
|
161
163
|
|
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,7 @@ 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';
|
167
169
|
|
168
170
|
// default callback so we don't call an undefined function, but in practice it should never be used
|
169
171
|
const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
|
@@ -231,6 +233,14 @@ export type AddMediaOptions = {
|
|
231
233
|
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
232
234
|
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
233
235
|
allowMediaInLobby?: boolean; // allows adding media when in the lobby
|
236
|
+
additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
|
237
|
+
};
|
238
|
+
|
239
|
+
export type AdditionalMediaOptions = {
|
240
|
+
sendVideo?: boolean; // if not specified, default value of videoEnabled is used
|
241
|
+
receiveVideo?: boolean; // if not specified, default value of videoEnabled is used
|
242
|
+
sendAudio?: boolean; // if not specified, default value of audioEnabled true is used
|
243
|
+
receiveAudio?: boolean; // if not specified, default value of audioEnabled true is used
|
234
244
|
};
|
235
245
|
|
236
246
|
export type CallStateForMetrics = {
|
@@ -263,8 +273,9 @@ type FetchMeetingInfoParams = {
|
|
263
273
|
};
|
264
274
|
|
265
275
|
type MediaReachabilityMetrics = ReachabilityMetrics & {
|
266
|
-
|
267
|
-
|
276
|
+
subnet_reachable: boolean;
|
277
|
+
selected_cluster: string | null;
|
278
|
+
selected_subnet: string | null;
|
268
279
|
};
|
269
280
|
|
270
281
|
/**
|
@@ -732,10 +743,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
732
743
|
/**
|
733
744
|
* @param {Object} attrs
|
734
745
|
* @param {Object} options
|
746
|
+
* @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
|
735
747
|
* @constructor
|
736
748
|
* @memberof Meeting
|
737
749
|
*/
|
738
|
-
constructor(attrs: any, options: object) {
|
750
|
+
constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
|
739
751
|
super({}, options);
|
740
752
|
/**
|
741
753
|
* @instance
|
@@ -761,6 +773,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
761
773
|
* @memberof Meeting
|
762
774
|
*/
|
763
775
|
this.id = uuid.v4();
|
776
|
+
|
777
|
+
if (callback) {
|
778
|
+
callback(this);
|
779
|
+
}
|
780
|
+
|
764
781
|
/**
|
765
782
|
* Call state used for metrics
|
766
783
|
* @instance
|
@@ -2757,9 +2774,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
2757
2774
|
LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
|
2758
2775
|
({spokenLanguage}) => {
|
2759
2776
|
if (spokenLanguage) {
|
2760
|
-
this.transcription
|
2777
|
+
if (this.transcription?.languageOptions) {
|
2778
|
+
this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
|
2779
|
+
}
|
2761
2780
|
// @ts-ignore
|
2762
|
-
this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
|
2781
|
+
this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage, this.id);
|
2763
2782
|
|
2764
2783
|
Trigger.trigger(
|
2765
2784
|
this,
|
@@ -2768,7 +2787,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
2768
2787
|
function: 'setupLocusControlsListener',
|
2769
2788
|
},
|
2770
2789
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
|
2771
|
-
{spokenLanguage}
|
2790
|
+
{spokenLanguage, meetingId: this.id}
|
2772
2791
|
);
|
2773
2792
|
}
|
2774
2793
|
}
|
@@ -3850,15 +3869,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
3850
3869
|
}
|
3851
3870
|
|
3852
3871
|
/**
|
3853
|
-
* Cancel an SIP call invitation made during a meeting
|
3872
|
+
* Cancel an SIP/phone call invitation made during a meeting
|
3854
3873
|
* @param {Object} invitee
|
3855
3874
|
* @param {String} invitee.memberId
|
3856
|
-
* @
|
3875
|
+
* @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
|
3876
|
+
* @returns {Promise} see #members.cancelInviteByMemberId
|
3857
3877
|
* @public
|
3858
3878
|
* @memberof Meeting
|
3859
3879
|
*/
|
3860
|
-
public
|
3861
|
-
return this.members.
|
3880
|
+
public cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
|
3881
|
+
return this.members.cancelInviteByMemberId(invitee);
|
3862
3882
|
}
|
3863
3883
|
|
3864
3884
|
/**
|
@@ -7754,8 +7774,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7754
7774
|
shareVideoEnabled = true,
|
7755
7775
|
remoteMediaManagerConfig,
|
7756
7776
|
bundlePolicy = 'max-bundle',
|
7777
|
+
additionalMediaOptions = {},
|
7757
7778
|
} = options;
|
7758
7779
|
|
7780
|
+
const {
|
7781
|
+
sendVideo: rawSendVideo,
|
7782
|
+
receiveVideo: rawReceiveVideo,
|
7783
|
+
sendAudio: rawSendAudio,
|
7784
|
+
receiveAudio: rawReceiveAudio,
|
7785
|
+
} = additionalMediaOptions;
|
7786
|
+
|
7787
|
+
const sendVideo = videoEnabled && (rawSendVideo ?? true);
|
7788
|
+
const receiveVideo = videoEnabled && (rawReceiveVideo ?? true);
|
7789
|
+
const sendAudio = audioEnabled && (rawSendAudio ?? true);
|
7790
|
+
const receiveAudio = audioEnabled && (rawReceiveAudio ?? true);
|
7791
|
+
|
7759
7792
|
this.allowMediaInLobby = options?.allowMediaInLobby;
|
7760
7793
|
|
7761
7794
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
@@ -7791,11 +7824,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7791
7824
|
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
|
7792
7825
|
// to avoid doing an extra SDP exchange when they are published for the first time
|
7793
7826
|
this.mediaProperties.setMediaDirection({
|
7794
|
-
sendAudio
|
7795
|
-
sendVideo
|
7827
|
+
sendAudio,
|
7828
|
+
sendVideo,
|
7796
7829
|
sendShare: false,
|
7797
|
-
receiveAudio
|
7798
|
-
receiveVideo
|
7830
|
+
receiveAudio,
|
7831
|
+
receiveVideo,
|
7799
7832
|
receiveShare: shareAudioEnabled || shareVideoEnabled,
|
7800
7833
|
});
|
7801
7834
|
|
@@ -9721,21 +9754,91 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
9721
9754
|
return total;
|
9722
9755
|
}, 0);
|
9723
9756
|
|
9757
|
+
const selectedSubnetFirstOctet = this.mediaServerIp?.split('.')[0];
|
9758
|
+
|
9724
9759
|
let isSubnetReachable = null;
|
9725
|
-
if (totalSuccessCases > 0) {
|
9726
|
-
|
9727
|
-
|
9760
|
+
if (totalSuccessCases > 0 && selectedSubnetFirstOctet) {
|
9761
|
+
isSubnetReachable =
|
9762
|
+
// @ts-ignore
|
9763
|
+
this.webex.meetings.reachability.isSubnetReachable(selectedSubnetFirstOctet);
|
9728
9764
|
}
|
9729
9765
|
|
9730
|
-
|
9731
|
-
if (this.mediaConnections && this.mediaConnections.length > 0) {
|
9732
|
-
selectedCluster = this.mediaConnections[0].mediaAgentCluster;
|
9733
|
-
}
|
9766
|
+
const selectedCluster = this.mediaConnections?.[0]?.mediaAgentCluster ?? null;
|
9734
9767
|
|
9735
9768
|
return {
|
9736
9769
|
...reachabilityMetrics,
|
9737
|
-
isSubnetReachable,
|
9738
|
-
selectedCluster,
|
9770
|
+
subnet_reachable: isSubnetReachable,
|
9771
|
+
selected_cluster: selectedCluster,
|
9772
|
+
selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
|
9773
|
+
};
|
9774
|
+
}
|
9775
|
+
|
9776
|
+
/**
|
9777
|
+
* Set the stage for the meeting
|
9778
|
+
*
|
9779
|
+
* @param {SetStageOptions} options Options to use when setting the stage
|
9780
|
+
* @returns {Promise} The locus request
|
9781
|
+
*/
|
9782
|
+
setStage({
|
9783
|
+
activeSpeakerProportion = 0.5,
|
9784
|
+
customBackground,
|
9785
|
+
customLogo,
|
9786
|
+
customNameLabel,
|
9787
|
+
importantParticipants,
|
9788
|
+
lockAttendeeViewOnStage = false,
|
9789
|
+
showActiveSpeaker = false,
|
9790
|
+
}: SetStageOptions = {}) {
|
9791
|
+
const videoLayout: SetStageVideoLayout = {
|
9792
|
+
overrideDefault: true,
|
9793
|
+
lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
|
9794
|
+
stageParameters: {
|
9795
|
+
activeSpeakerProportion,
|
9796
|
+
showActiveSpeaker: {show: showActiveSpeaker, order: 0},
|
9797
|
+
stageManagerType: 0,
|
9798
|
+
},
|
9739
9799
|
};
|
9800
|
+
|
9801
|
+
if (importantParticipants?.length) {
|
9802
|
+
videoLayout.stageParameters.importantParticipants = importantParticipants.map(
|
9803
|
+
(importantParticipant, index) => ({...importantParticipant, order: index + 1})
|
9804
|
+
);
|
9805
|
+
}
|
9806
|
+
|
9807
|
+
if (customLogo) {
|
9808
|
+
if (!videoLayout.customLayouts) {
|
9809
|
+
videoLayout.customLayouts = {};
|
9810
|
+
}
|
9811
|
+
videoLayout.customLayouts.logo = customLogo;
|
9812
|
+
// eslint-disable-next-line no-bitwise
|
9813
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
|
9814
|
+
}
|
9815
|
+
|
9816
|
+
if (customBackground) {
|
9817
|
+
if (!videoLayout.customLayouts) {
|
9818
|
+
videoLayout.customLayouts = {};
|
9819
|
+
}
|
9820
|
+
videoLayout.customLayouts.background = customBackground;
|
9821
|
+
// eslint-disable-next-line no-bitwise
|
9822
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
|
9823
|
+
}
|
9824
|
+
|
9825
|
+
if (customNameLabel) {
|
9826
|
+
videoLayout.nameLabelStyle = customNameLabel;
|
9827
|
+
// eslint-disable-next-line no-bitwise
|
9828
|
+
videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
|
9829
|
+
}
|
9830
|
+
|
9831
|
+
return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
|
9832
|
+
}
|
9833
|
+
|
9834
|
+
/**
|
9835
|
+
* Unset the stage for the meeting
|
9836
|
+
*
|
9837
|
+
* @returns {Promise} The locus request
|
9838
|
+
*/
|
9839
|
+
unsetStage() {
|
9840
|
+
const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
|
9841
|
+
|
9842
|
+
return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
|
9740
9843
|
}
|
9741
9844
|
}
|
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/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
@@ -846,12 +846,14 @@ export default class Members extends StatelessWebexPlugin {
|
|
846
846
|
}
|
847
847
|
|
848
848
|
/**
|
849
|
-
* Cancels an SIP call to the associated meeting
|
850
|
-
* @param {
|
849
|
+
* Cancels an SIP/phone call to the associated meeting
|
850
|
+
* @param {Object} invitee
|
851
|
+
* @param {String} invitee.memberId - The memberId of the invitee
|
852
|
+
* @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
|
851
853
|
* @returns {Promise}
|
852
854
|
* @memberof Members
|
853
855
|
*/
|
854
|
-
|
856
|
+
cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
|
855
857
|
if (!this.locusUrl) {
|
856
858
|
return Promise.reject(
|
857
859
|
new ParameterError('The associated locus url for this meeting object must be defined.')
|
@@ -862,9 +864,9 @@ export default class Members extends StatelessWebexPlugin {
|
|
862
864
|
new ParameterError('The invitee must be defined with a memberId property.')
|
863
865
|
);
|
864
866
|
}
|
865
|
-
const options = MembersUtil.
|
867
|
+
const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
|
866
868
|
|
867
|
-
return this.membersRequest.
|
869
|
+
return this.membersRequest.cancelInviteByMemberId(options);
|
868
870
|
}
|
869
871
|
|
870
872
|
/**
|
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
|
}
|