@webex/plugin-meetings 3.0.0 → 3.1.0
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/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +5 -4
- package/dist/constants.js +8 -4
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +16 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/selfUtils.js +5 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.d.ts +61 -0
- package/dist/media/MediaConnectionAwaiter.js +163 -0
- package/dist/media/MediaConnectionAwaiter.js.map +1 -0
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +4 -24
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.d.ts +26 -7
- package/dist/meeting/index.js +893 -677
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.d.ts +2 -8
- package/dist/meeting/muteState.js +37 -25
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.d.ts +3 -0
- package/dist/meeting/request.js +32 -23
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +4 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.d.ts +8 -0
- package/dist/meetings/index.js +20 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.d.ts +2 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.d.ts +2 -0
- package/dist/multistream/remoteMediaGroup.js +16 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.d.ts +15 -0
- package/dist/multistream/remoteMediaManager.js +179 -65
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.d.ts +9 -1
- package/dist/multistream/sendSlotManager.js +22 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/clusterReachability.d.ts +1 -0
- package/dist/reachability/clusterReachability.js +29 -15
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.d.ts +4 -0
- package/dist/reachability/index.js +18 -2
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +12 -10
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.d.ts +7 -0
- package/dist/reachability/util.js +19 -0
- package/dist/reachability/util.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.d.ts +10 -2
- package/dist/roap/index.js +15 -0
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +3 -3
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +64 -17
- package/dist/roap/turnDiscovery.js +307 -126
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/index.js +53 -30
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/config.ts +1 -0
- package/src/constants.ts +7 -3
- package/src/index.ts +1 -0
- package/src/interpretation/index.ts +18 -1
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/locus-info/selfUtils.ts +5 -0
- package/src/media/MediaConnectionAwaiter.ts +174 -0
- package/src/media/index.ts +3 -1
- package/src/media/properties.ts +6 -31
- package/src/meeting/index.ts +321 -106
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +18 -2
- package/src/meeting/util.ts +1 -0
- package/src/meeting-info/utilv2.ts +2 -1
- package/src/meetings/index.ts +18 -0
- package/src/multistream/mediaRequestManager.ts +4 -1
- package/src/multistream/remoteMediaGroup.ts +19 -0
- package/src/multistream/remoteMediaManager.ts +101 -16
- package/src/multistream/sendSlotManager.ts +28 -0
- package/src/reachability/clusterReachability.ts +20 -5
- package/src/reachability/index.ts +24 -1
- package/src/reachability/request.ts +15 -11
- package/src/reachability/util.ts +21 -0
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +25 -3
- package/src/roap/request.ts +3 -3
- package/src/roap/turnDiscovery.ts +244 -78
- package/src/statsAnalyzer/index.ts +63 -27
- package/test/integration/spec/journey.js +14 -14
- package/test/integration/spec/space-meeting.js +1 -1
- package/test/unit/spec/interpretation/index.ts +39 -3
- package/test/unit/spec/locus-info/index.js +28 -19
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
- package/test/unit/spec/locus-info/selfUtils.js +42 -12
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
- package/test/unit/spec/media/index.ts +89 -78
- package/test/unit/spec/media/properties.ts +16 -70
- package/test/unit/spec/meeting/index.js +638 -139
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +6 -1
- package/test/unit/spec/meeting-info/utilv2.js +6 -0
- package/test/unit/spec/meetings/index.js +40 -20
- package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +79 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +199 -1
- package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
- package/test/unit/spec/reachability/clusterReachability.ts +86 -22
- package/test/unit/spec/reachability/index.ts +197 -60
- package/test/unit/spec/reachability/request.js +15 -7
- package/test/unit/spec/reachability/util.ts +32 -2
- package/test/unit/spec/reconnection-manager/index.js +28 -0
- package/test/unit/spec/roap/index.ts +61 -6
- package/test/unit/spec/roap/turnDiscovery.ts +298 -16
- package/test/unit/spec/stats-analyzer/index.js +179 -0
- package/dist/member/member.types.d.ts +0 -11
- package/dist/member/member.types.js +0 -17
- package/dist/member/member.types.js.map +0 -1
- package/src/member/member.types.ts +0 -13
- /package/test/unit/spec/locus-info/{lib/selfConstant.js → selfConstant.js} +0 -0
package/src/meeting/muteState.ts
CHANGED
|
@@ -150,15 +150,30 @@ export class MuteState {
|
|
|
150
150
|
* @param {Boolean} [mute] true for muting, false for unmuting request
|
|
151
151
|
* @returns {void}
|
|
152
152
|
*/
|
|
153
|
-
public handleLocalStreamMuteStateChange(meeting?:
|
|
153
|
+
public handleLocalStreamMuteStateChange(meeting?: any) {
|
|
154
154
|
if (this.ignoreMuteStateChange) {
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
// either user or system may have triggered a mute state change, but localMute should reflect both
|
|
159
|
+
let newMuteState: boolean;
|
|
160
|
+
let userMuteState: boolean;
|
|
161
|
+
let systemMuteState: boolean;
|
|
162
|
+
if (this.type === AUDIO) {
|
|
163
|
+
newMuteState = meeting.mediaProperties.audioStream?.muted;
|
|
164
|
+
userMuteState = meeting.mediaProperties.audioStream?.userMuted;
|
|
165
|
+
systemMuteState = meeting.mediaProperties.audioStream?.systemMuted;
|
|
166
|
+
} else {
|
|
167
|
+
newMuteState = meeting.mediaProperties.videoStream?.muted;
|
|
168
|
+
userMuteState = meeting.mediaProperties.videoStream?.userMuted;
|
|
169
|
+
systemMuteState = meeting.mediaProperties.videoStream?.systemMuted;
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
LoggerProxy.logger.info(
|
|
158
|
-
`Meeting:muteState#handleLocalStreamMuteStateChange --> ${this.type}: local stream new mute state: ${mute}`
|
|
173
|
+
`Meeting:muteState#handleLocalStreamMuteStateChange --> ${this.type}: local stream new mute state: ${newMuteState} (user mute: ${userMuteState}, system mute: ${systemMuteState})`
|
|
159
174
|
);
|
|
160
175
|
|
|
161
|
-
this.state.client.localMute =
|
|
176
|
+
this.state.client.localMute = newMuteState;
|
|
162
177
|
|
|
163
178
|
this.applyClientStateToServer(meeting);
|
|
164
179
|
}
|
|
@@ -249,7 +264,12 @@ export class MuteState {
|
|
|
249
264
|
`Meeting:muteState#applyClientStateToServer --> ${this.type}: error: ${e}`
|
|
250
265
|
);
|
|
251
266
|
|
|
252
|
-
|
|
267
|
+
// failed to apply client state to server, so revert stream mute state to server state
|
|
268
|
+
this.muteLocalStream(
|
|
269
|
+
meeting,
|
|
270
|
+
this.state.server.localMute || this.state.server.remoteMute,
|
|
271
|
+
'clientRequestFailed'
|
|
272
|
+
);
|
|
253
273
|
});
|
|
254
274
|
}
|
|
255
275
|
|
|
@@ -325,18 +345,6 @@ export class MuteState {
|
|
|
325
345
|
});
|
|
326
346
|
}
|
|
327
347
|
|
|
328
|
-
/** Sets the mute state of the local stream according to what server thinks is our state
|
|
329
|
-
* @param {Object} meeting - the meeting object
|
|
330
|
-
* @param {ServerMuteReason} serverMuteReason - reason why we're applying server mute to the local stream
|
|
331
|
-
* @returns {void}
|
|
332
|
-
*/
|
|
333
|
-
private applyServerMuteToLocalStream(meeting: any, serverMuteReason: ServerMuteReason) {
|
|
334
|
-
const muted = this.state.server.localMute || this.state.server.remoteMute;
|
|
335
|
-
|
|
336
|
-
// update the local stream mute state, but not this.state.client.localMute
|
|
337
|
-
this.muteLocalStream(meeting, muted, serverMuteReason);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
348
|
/** Applies the current value for unmute allowed to the underlying stream
|
|
341
349
|
*
|
|
342
350
|
* @param {Meeting} meeting
|
|
@@ -371,7 +379,7 @@ export class MuteState {
|
|
|
371
379
|
}
|
|
372
380
|
if (muted !== undefined) {
|
|
373
381
|
this.state.server.remoteMute = muted;
|
|
374
|
-
this.
|
|
382
|
+
this.muteLocalStream(meeting, muted, 'remotelyMuted');
|
|
375
383
|
}
|
|
376
384
|
}
|
|
377
385
|
|
|
@@ -383,7 +391,7 @@ export class MuteState {
|
|
|
383
391
|
* @param {Object} [meeting] the meeting object
|
|
384
392
|
* @returns {undefined}
|
|
385
393
|
*/
|
|
386
|
-
public handleServerLocalUnmuteRequired(meeting?:
|
|
394
|
+
public handleServerLocalUnmuteRequired(meeting?: any) {
|
|
387
395
|
if (!this.state.client.enabled) {
|
|
388
396
|
LoggerProxy.logger.warn(
|
|
389
397
|
`Meeting:muteState#handleServerLocalUnmuteRequired --> ${this.type}: localAudioUnmuteRequired received while ${this.type} is disabled -> local unmute will not result in ${this.type} being sent`
|
|
@@ -396,9 +404,15 @@ export class MuteState {
|
|
|
396
404
|
|
|
397
405
|
// todo: I'm seeing "you can now unmute yourself " popup when this happens - but same thing happens on web.w.c so we can ignore for now
|
|
398
406
|
this.state.server.remoteMute = false;
|
|
399
|
-
this.state.client.localMute = false;
|
|
400
407
|
|
|
401
|
-
|
|
408
|
+
// change user mute state to false, but keep localMute true if overall mute state is still true
|
|
409
|
+
this.muteLocalStream(meeting, false, 'localUnmuteRequired');
|
|
410
|
+
if (this.type === AUDIO) {
|
|
411
|
+
this.state.client.localMute = meeting.mediaProperties.audioStream?.muted;
|
|
412
|
+
} else {
|
|
413
|
+
this.state.client.localMute = meeting.mediaProperties.videoStream?.muted;
|
|
414
|
+
}
|
|
415
|
+
|
|
402
416
|
this.applyClientStateToServer(meeting);
|
|
403
417
|
}
|
|
404
418
|
|
package/src/meeting/request.ts
CHANGED
|
@@ -103,6 +103,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
103
103
|
* @param {String} options.locale,
|
|
104
104
|
* @param {Array} options.deviceCapabilities
|
|
105
105
|
* @param {boolean} options.liveAnnotationSupported
|
|
106
|
+
* @param {String} options.alias
|
|
106
107
|
* @returns {Promise}
|
|
107
108
|
*/
|
|
108
109
|
async joinMeeting(options: {
|
|
@@ -122,11 +123,13 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
122
123
|
meetingNumber: any;
|
|
123
124
|
permissionToken: any;
|
|
124
125
|
preferTranscoding: any;
|
|
126
|
+
reachability: any;
|
|
125
127
|
breakoutsSupported: boolean;
|
|
126
128
|
locale?: string;
|
|
127
129
|
deviceCapabilities?: Array<string>;
|
|
128
130
|
liveAnnotationSupported: boolean;
|
|
129
131
|
ipVersion?: IP_VERSION;
|
|
132
|
+
alias?: string;
|
|
130
133
|
}) {
|
|
131
134
|
const {
|
|
132
135
|
asResourceOccupant,
|
|
@@ -143,12 +146,14 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
143
146
|
pin,
|
|
144
147
|
moveToResource,
|
|
145
148
|
roapMessage,
|
|
149
|
+
reachability,
|
|
146
150
|
preferTranscoding,
|
|
147
151
|
breakoutsSupported,
|
|
148
152
|
locale,
|
|
149
153
|
deviceCapabilities = [],
|
|
150
154
|
liveAnnotationSupported,
|
|
151
155
|
ipVersion,
|
|
156
|
+
alias,
|
|
152
157
|
} = options;
|
|
153
158
|
|
|
154
159
|
LoggerProxy.logger.info('Meeting:request#joinMeeting --> Joining a meeting', correlationId);
|
|
@@ -178,6 +183,10 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
178
183
|
},
|
|
179
184
|
};
|
|
180
185
|
|
|
186
|
+
if (alias) {
|
|
187
|
+
body.alias = alias;
|
|
188
|
+
}
|
|
189
|
+
|
|
181
190
|
if (breakoutsSupported) {
|
|
182
191
|
deviceCapabilities.push(BREAKOUTS.BREAKOUTS_SUPPORTED);
|
|
183
192
|
}
|
|
@@ -260,8 +269,15 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
260
269
|
};
|
|
261
270
|
}
|
|
262
271
|
|
|
263
|
-
if (roapMessage) {
|
|
264
|
-
body.localMedias =
|
|
272
|
+
if (roapMessage || reachability) {
|
|
273
|
+
body.localMedias = [
|
|
274
|
+
{
|
|
275
|
+
localSdp: JSON.stringify({
|
|
276
|
+
roapMessage,
|
|
277
|
+
reachability,
|
|
278
|
+
}),
|
|
279
|
+
},
|
|
280
|
+
];
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
/// @ts-ignore
|
package/src/meeting/util.ts
CHANGED
|
@@ -149,6 +149,7 @@ const MeetingUtil = {
|
|
|
149
149
|
locusUrl: meeting.locusUrl,
|
|
150
150
|
locusClusterUrl: meeting.meetingInfo?.locusClusterUrl,
|
|
151
151
|
correlationId: meeting.correlationId,
|
|
152
|
+
reachability: options.reachability,
|
|
152
153
|
roapMessage: options.roapMessage,
|
|
153
154
|
permissionToken: meeting.permissionToken,
|
|
154
155
|
resourceId: options.resourceId || null,
|
|
@@ -292,8 +292,9 @@ MeetingInfoUtil.getRequestBody = (options: {type: string; destination: object} |
|
|
|
292
292
|
MeetingInfoUtil.getWebexSite = (uri: string) => {
|
|
293
293
|
const exceptedDomains = ['meet.webex.com', 'meetup.webex.com', 'ciscospark.com'];
|
|
294
294
|
const site = uri?.match(/.+@([^.]+\.[^.]+\.[^.]+)$/)?.[1];
|
|
295
|
+
const isExceptedDomain = !!site && exceptedDomains.some((domain) => site.includes(domain));
|
|
295
296
|
|
|
296
|
-
return
|
|
297
|
+
return isExceptedDomain ? null : site;
|
|
297
298
|
};
|
|
298
299
|
|
|
299
300
|
/**
|
package/src/meetings/index.ts
CHANGED
|
@@ -717,6 +717,24 @@ export default class Meetings extends WebexPlugin {
|
|
|
717
717
|
}
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
/**
|
|
721
|
+
* API to toggle TLS reachability, needs to be called before webex.meetings.register()
|
|
722
|
+
* @param {Boolean} newValue
|
|
723
|
+
* @private
|
|
724
|
+
* @memberof Meetings
|
|
725
|
+
* @returns {undefined}
|
|
726
|
+
*/
|
|
727
|
+
private _toggleTlsReachability(newValue: boolean) {
|
|
728
|
+
if (typeof newValue !== 'boolean') {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// @ts-ignore
|
|
732
|
+
if (this.config.experimental.enableTlsReachability !== newValue) {
|
|
733
|
+
// @ts-ignore
|
|
734
|
+
this.config.experimental.enableTlsReachability = newValue;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
720
738
|
/**
|
|
721
739
|
* Explicitly sets up the meetings plugin by registering
|
|
722
740
|
* the device, connecting to mercury, and listening for locus events.
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
H264Codec,
|
|
9
9
|
getRecommendedMaxBitrateForFrameSize,
|
|
10
10
|
RecommendedOpusBitrates,
|
|
11
|
+
NamedMediaGroup,
|
|
11
12
|
} from '@webex/internal-media-core';
|
|
12
13
|
import {cloneDeepWith, debounce, isEmpty} from 'lodash';
|
|
13
14
|
|
|
@@ -22,6 +23,7 @@ export interface ActiveSpeakerPolicyInfo {
|
|
|
22
23
|
crossPriorityDuplication: boolean;
|
|
23
24
|
crossPolicyDuplication: boolean;
|
|
24
25
|
preferLiveVideo: boolean;
|
|
26
|
+
namedMediaGroups?: NamedMediaGroup[];
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export interface ReceiverSelectedPolicyInfo {
|
|
@@ -347,7 +349,8 @@ export class MediaRequestManager {
|
|
|
347
349
|
mr.policyInfo.priority,
|
|
348
350
|
mr.policyInfo.crossPriorityDuplication,
|
|
349
351
|
mr.policyInfo.crossPolicyDuplication,
|
|
350
|
-
mr.policyInfo.preferLiveVideo
|
|
352
|
+
mr.policyInfo.preferLiveVideo,
|
|
353
|
+
mr.policyInfo.namedMediaGroups
|
|
351
354
|
)
|
|
352
355
|
: new ReceiverSelectedInfo(mr.policyInfo.csi),
|
|
353
356
|
mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* eslint-disable require-jsdoc */
|
|
3
3
|
/* eslint-disable import/prefer-default-export */
|
|
4
4
|
import {forEach} from 'lodash';
|
|
5
|
+
import {NamedMediaGroup} from '@webex/internal-media-core';
|
|
5
6
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
6
7
|
|
|
7
8
|
import {getMaxFs, RemoteMedia, RemoteVideoResolution} from './remoteMedia';
|
|
@@ -11,6 +12,7 @@ import {CSI, ReceiveSlot} from './receiveSlot';
|
|
|
11
12
|
type Options = {
|
|
12
13
|
resolution?: RemoteVideoResolution; // applies only to groups of type MediaType.VideoMain and MediaType.VideoSlides
|
|
13
14
|
preferLiveVideo?: boolean; // applies only to groups of type MediaType.VideoMain and MediaType.VideoSlides
|
|
15
|
+
namedMediaGroup?: NamedMediaGroup; // applies only to named media groups for audio
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export class RemoteMediaGroup {
|
|
@@ -221,6 +223,9 @@ export class RemoteMediaGroup {
|
|
|
221
223
|
crossPriorityDuplication: false,
|
|
222
224
|
crossPolicyDuplication: false,
|
|
223
225
|
preferLiveVideo: !!this.options?.preferLiveVideo,
|
|
226
|
+
namedMediaGroups: this.options.namedMediaGroup?.value
|
|
227
|
+
? [this.options?.namedMediaGroup]
|
|
228
|
+
: undefined,
|
|
224
229
|
},
|
|
225
230
|
receiveSlots: this.unpinnedRemoteMedia.map((remoteMedia) =>
|
|
226
231
|
remoteMedia.getUnderlyingReceiveSlot()
|
|
@@ -241,6 +246,20 @@ export class RemoteMediaGroup {
|
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
|
249
|
+
/**
|
|
250
|
+
* setNamedMediaGroup - sets named media group type and value
|
|
251
|
+
* @internal
|
|
252
|
+
*/
|
|
253
|
+
public setNamedMediaGroup(namedMediaGroup: NamedMediaGroup, commit: boolean) {
|
|
254
|
+
if (
|
|
255
|
+
this.options.namedMediaGroup.value !== namedMediaGroup.value ||
|
|
256
|
+
this.options.namedMediaGroup.type !== namedMediaGroup.type
|
|
257
|
+
) {
|
|
258
|
+
this.options.namedMediaGroup = namedMediaGroup;
|
|
259
|
+
this.sendActiveSpeakerMediaRequest(commit);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
244
263
|
/**
|
|
245
264
|
* Invalidates the remote media group by clearing the references to the receive slots
|
|
246
265
|
* used by all remote media from that group and cancelling all media requests.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable valid-jsdoc */
|
|
2
2
|
import {cloneDeep, forEach, remove} from 'lodash';
|
|
3
3
|
import {EventMap} from 'typed-emitter';
|
|
4
|
-
import {MediaType} from '@webex/internal-media-core';
|
|
4
|
+
import {MediaType, NamedMediaGroup} from '@webex/internal-media-core';
|
|
5
5
|
|
|
6
6
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
7
|
import EventsScope from '../common/events/events-scope';
|
|
@@ -11,6 +11,7 @@ import {ReceiveSlot, CSI} from './receiveSlot';
|
|
|
11
11
|
import {ReceiveSlotManager} from './receiveSlotManager';
|
|
12
12
|
import {RemoteMediaGroup} from './remoteMediaGroup';
|
|
13
13
|
import {MediaRequestManager} from './mediaRequestManager';
|
|
14
|
+
import {NAMED_MEDIA_GROUP_TYPE_AUDIO} from '../constants';
|
|
14
15
|
|
|
15
16
|
export type PaneSize = RemoteVideoResolution;
|
|
16
17
|
export type LayoutId = string;
|
|
@@ -49,6 +50,7 @@ export interface Configuration {
|
|
|
49
50
|
|
|
50
51
|
layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
|
|
51
52
|
};
|
|
53
|
+
namedMediaGroup?: NamedMediaGroup;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/* Predefined layouts: */
|
|
@@ -173,6 +175,7 @@ export const DefaultConfiguration: Configuration = {
|
|
|
173
175
|
export enum Event {
|
|
174
176
|
// events for audio streams
|
|
175
177
|
AudioCreated = 'AudioCreated',
|
|
178
|
+
InterpretationAudioCreated = 'InterpretationAudioCreated',
|
|
176
179
|
ScreenShareAudioCreated = 'ScreenShareAudioCreated',
|
|
177
180
|
|
|
178
181
|
// events for video streams
|
|
@@ -221,7 +224,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
221
224
|
private currentLayout?: VideoLayout;
|
|
222
225
|
|
|
223
226
|
private slots: {
|
|
224
|
-
audio:
|
|
227
|
+
audio: {
|
|
228
|
+
main: ReceiveSlot[];
|
|
229
|
+
si: ReceiveSlot;
|
|
230
|
+
};
|
|
225
231
|
screenShare: {
|
|
226
232
|
audio: ReceiveSlot[];
|
|
227
233
|
video?: ReceiveSlot;
|
|
@@ -234,7 +240,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
234
240
|
};
|
|
235
241
|
|
|
236
242
|
private media: {
|
|
237
|
-
audio
|
|
243
|
+
audio: {
|
|
244
|
+
main?: RemoteMediaGroup;
|
|
245
|
+
si?: RemoteMediaGroup;
|
|
246
|
+
};
|
|
238
247
|
video: {
|
|
239
248
|
activeSpeakerGroups: {
|
|
240
249
|
[key: PaneGroupId]: RemoteMediaGroup;
|
|
@@ -277,7 +286,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
277
286
|
this.receiveSlotManager = receiveSlotManager;
|
|
278
287
|
this.mediaRequestManagers = mediaRequestManagers;
|
|
279
288
|
this.media = {
|
|
280
|
-
audio:
|
|
289
|
+
audio: {
|
|
290
|
+
main: undefined,
|
|
291
|
+
si: undefined,
|
|
292
|
+
},
|
|
281
293
|
video: {
|
|
282
294
|
activeSpeakerGroups: {},
|
|
283
295
|
memberPanes: {},
|
|
@@ -291,7 +303,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
291
303
|
this.checkConfigValidity();
|
|
292
304
|
|
|
293
305
|
this.slots = {
|
|
294
|
-
audio:
|
|
306
|
+
audio: {
|
|
307
|
+
main: [],
|
|
308
|
+
si: undefined,
|
|
309
|
+
},
|
|
295
310
|
screenShare: {
|
|
296
311
|
audio: [],
|
|
297
312
|
video: undefined,
|
|
@@ -389,8 +404,11 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
389
404
|
});
|
|
390
405
|
|
|
391
406
|
// release all audio receive slots
|
|
392
|
-
this.slots.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
393
|
-
this.slots.audio.length = 0;
|
|
407
|
+
this.slots.audio.main.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
408
|
+
this.slots.audio.main.length = 0;
|
|
409
|
+
if (this.slots.audio.si) {
|
|
410
|
+
this.receiveSlotManager.releaseSlot(this.slots.audio.si);
|
|
411
|
+
}
|
|
394
412
|
|
|
395
413
|
// release screen share slots
|
|
396
414
|
this.slots.screenShare.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
@@ -525,22 +543,54 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
525
543
|
this.mediaRequestManagers.video.commit();
|
|
526
544
|
}
|
|
527
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Sets which named media group need receiving
|
|
548
|
+
* @param {MediaType} mediaType of the stream
|
|
549
|
+
* @param {number} languageCode of the stream. If the languageId is 0, the named media group request will be canceled,
|
|
550
|
+
* and only receive the main audio stream.
|
|
551
|
+
* @returns {void}
|
|
552
|
+
*/
|
|
553
|
+
public async setReceiveNamedMediaGroup(mediaType: MediaType, languageId: number) {
|
|
554
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
555
|
+
throw new Error(`cannot set receive named media group which media type is ${mediaType}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const value = languageId;
|
|
559
|
+
if (value === this.config.namedMediaGroup?.value) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.config.namedMediaGroup = {
|
|
564
|
+
type: NAMED_MEDIA_GROUP_TYPE_AUDIO,
|
|
565
|
+
value,
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
if (!this.media.audio.si) {
|
|
569
|
+
await this.createInterpretationAudioMedia(true);
|
|
570
|
+
} else {
|
|
571
|
+
this.media.audio.si.setNamedMediaGroup(this.config.namedMediaGroup, true);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
528
575
|
/**
|
|
529
576
|
* Creates the audio slots
|
|
530
577
|
*/
|
|
531
578
|
private async createAudioMedia() {
|
|
532
|
-
// create
|
|
579
|
+
// create si audio request
|
|
580
|
+
await this.createInterpretationAudioMedia(false);
|
|
581
|
+
|
|
582
|
+
// create main audio receive slots
|
|
533
583
|
for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
|
|
534
584
|
// eslint-disable-next-line no-await-in-loop
|
|
535
585
|
const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
536
586
|
|
|
537
|
-
this.slots.audio.push(slot);
|
|
587
|
+
this.slots.audio.main.push(slot);
|
|
538
588
|
}
|
|
539
589
|
|
|
540
|
-
// create a remote media group
|
|
541
|
-
this.media.audio = new RemoteMediaGroup(
|
|
590
|
+
// create a remote media group for main audio
|
|
591
|
+
this.media.audio.main = new RemoteMediaGroup(
|
|
542
592
|
this.mediaRequestManagers.audio,
|
|
543
|
-
this.slots.audio,
|
|
593
|
+
this.slots.audio.main,
|
|
544
594
|
255,
|
|
545
595
|
true
|
|
546
596
|
);
|
|
@@ -548,10 +598,40 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
548
598
|
this.emit(
|
|
549
599
|
{file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
|
|
550
600
|
Event.AudioCreated,
|
|
551
|
-
this.media.audio
|
|
601
|
+
this.media.audio.main
|
|
552
602
|
);
|
|
553
603
|
}
|
|
554
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Creates the audio slots for named media
|
|
607
|
+
*/
|
|
608
|
+
private async createInterpretationAudioMedia(commitRequest: boolean) {
|
|
609
|
+
// create slot for interpretation language audio
|
|
610
|
+
if (
|
|
611
|
+
this.config.namedMediaGroup?.type === NAMED_MEDIA_GROUP_TYPE_AUDIO &&
|
|
612
|
+
this.config.namedMediaGroup?.value
|
|
613
|
+
) {
|
|
614
|
+
this.slots.audio.si = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
615
|
+
|
|
616
|
+
// create a remote media group for si audio
|
|
617
|
+
this.media.audio.si = new RemoteMediaGroup(
|
|
618
|
+
this.mediaRequestManagers.audio,
|
|
619
|
+
[this.slots.audio.si],
|
|
620
|
+
255,
|
|
621
|
+
commitRequest,
|
|
622
|
+
{
|
|
623
|
+
namedMediaGroup: this.config.namedMediaGroup,
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
this.emit(
|
|
628
|
+
{file: 'multistream/remoteMediaManager', function: 'createInterpretationAudioMedia'},
|
|
629
|
+
Event.InterpretationAudioCreated,
|
|
630
|
+
this.media.audio.si
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
555
635
|
/**
|
|
556
636
|
* Creates receive slots required for receiving screen share audio and video
|
|
557
637
|
*/
|
|
@@ -748,7 +828,7 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
748
828
|
/** logs main audio slots */
|
|
749
829
|
private logMainAudioReceiveSlots() {
|
|
750
830
|
LoggerProxy.logger.log(
|
|
751
|
-
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
|
|
831
|
+
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio.main
|
|
752
832
|
.map((slot) => slot.logString)
|
|
753
833
|
.join(', ')}`
|
|
754
834
|
);
|
|
@@ -924,8 +1004,13 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
924
1004
|
}) {
|
|
925
1005
|
const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
|
|
926
1006
|
|
|
927
|
-
if (audio
|
|
928
|
-
this.media.audio.
|
|
1007
|
+
if (audio) {
|
|
1008
|
+
if (this.media.audio.main) {
|
|
1009
|
+
this.media.audio.main.stop(commit);
|
|
1010
|
+
}
|
|
1011
|
+
if (this.media.audio.si) {
|
|
1012
|
+
this.media.audio.si.stop(commit);
|
|
1013
|
+
}
|
|
929
1014
|
}
|
|
930
1015
|
if (video) {
|
|
931
1016
|
Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
MediaType,
|
|
4
4
|
LocalStream,
|
|
5
5
|
MultistreamRoapMediaConnection,
|
|
6
|
+
NamedMediaGroup,
|
|
6
7
|
} from '@webex/internal-media-core';
|
|
7
8
|
|
|
8
9
|
export default class SendSlotManager {
|
|
@@ -55,6 +56,33 @@ export default class SendSlotManager {
|
|
|
55
56
|
return slot;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Allow users to specify 'namedMediaGroups' to indicate which named media group its audio should be sent to.
|
|
61
|
+
* @param {MediaType} mediaType MediaType of the sendSlot to which the audio stream needs to be send to the media group
|
|
62
|
+
* @param {[]}namedMediaGroups - Allow users to specify 'namedMediaGroups'.If the value of 'namedMediaGroups' is zero,
|
|
63
|
+
* named media group will be canceled and the audio stream will be sent to the floor.
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
public setNamedMediaGroups(mediaType: MediaType, namedMediaGroups: NamedMediaGroup[]) {
|
|
67
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`sendSlotManager cannot set named media group which media type is ${mediaType}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const slot = this.slots.get(mediaType);
|
|
74
|
+
|
|
75
|
+
if (!slot) {
|
|
76
|
+
throw new Error(`Slot for ${mediaType} does not exist`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
slot.setNamedMediaGroups(namedMediaGroups);
|
|
80
|
+
|
|
81
|
+
this.LoggerProxy.logger.info(
|
|
82
|
+
`SendSlotsManager->setNamedMediaGroups#set named media group ${namedMediaGroups}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
/**
|
|
59
87
|
* This method publishes the given stream to the sendSlot for the given mediaType
|
|
60
88
|
* @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
|
|
@@ -2,7 +2,7 @@ import {Defer} from '@webex/common';
|
|
|
2
2
|
|
|
3
3
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
4
4
|
import {ClusterNode} from './request';
|
|
5
|
-
import {convertStunUrlToTurn} from './util';
|
|
5
|
+
import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
|
|
6
6
|
|
|
7
7
|
import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';
|
|
8
8
|
|
|
@@ -29,6 +29,7 @@ export type ClusterReachabilityResult = {
|
|
|
29
29
|
export class ClusterReachability {
|
|
30
30
|
private numUdpUrls: number;
|
|
31
31
|
private numTcpUrls: number;
|
|
32
|
+
private numXTlsUrls: number;
|
|
32
33
|
private result: ClusterReachabilityResult;
|
|
33
34
|
private pc?: RTCPeerConnection;
|
|
34
35
|
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
|
|
@@ -46,6 +47,7 @@ export class ClusterReachability {
|
|
|
46
47
|
this.isVideoMesh = clusterInfo.isVideoMesh;
|
|
47
48
|
this.numUdpUrls = clusterInfo.udp.length;
|
|
48
49
|
this.numTcpUrls = clusterInfo.tcp.length;
|
|
50
|
+
this.numXTlsUrls = clusterInfo.xtls.length;
|
|
49
51
|
|
|
50
52
|
this.pc = this.createPeerConnection(clusterInfo);
|
|
51
53
|
|
|
@@ -94,8 +96,16 @@ export class ClusterReachability {
|
|
|
94
96
|
};
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
const turnTlsIceServers = cluster.xtls.map((urlString: string) => {
|
|
100
|
+
return {
|
|
101
|
+
username: 'webexturnreachuser',
|
|
102
|
+
credential: 'webexturnreachpwd',
|
|
103
|
+
urls: [convertStunUrlToTurnTls(urlString)],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
97
107
|
return {
|
|
98
|
-
iceServers: [...udpIceServers, ...tcpIceServers],
|
|
108
|
+
iceServers: [...udpIceServers, ...tcpIceServers, ...turnTlsIceServers],
|
|
99
109
|
iceCandidatePoolSize: 0,
|
|
100
110
|
iceTransportPolicy: 'all',
|
|
101
111
|
};
|
|
@@ -194,7 +204,7 @@ export class ClusterReachability {
|
|
|
194
204
|
* @returns {boolean} true if we have all results, false otherwise
|
|
195
205
|
*/
|
|
196
206
|
private haveWeGotAllResults(): boolean {
|
|
197
|
-
return ['udp', 'tcp'].every(
|
|
207
|
+
return ['udp', 'tcp', 'xtls'].every(
|
|
198
208
|
(protocol) =>
|
|
199
209
|
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
|
|
200
210
|
);
|
|
@@ -207,7 +217,7 @@ export class ClusterReachability {
|
|
|
207
217
|
* @param {number} latency
|
|
208
218
|
* @returns {void}
|
|
209
219
|
*/
|
|
210
|
-
private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
|
|
220
|
+
private storeLatencyResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number) {
|
|
211
221
|
const result = this.result[protocol];
|
|
212
222
|
|
|
213
223
|
if (result.latencyInMilliseconds === undefined) {
|
|
@@ -227,6 +237,7 @@ export class ClusterReachability {
|
|
|
227
237
|
*/
|
|
228
238
|
private registerIceCandidateListener() {
|
|
229
239
|
this.pc.onicecandidate = (e) => {
|
|
240
|
+
const TURN_TLS_PORT = 443;
|
|
230
241
|
const CANDIDATE_TYPES = {
|
|
231
242
|
SERVER_REFLEXIVE: 'srflx',
|
|
232
243
|
RELAY: 'relay',
|
|
@@ -239,7 +250,8 @@ export class ClusterReachability {
|
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
|
242
|
-
|
|
253
|
+
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
|
|
254
|
+
this.storeLatencyResult(protocol, this.getElapsedTime());
|
|
243
255
|
// we don't add public IP for TCP, because in the case of relay candidates
|
|
244
256
|
// e.candidate.address is the TURN server address, not the client's public IP
|
|
245
257
|
}
|
|
@@ -275,6 +287,9 @@ export class ClusterReachability {
|
|
|
275
287
|
this.result.tcp = {
|
|
276
288
|
result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
|
|
277
289
|
};
|
|
290
|
+
this.result.xtls = {
|
|
291
|
+
result: this.numXTlsUrls > 0 ? 'unreachable' : 'untested',
|
|
292
|
+
};
|
|
278
293
|
|
|
279
294
|
try {
|
|
280
295
|
const offer = await this.pc.createOffer({offerToReceiveAudio: true});
|