@webex/plugin-meetings 3.0.0-beta.250 → 3.0.0-beta.252
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/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +30 -24
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/media/index.js +18 -19
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +52 -52
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +417 -372
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +39 -38
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/util.js +9 -9
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/index.js +45 -23
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +22 -4
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +4 -2
- package/dist/meetings/index.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +233 -0
- package/dist/multistream/sendSlotManager.js.map +1 -0
- package/dist/reconnection-manager/index.js +10 -10
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/media/index.d.ts +2 -2
- package/dist/types/media/properties.d.ts +24 -24
- package/dist/types/meeting/index.d.ts +61 -51
- package/dist/types/meeting/muteState.d.ts +16 -16
- package/dist/types/meeting/util.d.ts +2 -2
- package/dist/types/meeting-info/index.d.ts +7 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
- package/dist/types/multistream/sendSlotManager.d.ts +61 -0
- package/dist/types/reconnection-manager/index.d.ts +2 -2
- package/package.json +20 -20
- package/src/constants.ts +2 -0
- package/src/index.ts +14 -13
- package/src/media/index.ts +32 -34
- package/src/media/properties.ts +47 -46
- package/src/meeting/index.ts +402 -331
- package/src/meeting/muteState.ts +35 -34
- package/src/meeting/util.ts +11 -10
- package/src/meeting-info/index.ts +45 -20
- package/src/meeting-info/meeting-info-v2.ts +25 -5
- package/src/meetings/index.ts +9 -2
- package/src/multistream/sendSlotManager.ts +170 -0
- package/src/reconnection-manager/index.ts +8 -8
- package/test/integration/spec/converged-space-meetings.js +7 -7
- package/test/integration/spec/journey.js +85 -103
- package/test/integration/spec/space-meeting.js +9 -9
- package/test/unit/spec/media/index.ts +23 -66
- package/test/unit/spec/meeting/index.js +786 -848
- package/test/unit/spec/meeting/muteState.js +113 -75
- package/test/unit/spec/meeting/utils.js +14 -16
- package/test/unit/spec/meeting-info/index.js +173 -61
- package/test/unit/spec/meeting-info/meetinginfov2.js +186 -52
- package/test/unit/spec/meetings/index.js +6 -5
- package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -3
- package/test/utils/integrationTestUtils.js +4 -4
package/src/meeting/index.ts
CHANGED
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
getDevices,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
LocalStream,
|
|
24
|
+
LocalCameraStream,
|
|
25
|
+
LocalDisplayStream,
|
|
26
|
+
LocalSystemAudioStream,
|
|
27
|
+
LocalMicrophoneStream,
|
|
28
|
+
LocalStreamEventNames,
|
|
29
|
+
StreamEventNames,
|
|
30
|
+
RemoteStream,
|
|
30
31
|
} from '@webex/media-helpers';
|
|
31
32
|
|
|
32
33
|
import {
|
|
@@ -66,7 +67,6 @@ import {
|
|
|
66
67
|
AUDIO,
|
|
67
68
|
CONTENT,
|
|
68
69
|
DISPLAY_HINTS,
|
|
69
|
-
ENDED,
|
|
70
70
|
EVENT_TRIGGERS,
|
|
71
71
|
EVENT_TYPES,
|
|
72
72
|
EVENTS,
|
|
@@ -105,6 +105,7 @@ import {
|
|
|
105
105
|
} from '../meeting-info/meeting-info-v2';
|
|
106
106
|
import BrowserDetection from '../common/browser-detection';
|
|
107
107
|
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
108
|
+
import SendSlotManager from '../multistream/sendSlotManager';
|
|
108
109
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
109
110
|
import {
|
|
110
111
|
Configuration as RemoteMediaManagerConfiguration,
|
|
@@ -146,20 +147,21 @@ const logRequest = (request: any, {logText = ''}) => {
|
|
|
146
147
|
});
|
|
147
148
|
};
|
|
148
149
|
|
|
149
|
-
export type
|
|
150
|
-
microphone?:
|
|
151
|
-
camera?:
|
|
150
|
+
export type LocalStreams = {
|
|
151
|
+
microphone?: LocalMicrophoneStream;
|
|
152
|
+
camera?: LocalCameraStream;
|
|
152
153
|
screenShare?: {
|
|
153
|
-
audio?:
|
|
154
|
-
video?:
|
|
154
|
+
audio?: LocalSystemAudioStream;
|
|
155
|
+
video?: LocalDisplayStream;
|
|
155
156
|
};
|
|
156
157
|
};
|
|
157
158
|
|
|
158
159
|
export type AddMediaOptions = {
|
|
159
|
-
|
|
160
|
+
localStreams?: LocalStreams;
|
|
160
161
|
audioEnabled?: boolean; // if not specified, default value true is used
|
|
161
162
|
videoEnabled?: boolean; // if not specified, default value true is used
|
|
162
|
-
|
|
163
|
+
shareAudioEnabled?: boolean; // if not specified, default value true is used
|
|
164
|
+
shareVideoEnabled?: boolean; // if not specified, default value true is used
|
|
163
165
|
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
|
164
166
|
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
|
165
167
|
allowMediaInLobby?: boolean; // allows adding media when in the lobby
|
|
@@ -548,13 +550,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
548
550
|
resourceUrl: string;
|
|
549
551
|
selfId: string;
|
|
550
552
|
state: any;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
553
|
+
localAudioStreamMuteStateHandler: (muted: boolean) => void;
|
|
554
|
+
localVideoStreamMuteStateHandler: (muted: boolean) => void;
|
|
555
|
+
localOutputTrackChangeHandler: () => void;
|
|
554
556
|
roles: any[];
|
|
555
557
|
environment: string;
|
|
556
558
|
namespace = MEETINGS;
|
|
557
559
|
allowMediaInLobby: boolean;
|
|
560
|
+
private sendSlotManager: SendSlotManager = new SendSlotManager(LoggerProxy);
|
|
558
561
|
|
|
559
562
|
/**
|
|
560
563
|
* @param {Object} attrs
|
|
@@ -1226,19 +1229,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1226
1229
|
*/
|
|
1227
1230
|
this.remoteMediaManager = null;
|
|
1228
1231
|
|
|
1229
|
-
this.
|
|
1230
|
-
this.audio.
|
|
1232
|
+
this.localAudioStreamMuteStateHandler = (muted: boolean) => {
|
|
1233
|
+
this.audio.handleLocalStreamMuteStateChange(this, muted);
|
|
1231
1234
|
};
|
|
1232
1235
|
|
|
1233
|
-
this.
|
|
1234
|
-
this.video.
|
|
1236
|
+
this.localVideoStreamMuteStateHandler = (muted: boolean) => {
|
|
1237
|
+
this.video.handleLocalStreamMuteStateChange(this, muted);
|
|
1235
1238
|
};
|
|
1236
1239
|
|
|
1237
|
-
// The handling of
|
|
1240
|
+
// The handling of output track changes should be done inside
|
|
1238
1241
|
// @webex/internal-media-core, but for now we have to do it here, because
|
|
1239
1242
|
// RoapMediaConnection has to use raw MediaStreamTracks in its API until
|
|
1240
|
-
// the Calling SDK also moves to using webrtc-core
|
|
1241
|
-
this.
|
|
1243
|
+
// the Calling SDK also moves to using webrtc-core streams
|
|
1244
|
+
this.localOutputTrackChangeHandler = () => {
|
|
1242
1245
|
if (!this.isMultistream) {
|
|
1243
1246
|
this.updateTranscodedMediaConnection();
|
|
1244
1247
|
}
|
|
@@ -1280,6 +1283,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1280
1283
|
* @param {Object} options
|
|
1281
1284
|
* @param {String} [options.password] optional
|
|
1282
1285
|
* @param {String} [options.captchaCode] optional
|
|
1286
|
+
* @param {Boolean} [options.sendCAevents] optional - Whether to submit Call Analyzer events or not. Default: false.
|
|
1283
1287
|
* @public
|
|
1284
1288
|
* @memberof Meeting
|
|
1285
1289
|
* @returns {Promise}
|
|
@@ -1288,10 +1292,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1288
1292
|
password = null,
|
|
1289
1293
|
captchaCode = null,
|
|
1290
1294
|
extraParams = {},
|
|
1295
|
+
sendCAevents = false,
|
|
1291
1296
|
}: {
|
|
1292
1297
|
password?: string;
|
|
1293
1298
|
captchaCode?: string;
|
|
1294
1299
|
extraParams?: Record<string, any>;
|
|
1300
|
+
sendCAevents?: boolean;
|
|
1295
1301
|
}) {
|
|
1296
1302
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1297
1303
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
@@ -1327,7 +1333,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1327
1333
|
this.config.installedOrgID,
|
|
1328
1334
|
this.locusId,
|
|
1329
1335
|
extraParams,
|
|
1330
|
-
{meetingId: this.id}
|
|
1336
|
+
{meetingId: this.id, sendCAevents}
|
|
1331
1337
|
);
|
|
1332
1338
|
|
|
1333
1339
|
this.parseMeetingInfo(info, this.destination);
|
|
@@ -1419,14 +1425,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1419
1425
|
* password and captcha code were correct or not.
|
|
1420
1426
|
* @param {String} password - this can be either a password or a host key, can be undefined if only captcha was required
|
|
1421
1427
|
* @param {String} captchaCode - can be undefined if captcha was not required by the server
|
|
1428
|
+
* @param {Boolean} sendCAevents - whether Call Analyzer events should be sent when fetching meeting information
|
|
1422
1429
|
* @public
|
|
1423
1430
|
* @memberof Meeting
|
|
1424
1431
|
* @returns {Promise<{isPasswordValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
|
|
1425
1432
|
*/
|
|
1426
|
-
public verifyPassword(password: string, captchaCode: string) {
|
|
1433
|
+
public verifyPassword(password: string, captchaCode: string, sendCAevents = false) {
|
|
1427
1434
|
return this.fetchMeetingInfo({
|
|
1428
1435
|
password,
|
|
1429
1436
|
captchaCode,
|
|
1437
|
+
sendCAevents,
|
|
1430
1438
|
})
|
|
1431
1439
|
.then(() => {
|
|
1432
1440
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS);
|
|
@@ -2272,9 +2280,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2272
2280
|
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2273
2281
|
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2274
2282
|
) {
|
|
2275
|
-
await this.
|
|
2276
|
-
this.mediaProperties.
|
|
2277
|
-
this.mediaProperties.
|
|
2283
|
+
await this.unpublishStreams([
|
|
2284
|
+
this.mediaProperties.shareVideoStream,
|
|
2285
|
+
this.mediaProperties.shareAudioStream,
|
|
2278
2286
|
]);
|
|
2279
2287
|
}
|
|
2280
2288
|
} finally {
|
|
@@ -2778,11 +2786,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2778
2786
|
|
|
2779
2787
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
2780
2788
|
if (this.wirelessShare) {
|
|
2781
|
-
if (this.mediaProperties.
|
|
2782
|
-
await this.
|
|
2789
|
+
if (this.mediaProperties.shareVideoStream) {
|
|
2790
|
+
await this.setLocalShareVideoStream(undefined);
|
|
2783
2791
|
}
|
|
2784
|
-
if (this.mediaProperties.
|
|
2785
|
-
await this.
|
|
2792
|
+
if (this.mediaProperties.shareAudioStream) {
|
|
2793
|
+
await this.setLocalShareAudioStream(undefined);
|
|
2786
2794
|
}
|
|
2787
2795
|
}
|
|
2788
2796
|
// when multiple WEB deviceType join with same user
|
|
@@ -3365,11 +3373,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3365
3373
|
}
|
|
3366
3374
|
|
|
3367
3375
|
/**
|
|
3368
|
-
* Removes remote audio, video and share
|
|
3376
|
+
* Removes remote audio, video and share streams from class instance's mediaProperties
|
|
3369
3377
|
* @returns {undefined}
|
|
3370
3378
|
*/
|
|
3371
|
-
|
|
3372
|
-
this.mediaProperties.
|
|
3379
|
+
unsetRemoteStreams() {
|
|
3380
|
+
this.mediaProperties.unsetRemoteStreams();
|
|
3373
3381
|
}
|
|
3374
3382
|
|
|
3375
3383
|
/**
|
|
@@ -3384,17 +3392,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3384
3392
|
LoggerProxy.logger.warn(
|
|
3385
3393
|
'Meeting:index#closeRemoteStream --> [DEPRECATION WARNING]: closeRemoteStream has been deprecated after v1.89.3'
|
|
3386
3394
|
);
|
|
3387
|
-
this.
|
|
3395
|
+
this.closeRemoteStreams();
|
|
3388
3396
|
}
|
|
3389
3397
|
|
|
3390
3398
|
/**
|
|
3391
|
-
* Removes the remote
|
|
3399
|
+
* Removes the remote streams on the class instance and triggers an event
|
|
3392
3400
|
* to developers
|
|
3393
3401
|
* @returns {undefined}
|
|
3394
3402
|
* @memberof Meeting
|
|
3395
3403
|
*/
|
|
3396
|
-
|
|
3397
|
-
const {
|
|
3404
|
+
closeRemoteStreams() {
|
|
3405
|
+
const {remoteAudioStream, remoteVideoStream, remoteShareStream, shareAudioStream} =
|
|
3406
|
+
this.mediaProperties;
|
|
3398
3407
|
|
|
3399
3408
|
/**
|
|
3400
3409
|
* Triggers an event to the developer
|
|
@@ -3408,7 +3417,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3408
3417
|
this,
|
|
3409
3418
|
{
|
|
3410
3419
|
file: 'meeting/index',
|
|
3411
|
-
function: '
|
|
3420
|
+
function: 'closeRemoteStreams',
|
|
3412
3421
|
},
|
|
3413
3422
|
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3414
3423
|
{
|
|
@@ -3418,193 +3427,240 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3418
3427
|
};
|
|
3419
3428
|
|
|
3420
3429
|
/**
|
|
3421
|
-
* Stops a media
|
|
3422
|
-
* @param {
|
|
3423
|
-
* @param {string} type Media
|
|
3430
|
+
* Stops a media stream and emits an event
|
|
3431
|
+
* @param {RemoteStream} stream Media stream to stop
|
|
3432
|
+
* @param {string} type Media stream type
|
|
3424
3433
|
* @returns {Promise}
|
|
3425
3434
|
* @inner
|
|
3426
3435
|
*/
|
|
3427
3436
|
// eslint-disable-next-line arrow-body-style
|
|
3428
|
-
const
|
|
3429
|
-
return Media.
|
|
3430
|
-
|
|
3431
|
-
const isWrongReadyState = track && !isTrackStopped;
|
|
3432
|
-
|
|
3433
|
-
if (isTrackStopped) {
|
|
3434
|
-
triggerMediaStoppedEvent(type);
|
|
3435
|
-
} else if (isWrongReadyState) {
|
|
3436
|
-
LoggerProxy.logger.warn(
|
|
3437
|
-
`Meeting:index#closeRemoteTracks --> Error: MediaStreamTrack.readyState is ${track.readyState} for ${type}`
|
|
3438
|
-
);
|
|
3439
|
-
}
|
|
3437
|
+
const stopStream = (stream: RemoteStream, type: string) => {
|
|
3438
|
+
return Media.stopStream(stream).then(() => {
|
|
3439
|
+
triggerMediaStoppedEvent(type);
|
|
3440
3440
|
});
|
|
3441
3441
|
};
|
|
3442
3442
|
|
|
3443
3443
|
return Promise.all([
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3444
|
+
stopStream(remoteAudioStream, EVENT_TYPES.REMOTE_AUDIO),
|
|
3445
|
+
stopStream(remoteVideoStream, EVENT_TYPES.REMOTE_VIDEO),
|
|
3446
|
+
stopStream(remoteShareStream, EVENT_TYPES.REMOTE_SHARE),
|
|
3447
|
+
stopStream(shareAudioStream, EVENT_TYPES.REMOTE_SHARE_AUDIO),
|
|
3447
3448
|
]);
|
|
3448
3449
|
}
|
|
3449
3450
|
|
|
3450
3451
|
/**
|
|
3451
|
-
* Stores the reference to a new microphone
|
|
3452
|
-
* on it, cleans up previous
|
|
3452
|
+
* Stores the reference to a new microphone stream, sets up the required event listeners
|
|
3453
|
+
* on it, cleans up previous stream, etc.
|
|
3453
3454
|
*
|
|
3454
|
-
* @param {
|
|
3455
|
+
* @param {LocalMicrophoneStream | null} localStream local microphone stream
|
|
3455
3456
|
* @returns {Promise<void>}
|
|
3456
3457
|
*/
|
|
3457
|
-
private async
|
|
3458
|
-
const
|
|
3458
|
+
private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
|
|
3459
|
+
const oldStream = this.mediaProperties.audioStream;
|
|
3459
3460
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3461
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3462
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3462
3463
|
|
|
3463
3464
|
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3464
|
-
this.mediaProperties.
|
|
3465
|
+
this.mediaProperties.setLocalAudioStream(localStream);
|
|
3465
3466
|
|
|
3466
|
-
this.audio.
|
|
3467
|
+
this.audio.handleLocalStreamChange(this);
|
|
3467
3468
|
|
|
3468
|
-
|
|
3469
|
-
|
|
3469
|
+
localStream?.on(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3470
|
+
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3470
3471
|
|
|
3471
|
-
if (!this.isMultistream || !
|
|
3472
|
-
// for multistream WCME automatically un-publishes the old
|
|
3473
|
-
await this.
|
|
3472
|
+
if (!this.isMultistream || !localStream) {
|
|
3473
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3474
|
+
await this.unpublishStream(MediaType.AudioMain, oldStream);
|
|
3474
3475
|
}
|
|
3475
|
-
await this.
|
|
3476
|
+
await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
|
|
3476
3477
|
}
|
|
3477
3478
|
|
|
3478
3479
|
/**
|
|
3479
|
-
* Stores the reference to a new camera
|
|
3480
|
-
* on it, cleans up previous
|
|
3480
|
+
* Stores the reference to a new camera stream, sets up the required event listeners
|
|
3481
|
+
* on it, cleans up previous stream, etc.
|
|
3481
3482
|
*
|
|
3482
|
-
* @param {
|
|
3483
|
+
* @param {LocalCameraStream | null} localStream local camera stream
|
|
3483
3484
|
* @returns {Promise<void>}
|
|
3484
3485
|
*/
|
|
3485
|
-
private async
|
|
3486
|
-
const
|
|
3486
|
+
private async setLocalVideoStream(localStream?: LocalCameraStream) {
|
|
3487
|
+
const oldStream = this.mediaProperties.videoStream;
|
|
3487
3488
|
|
|
3488
|
-
|
|
3489
|
-
|
|
3489
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3490
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3490
3491
|
|
|
3491
3492
|
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3492
|
-
this.mediaProperties.
|
|
3493
|
+
this.mediaProperties.setLocalVideoStream(localStream);
|
|
3493
3494
|
|
|
3494
|
-
this.video.
|
|
3495
|
+
this.video.handleLocalStreamChange(this);
|
|
3495
3496
|
|
|
3496
|
-
|
|
3497
|
-
|
|
3497
|
+
localStream?.on(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3498
|
+
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3498
3499
|
|
|
3499
|
-
if (!this.isMultistream || !
|
|
3500
|
-
// for multistream WCME automatically un-publishes the old
|
|
3501
|
-
await this.
|
|
3500
|
+
if (!this.isMultistream || !localStream) {
|
|
3501
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3502
|
+
await this.unpublishStream(MediaType.VideoMain, oldStream);
|
|
3502
3503
|
}
|
|
3503
|
-
await this.
|
|
3504
|
+
await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
|
|
3504
3505
|
}
|
|
3505
3506
|
|
|
3506
3507
|
/**
|
|
3507
|
-
* Stores the reference to a new screen share
|
|
3508
|
-
* on it, cleans up previous
|
|
3508
|
+
* Stores the reference to a new screen share stream, sets up the required event listeners
|
|
3509
|
+
* on it, cleans up previous stream, etc.
|
|
3510
|
+
* It also sends the floor grant/release request.
|
|
3509
3511
|
*
|
|
3510
|
-
* @param {
|
|
3512
|
+
* @param {LocalDisplayStream | undefined} localDisplayStream local display stream
|
|
3511
3513
|
* @returns {Promise<void>}
|
|
3512
3514
|
*/
|
|
3513
|
-
private async
|
|
3514
|
-
const
|
|
3515
|
+
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3516
|
+
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3515
3517
|
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
+
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3519
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3518
3520
|
|
|
3519
|
-
this.mediaProperties.
|
|
3521
|
+
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3520
3522
|
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
this.
|
|
3523
|
+
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3524
|
+
localDisplayStream?.on(
|
|
3525
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3526
|
+
this.localOutputTrackChangeHandler
|
|
3525
3527
|
);
|
|
3526
3528
|
|
|
3527
|
-
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.
|
|
3529
|
+
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
|
|
3528
3530
|
|
|
3529
|
-
if (!this.isMultistream || !
|
|
3530
|
-
// for multistream WCME automatically un-publishes the old
|
|
3531
|
-
await this.
|
|
3531
|
+
if (!this.isMultistream || !localDisplayStream) {
|
|
3532
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3533
|
+
await this.unpublishStream(MediaType.VideoSlides, oldStream);
|
|
3532
3534
|
}
|
|
3533
|
-
await this.
|
|
3535
|
+
await this.publishStream(MediaType.VideoSlides, this.mediaProperties.shareVideoStream);
|
|
3534
3536
|
}
|
|
3535
3537
|
|
|
3536
3538
|
/**
|
|
3537
|
-
* Stores the reference to a new screen share audio
|
|
3538
|
-
* on it, cleans up previous
|
|
3539
|
+
* Stores the reference to a new screen share audio stream, sets up the required event listeners
|
|
3540
|
+
* on it, cleans up previous stream, etc.
|
|
3539
3541
|
*
|
|
3540
|
-
* @param {
|
|
3542
|
+
* @param {LocalSystemAudioStream | undefined} localSystemAudioStream local system audio stream
|
|
3541
3543
|
* @returns {Promise<void>}
|
|
3542
3544
|
*/
|
|
3543
|
-
private async
|
|
3544
|
-
const
|
|
3545
|
+
private async setLocalShareAudioStream(localSystemAudioStream?: LocalSystemAudioStream) {
|
|
3546
|
+
const oldStream = this.mediaProperties.shareAudioStream;
|
|
3545
3547
|
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
+
oldStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3549
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3548
3550
|
|
|
3549
|
-
this.mediaProperties.
|
|
3551
|
+
this.mediaProperties.setLocalShareAudioStream(localSystemAudioStream);
|
|
3550
3552
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
this.
|
|
3553
|
+
localSystemAudioStream?.on(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3554
|
+
localSystemAudioStream?.on(
|
|
3555
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3556
|
+
this.localOutputTrackChangeHandler
|
|
3555
3557
|
);
|
|
3556
3558
|
|
|
3557
|
-
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.
|
|
3559
|
+
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
|
|
3558
3560
|
|
|
3559
|
-
if (!this.isMultistream || !
|
|
3560
|
-
|
|
3561
|
+
if (!this.isMultistream || !localSystemAudioStream) {
|
|
3562
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3563
|
+
await this.unpublishStream(MediaType.AudioSlides, oldStream);
|
|
3561
3564
|
}
|
|
3562
|
-
await this.
|
|
3565
|
+
await this.publishStream(MediaType.AudioSlides, this.mediaProperties.shareAudioStream);
|
|
3563
3566
|
}
|
|
3564
3567
|
|
|
3565
3568
|
/**
|
|
3566
|
-
*
|
|
3569
|
+
* Handles the local audio stream publish state change event
|
|
3570
|
+
* @internal
|
|
3571
|
+
* @param {Object} options parameters functionName, isPublished, mediaType and stream needed to trigger event
|
|
3572
|
+
* @returns {undefined}
|
|
3573
|
+
*/
|
|
3574
|
+
private emitPublishStateChangeEvent(options: {
|
|
3575
|
+
functionName: string;
|
|
3576
|
+
isPublished: boolean;
|
|
3577
|
+
mediaType: MediaType;
|
|
3578
|
+
stream: MediaStream;
|
|
3579
|
+
}) {
|
|
3580
|
+
const {functionName, isPublished, mediaType, stream} = options;
|
|
3581
|
+
Trigger.trigger(
|
|
3582
|
+
this,
|
|
3583
|
+
{
|
|
3584
|
+
file: 'meeting/index',
|
|
3585
|
+
function: functionName,
|
|
3586
|
+
},
|
|
3587
|
+
EVENT_TRIGGERS.MEETING_STREAM_PUBLISH_STATE_CHANGED,
|
|
3588
|
+
{
|
|
3589
|
+
isPublished,
|
|
3590
|
+
mediaType,
|
|
3591
|
+
stream,
|
|
3592
|
+
}
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
/**
|
|
3597
|
+
* Removes references to local streams. This function should be called
|
|
3567
3598
|
* on cleanup when we leave the meeting etc.
|
|
3568
3599
|
*
|
|
3569
3600
|
* @internal
|
|
3570
3601
|
* @returns {void}
|
|
3571
3602
|
*/
|
|
3572
|
-
public
|
|
3573
|
-
const {
|
|
3603
|
+
public cleanupLocalStreams() {
|
|
3604
|
+
const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
|
|
3574
3605
|
|
|
3575
|
-
|
|
3576
|
-
|
|
3606
|
+
audioStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3607
|
+
audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3577
3608
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3609
|
+
videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3610
|
+
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3580
3611
|
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
this.
|
|
3612
|
+
shareAudioStream?.off(StreamEventNames.MuteStateChange, this.handleShareAudioStreamEnded);
|
|
3613
|
+
shareAudioStream?.off(
|
|
3614
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3615
|
+
this.localOutputTrackChangeHandler
|
|
3585
3616
|
);
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
this.underlyingLocalTrackChangeHandler
|
|
3617
|
+
shareVideoStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamEnded);
|
|
3618
|
+
shareVideoStream?.off(
|
|
3619
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3620
|
+
this.localOutputTrackChangeHandler
|
|
3591
3621
|
);
|
|
3592
3622
|
|
|
3593
|
-
this.mediaProperties.
|
|
3594
|
-
this.mediaProperties.
|
|
3595
|
-
this.mediaProperties.
|
|
3596
|
-
this.mediaProperties.
|
|
3623
|
+
this.mediaProperties.setLocalAudioStream(undefined);
|
|
3624
|
+
this.mediaProperties.setLocalVideoStream(undefined);
|
|
3625
|
+
this.mediaProperties.setLocalShareAudioStream(undefined);
|
|
3626
|
+
this.mediaProperties.setLocalShareVideoStream(undefined);
|
|
3597
3627
|
|
|
3598
3628
|
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3599
3629
|
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3600
3630
|
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3601
3631
|
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3632
|
+
if (audioStream) {
|
|
3633
|
+
this.emitPublishStateChangeEvent({
|
|
3634
|
+
functionName: 'cleanupLocalStreams',
|
|
3635
|
+
isPublished: false,
|
|
3636
|
+
mediaType: MediaType.AudioMain,
|
|
3637
|
+
stream: audioStream,
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
if (videoStream) {
|
|
3641
|
+
this.emitPublishStateChangeEvent({
|
|
3642
|
+
functionName: 'cleanupLocalStreams',
|
|
3643
|
+
isPublished: false,
|
|
3644
|
+
mediaType: MediaType.VideoMain,
|
|
3645
|
+
stream: videoStream,
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
if (shareVideoStream) {
|
|
3649
|
+
this.emitPublishStateChangeEvent({
|
|
3650
|
+
functionName: 'cleanupLocalStreams',
|
|
3651
|
+
isPublished: false,
|
|
3652
|
+
mediaType: MediaType.VideoSlides,
|
|
3653
|
+
stream: shareVideoStream,
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
if (shareAudioStream) {
|
|
3657
|
+
this.emitPublishStateChangeEvent({
|
|
3658
|
+
functionName: 'cleanupLocalStreams',
|
|
3659
|
+
isPublished: false,
|
|
3660
|
+
mediaType: MediaType.AudioSlides,
|
|
3661
|
+
stream: shareAudioStream,
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3608
3664
|
}
|
|
3609
3665
|
|
|
3610
3666
|
/**
|
|
@@ -3671,6 +3727,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3671
3727
|
|
|
3672
3728
|
this.receiveSlotManager.reset();
|
|
3673
3729
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3730
|
+
this.sendSlotManager.reset();
|
|
3674
3731
|
}
|
|
3675
3732
|
|
|
3676
3733
|
this.audio = null;
|
|
@@ -3744,7 +3801,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3744
3801
|
this.audio
|
|
3745
3802
|
.handleClientRequest(this, true)
|
|
3746
3803
|
.then(() => {
|
|
3747
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.
|
|
3804
|
+
MeetingUtil.handleAudioLogging(this.mediaProperties.audioStream);
|
|
3748
3805
|
// @ts-ignore
|
|
3749
3806
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3750
3807
|
name: 'client.muted',
|
|
@@ -3794,7 +3851,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3794
3851
|
this.audio
|
|
3795
3852
|
.handleClientRequest(this, false)
|
|
3796
3853
|
.then(() => {
|
|
3797
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.
|
|
3854
|
+
MeetingUtil.handleAudioLogging(this.mediaProperties.audioStream);
|
|
3798
3855
|
// @ts-ignore
|
|
3799
3856
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3800
3857
|
name: 'client.unmuted',
|
|
@@ -3843,7 +3900,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3843
3900
|
this.video
|
|
3844
3901
|
.handleClientRequest(this, true)
|
|
3845
3902
|
.then(() => {
|
|
3846
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.
|
|
3903
|
+
MeetingUtil.handleVideoLogging(this.mediaProperties.videoStream);
|
|
3847
3904
|
// @ts-ignore
|
|
3848
3905
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3849
3906
|
name: 'client.muted',
|
|
@@ -3892,7 +3949,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3892
3949
|
this.video
|
|
3893
3950
|
.handleClientRequest(this, false)
|
|
3894
3951
|
.then(() => {
|
|
3895
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.
|
|
3952
|
+
MeetingUtil.handleVideoLogging(this.mediaProperties.videoStream);
|
|
3896
3953
|
// @ts-ignore
|
|
3897
3954
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3898
3955
|
name: 'client.unmuted',
|
|
@@ -3928,7 +3985,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3928
3985
|
* joinWithMedia({
|
|
3929
3986
|
* joinOptions: {resourceId: 'resourceId' },
|
|
3930
3987
|
* mediaOptions: {
|
|
3931
|
-
*
|
|
3988
|
+
* localStreams: { microphone: microphoneStream, camera: cameraStream }
|
|
3932
3989
|
* }
|
|
3933
3990
|
* })
|
|
3934
3991
|
*/
|
|
@@ -4344,23 +4401,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4344
4401
|
options: {meetingId: this.id},
|
|
4345
4402
|
});
|
|
4346
4403
|
|
|
4347
|
-
if (!isEmpty(this.meetingInfo)) {
|
|
4348
|
-
// @ts-ignore
|
|
4349
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4350
|
-
name: 'client.meetinginfo.request',
|
|
4351
|
-
options: {meetingId: this.id},
|
|
4352
|
-
});
|
|
4353
|
-
|
|
4354
|
-
// @ts-ignore
|
|
4355
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4356
|
-
name: 'client.meetinginfo.response',
|
|
4357
|
-
payload: {
|
|
4358
|
-
identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
|
|
4359
|
-
},
|
|
4360
|
-
options: {meetingId: this.id},
|
|
4361
|
-
});
|
|
4362
|
-
}
|
|
4363
|
-
|
|
4364
4404
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
4365
4405
|
|
|
4366
4406
|
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
@@ -4700,7 +4740,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4700
4740
|
});
|
|
4701
4741
|
|
|
4702
4742
|
this.locusInfo.once(LOCUSINFO.EVENTS.SELF_OBSERVING, async () => {
|
|
4703
|
-
// Clean up the camera , microphone
|
|
4743
|
+
// Clean up the camera , microphone stream and re initiate it
|
|
4704
4744
|
|
|
4705
4745
|
try {
|
|
4706
4746
|
if (this.screenShareFloorState === ScreenShareFloorStatus.GRANTED) {
|
|
@@ -4717,7 +4757,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4717
4757
|
},
|
|
4718
4758
|
};
|
|
4719
4759
|
|
|
4720
|
-
this.
|
|
4760
|
+
this.cleanupLocalStreams();
|
|
4721
4761
|
|
|
4722
4762
|
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4723
4763
|
this.mediaProperties.unsetRemoteMedia();
|
|
@@ -4999,46 +5039,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4999
5039
|
)}`
|
|
5000
5040
|
);
|
|
5001
5041
|
|
|
5002
|
-
|
|
5042
|
+
if (event.track) {
|
|
5043
|
+
const mediaTrack = event.track;
|
|
5044
|
+
const remoteStream = new RemoteStream(MediaUtil.createMediaStream([mediaTrack]));
|
|
5003
5045
|
|
|
5004
|
-
|
|
5005
|
-
|
|
5046
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
5047
|
+
let eventType;
|
|
5006
5048
|
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
if (event.track) {
|
|
5049
|
+
switch (event.type) {
|
|
5050
|
+
case RemoteTrackType.AUDIO:
|
|
5051
|
+
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
5052
|
+
this.mediaProperties.setRemoteAudioStream(remoteStream);
|
|
5053
|
+
break;
|
|
5054
|
+
case RemoteTrackType.VIDEO:
|
|
5055
|
+
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
5056
|
+
this.mediaProperties.setRemoteVideoStream(remoteStream);
|
|
5057
|
+
break;
|
|
5058
|
+
case RemoteTrackType.SCREENSHARE_VIDEO:
|
|
5018
5059
|
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
5019
|
-
this.mediaProperties.
|
|
5060
|
+
this.mediaProperties.setRemoteShareStream(remoteStream);
|
|
5061
|
+
break;
|
|
5062
|
+
default: {
|
|
5063
|
+
LoggerProxy.logger.log(
|
|
5064
|
+
'Meeting:index#setupMediaConnectionListeners --> unexpected track'
|
|
5065
|
+
);
|
|
5020
5066
|
}
|
|
5021
|
-
break;
|
|
5022
|
-
default: {
|
|
5023
|
-
LoggerProxy.logger.log(
|
|
5024
|
-
'Meeting:index#setupMediaConnectionListeners --> unexpected track'
|
|
5025
|
-
);
|
|
5026
5067
|
}
|
|
5027
|
-
}
|
|
5028
5068
|
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5069
|
+
if (eventType && mediaTrack) {
|
|
5070
|
+
Trigger.trigger(
|
|
5071
|
+
this,
|
|
5072
|
+
{
|
|
5073
|
+
file: 'meeting/index',
|
|
5074
|
+
function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED',
|
|
5075
|
+
},
|
|
5076
|
+
EVENT_TRIGGERS.MEDIA_READY,
|
|
5077
|
+
{
|
|
5078
|
+
type: eventType,
|
|
5079
|
+
stream: remoteStream.outputStream,
|
|
5080
|
+
}
|
|
5081
|
+
);
|
|
5082
|
+
}
|
|
5042
5083
|
}
|
|
5043
5084
|
});
|
|
5044
5085
|
|
|
@@ -5127,7 +5168,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5127
5168
|
}
|
|
5128
5169
|
});
|
|
5129
5170
|
|
|
5130
|
-
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (
|
|
5171
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (csis) => {
|
|
5131
5172
|
Trigger.trigger(
|
|
5132
5173
|
this,
|
|
5133
5174
|
{
|
|
@@ -5136,8 +5177,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5136
5177
|
},
|
|
5137
5178
|
EVENT_TRIGGERS.ACTIVE_SPEAKER_CHANGED,
|
|
5138
5179
|
{
|
|
5139
|
-
|
|
5140
|
-
memberIds: msg.csis
|
|
5180
|
+
memberIds: csis
|
|
5141
5181
|
// @ts-ignore
|
|
5142
5182
|
.map((csi) => this.members.findMemberByCsi(csi)?.id)
|
|
5143
5183
|
.filter((item) => item !== undefined),
|
|
@@ -5281,10 +5321,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5281
5321
|
}
|
|
5282
5322
|
|
|
5283
5323
|
/**
|
|
5284
|
-
* Creates a webrtc media connection and publishes
|
|
5324
|
+
* Creates a webrtc media connection and publishes streams to it
|
|
5285
5325
|
*
|
|
5286
5326
|
* @param {Object} turnServerInfo TURN server information
|
|
5287
5327
|
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
5328
|
+
* @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
|
|
5288
5329
|
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
5289
5330
|
*/
|
|
5290
5331
|
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
@@ -5310,18 +5351,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5310
5351
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
5311
5352
|
this.setupMediaConnectionListeners();
|
|
5312
5353
|
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5354
|
+
if (this.isMultistream) {
|
|
5355
|
+
const [audioEnabled, videoEnabled, shareEnabled] = [
|
|
5356
|
+
this.mediaProperties.mediaDirection.sendAudio ||
|
|
5357
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
5358
|
+
this.mediaProperties.mediaDirection.sendVideo ||
|
|
5359
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
5360
|
+
this.mediaProperties.mediaDirection.sendShare ||
|
|
5361
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
5362
|
+
];
|
|
5363
|
+
|
|
5364
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoMain, audioEnabled);
|
|
5365
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioMain, videoEnabled);
|
|
5366
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoSlides, shareEnabled);
|
|
5367
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioSlides, shareEnabled);
|
|
5316
5368
|
}
|
|
5317
|
-
|
|
5318
|
-
|
|
5369
|
+
|
|
5370
|
+
// publish the streams
|
|
5371
|
+
if (this.mediaProperties.audioStream) {
|
|
5372
|
+
await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
|
|
5319
5373
|
}
|
|
5320
|
-
if (this.mediaProperties.
|
|
5321
|
-
await this.
|
|
5374
|
+
if (this.mediaProperties.videoStream) {
|
|
5375
|
+
await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
|
|
5322
5376
|
}
|
|
5323
|
-
if (this.
|
|
5324
|
-
await this.
|
|
5377
|
+
if (this.mediaProperties.shareVideoStream) {
|
|
5378
|
+
await this.publishStream(MediaType.VideoSlides, this.mediaProperties.shareVideoStream);
|
|
5379
|
+
}
|
|
5380
|
+
if (this.isMultistream && this.mediaProperties.shareAudioStream) {
|
|
5381
|
+
await this.publishStream(MediaType.AudioSlides, this.mediaProperties.shareAudioStream);
|
|
5325
5382
|
}
|
|
5326
5383
|
|
|
5327
5384
|
return mc;
|
|
@@ -5375,10 +5432,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5375
5432
|
}
|
|
5376
5433
|
|
|
5377
5434
|
const {
|
|
5378
|
-
|
|
5435
|
+
localStreams,
|
|
5379
5436
|
audioEnabled = true,
|
|
5380
5437
|
videoEnabled = true,
|
|
5381
|
-
|
|
5438
|
+
shareAudioEnabled = true,
|
|
5439
|
+
shareVideoEnabled = true,
|
|
5382
5440
|
remoteMediaManagerConfig,
|
|
5383
5441
|
bundlePolicy,
|
|
5384
5442
|
allowMediaInLobby,
|
|
@@ -5416,7 +5474,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5416
5474
|
options: {meetingId: this.id},
|
|
5417
5475
|
});
|
|
5418
5476
|
|
|
5419
|
-
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any
|
|
5477
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
|
|
5420
5478
|
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5421
5479
|
this.mediaProperties.setMediaDirection({
|
|
5422
5480
|
sendAudio: audioEnabled,
|
|
@@ -5424,7 +5482,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5424
5482
|
sendShare: false,
|
|
5425
5483
|
receiveAudio: audioEnabled,
|
|
5426
5484
|
receiveVideo: videoEnabled,
|
|
5427
|
-
receiveShare,
|
|
5485
|
+
receiveShare: shareAudioEnabled || shareVideoEnabled,
|
|
5428
5486
|
});
|
|
5429
5487
|
|
|
5430
5488
|
this.locusMediaRequest = new LocusMediaRequest(
|
|
@@ -5451,19 +5509,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5451
5509
|
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5452
5510
|
const promises = [];
|
|
5453
5511
|
|
|
5454
|
-
// setup all the references to local
|
|
5512
|
+
// setup all the references to local streams in this.mediaProperties before creating media connection
|
|
5455
5513
|
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5456
|
-
if (
|
|
5457
|
-
promises.push(this.
|
|
5514
|
+
if (localStreams?.microphone) {
|
|
5515
|
+
promises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
5458
5516
|
}
|
|
5459
|
-
if (
|
|
5460
|
-
promises.push(this.
|
|
5517
|
+
if (localStreams?.camera) {
|
|
5518
|
+
promises.push(this.setLocalVideoStream(localStreams.camera));
|
|
5461
5519
|
}
|
|
5462
|
-
if (
|
|
5463
|
-
promises.push(this.
|
|
5520
|
+
if (localStreams?.screenShare?.video) {
|
|
5521
|
+
promises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
5464
5522
|
}
|
|
5465
|
-
if (
|
|
5466
|
-
promises.push(this.
|
|
5523
|
+
if (localStreams?.screenShare?.audio) {
|
|
5524
|
+
promises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
5467
5525
|
}
|
|
5468
5526
|
|
|
5469
5527
|
return Promise.all(promises)
|
|
@@ -5595,7 +5653,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5595
5653
|
})
|
|
5596
5654
|
)
|
|
5597
5655
|
.then(() => {
|
|
5598
|
-
if (this.mediaProperties.
|
|
5656
|
+
if (this.mediaProperties.hasLocalShareStream()) {
|
|
5599
5657
|
return this.enqueueScreenShareFloorRequest();
|
|
5600
5658
|
}
|
|
5601
5659
|
|
|
@@ -5805,11 +5863,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5805
5863
|
public async updateMedia(options: {
|
|
5806
5864
|
audioEnabled?: boolean;
|
|
5807
5865
|
videoEnabled?: boolean;
|
|
5808
|
-
|
|
5866
|
+
shareAudioEnabled?: boolean;
|
|
5867
|
+
shareVideoEnabled?: boolean;
|
|
5809
5868
|
}) {
|
|
5810
5869
|
this.checkMediaConnection();
|
|
5811
5870
|
|
|
5812
|
-
const {audioEnabled, videoEnabled,
|
|
5871
|
+
const {audioEnabled, videoEnabled, shareAudioEnabled, shareVideoEnabled} = options;
|
|
5813
5872
|
|
|
5814
5873
|
LoggerProxy.logger.log(
|
|
5815
5874
|
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
@@ -5820,40 +5879,40 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5820
5879
|
}
|
|
5821
5880
|
|
|
5822
5881
|
if (this.isMultistream) {
|
|
5823
|
-
if (
|
|
5824
|
-
throw new Error(
|
|
5825
|
-
'enabling/disabling video in a meeting is not supported for multistream, it can only be done upfront when calling addMedia()'
|
|
5826
|
-
);
|
|
5827
|
-
}
|
|
5828
|
-
|
|
5829
|
-
if (receiveShare !== undefined) {
|
|
5882
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
5830
5883
|
throw new Error(
|
|
5831
|
-
'toggling
|
|
5884
|
+
'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5832
5885
|
);
|
|
5833
5886
|
}
|
|
5887
|
+
} else if (shareAudioEnabled !== undefined) {
|
|
5888
|
+
throw new Error(
|
|
5889
|
+
'toggling shareAudioEnabled in a transcoded meeting is not supported as of now'
|
|
5890
|
+
);
|
|
5834
5891
|
}
|
|
5835
5892
|
|
|
5836
5893
|
if (audioEnabled !== undefined) {
|
|
5837
5894
|
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5838
5895
|
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5839
5896
|
this.audio.enable(this, audioEnabled);
|
|
5897
|
+
if (this.isMultistream) {
|
|
5898
|
+
this.sendSlotManager.setActive(MediaType.AudioMain, audioEnabled);
|
|
5899
|
+
}
|
|
5840
5900
|
}
|
|
5841
5901
|
|
|
5842
5902
|
if (videoEnabled !== undefined) {
|
|
5843
5903
|
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5844
5904
|
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5845
5905
|
this.video.enable(this, videoEnabled);
|
|
5906
|
+
if (this.isMultistream) {
|
|
5907
|
+
this.sendSlotManager.setActive(MediaType.VideoMain, videoEnabled);
|
|
5908
|
+
}
|
|
5846
5909
|
}
|
|
5847
5910
|
|
|
5848
|
-
if (
|
|
5849
|
-
this.mediaProperties.mediaDirection.receiveShare =
|
|
5911
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
5912
|
+
this.mediaProperties.mediaDirection.receiveShare = !!(shareAudioEnabled || shareVideoEnabled);
|
|
5850
5913
|
}
|
|
5851
5914
|
|
|
5852
|
-
if (this.isMultistream) {
|
|
5853
|
-
if (audioEnabled !== undefined) {
|
|
5854
|
-
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5855
|
-
}
|
|
5856
|
-
} else {
|
|
5915
|
+
if (!this.isMultistream) {
|
|
5857
5916
|
await this.updateTranscodedMediaConnection();
|
|
5858
5917
|
}
|
|
5859
5918
|
|
|
@@ -6188,15 +6247,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6188
6247
|
*/
|
|
6189
6248
|
private requestScreenShareFloor() {
|
|
6190
6249
|
if (
|
|
6191
|
-
!this.mediaProperties.
|
|
6250
|
+
!this.mediaProperties.hasLocalShareStream() ||
|
|
6192
6251
|
!this.mediaProperties.mediaDirection.sendShare
|
|
6193
6252
|
) {
|
|
6194
6253
|
LoggerProxy.logger.log(
|
|
6195
|
-
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share
|
|
6196
|
-
this.mediaProperties.
|
|
6197
|
-
},
|
|
6198
|
-
this.mediaProperties.mediaDirection.sendShare
|
|
6199
|
-
})`
|
|
6254
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share stream anymore (shareStream=${
|
|
6255
|
+
this.mediaProperties.shareVideoStream ? 'yes' : 'no'
|
|
6256
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
6200
6257
|
);
|
|
6201
6258
|
this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
|
|
6202
6259
|
|
|
@@ -6496,12 +6553,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6496
6553
|
} = {} as any
|
|
6497
6554
|
) {
|
|
6498
6555
|
const {main, content} = renderInfo;
|
|
6499
|
-
const {mediaDirection,
|
|
6556
|
+
const {mediaDirection, remoteShareStream, remoteVideoStream} = this.mediaProperties;
|
|
6500
6557
|
|
|
6501
6558
|
const layoutInfo = cloneDeep(this.lastVideoLayoutInfo);
|
|
6502
6559
|
|
|
6503
6560
|
// TODO: We need a real time value for Audio, Video and Share send indicator
|
|
6504
|
-
if (mediaDirection.receiveVideo !== true || !
|
|
6561
|
+
if (mediaDirection.receiveVideo !== true || !remoteVideoStream) {
|
|
6505
6562
|
return this.rejectWithErrorLog(
|
|
6506
6563
|
'Meeting:index#changeVideoLayout --> cannot change video layout, you are not recieving any video/share stream'
|
|
6507
6564
|
);
|
|
@@ -6532,7 +6589,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6532
6589
|
}
|
|
6533
6590
|
|
|
6534
6591
|
if (content) {
|
|
6535
|
-
if (this.mediaProperties.mediaDirection.receiveShare &&
|
|
6592
|
+
if (this.mediaProperties.mediaDirection.receiveShare && remoteShareStream) {
|
|
6536
6593
|
const contentWidth = Math.round(content.width);
|
|
6537
6594
|
const contentHeight = Math.round(content.height);
|
|
6538
6595
|
|
|
@@ -6630,23 +6687,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6630
6687
|
* @memberof Meeting
|
|
6631
6688
|
* @returns {undefined}
|
|
6632
6689
|
*/
|
|
6633
|
-
private
|
|
6634
|
-
// current share audio
|
|
6635
|
-
// share video
|
|
6636
|
-
// completely ended, which means no share audio or video
|
|
6637
|
-
if (this.wirelessShare && !this.mediaProperties.
|
|
6690
|
+
private handleShareAudioStreamEnded = async () => {
|
|
6691
|
+
// current share audio stream has ended, but there might be an active
|
|
6692
|
+
// share video stream. we only leave from wireless share if share has
|
|
6693
|
+
// completely ended, which means no share audio or video streams active
|
|
6694
|
+
if (this.wirelessShare && !this.mediaProperties.shareVideoStream) {
|
|
6638
6695
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6639
6696
|
} else {
|
|
6640
6697
|
try {
|
|
6641
|
-
await this.
|
|
6698
|
+
await this.unpublishStreams([this.mediaProperties.shareAudioStream]);
|
|
6642
6699
|
} catch (error) {
|
|
6643
6700
|
LoggerProxy.logger.log(
|
|
6644
|
-
'Meeting:index#
|
|
6701
|
+
'Meeting:index#handleShareAudioStreamEnded --> Error stopping share: ',
|
|
6645
6702
|
error
|
|
6646
6703
|
);
|
|
6647
6704
|
}
|
|
6648
6705
|
}
|
|
6649
|
-
this.triggerStoppedSharing();
|
|
6650
6706
|
};
|
|
6651
6707
|
|
|
6652
6708
|
/**
|
|
@@ -6655,18 +6711,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6655
6711
|
* @memberof Meeting
|
|
6656
6712
|
* @returns {undefined}
|
|
6657
6713
|
*/
|
|
6658
|
-
private
|
|
6659
|
-
// current share video
|
|
6660
|
-
// share audio
|
|
6661
|
-
// completely ended, which means no share audio or video
|
|
6662
|
-
if (this.wirelessShare && !this.mediaProperties.
|
|
6714
|
+
private handleShareVideoStreamEnded = async () => {
|
|
6715
|
+
// current share video stream has ended, but there might be an active
|
|
6716
|
+
// share audio stream. we only leave from wireless share if share has
|
|
6717
|
+
// completely ended, which means no share audio or video streams active
|
|
6718
|
+
if (this.wirelessShare && !this.mediaProperties.shareAudioStream) {
|
|
6663
6719
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6664
6720
|
} else {
|
|
6665
6721
|
try {
|
|
6666
|
-
await this.
|
|
6722
|
+
await this.unpublishStreams([this.mediaProperties.shareVideoStream]);
|
|
6667
6723
|
} catch (error) {
|
|
6668
6724
|
LoggerProxy.logger.log(
|
|
6669
|
-
'Meeting:index#
|
|
6725
|
+
'Meeting:index#handleShareVideoStreamEnded --> Error stopping share: ',
|
|
6670
6726
|
error
|
|
6671
6727
|
);
|
|
6672
6728
|
}
|
|
@@ -6681,12 +6737,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6681
6737
|
* @memberof Meeting
|
|
6682
6738
|
*/
|
|
6683
6739
|
private triggerStoppedSharing = () => {
|
|
6684
|
-
if (!this.mediaProperties.
|
|
6740
|
+
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
6685
6741
|
Trigger.trigger(
|
|
6686
6742
|
this,
|
|
6687
6743
|
{
|
|
6688
6744
|
file: 'meeting/index',
|
|
6689
|
-
function: '
|
|
6745
|
+
function: 'handleShareStreamEnded',
|
|
6690
6746
|
},
|
|
6691
6747
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
6692
6748
|
{
|
|
@@ -6729,11 +6785,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6729
6785
|
* @returns {undefined}
|
|
6730
6786
|
*/
|
|
6731
6787
|
private handleMediaLogging(mediaProperties: {
|
|
6732
|
-
|
|
6733
|
-
|
|
6788
|
+
audioStream?: LocalMicrophoneStream;
|
|
6789
|
+
videoStream?: LocalCameraStream;
|
|
6734
6790
|
}) {
|
|
6735
|
-
MeetingUtil.handleVideoLogging(mediaProperties.
|
|
6736
|
-
MeetingUtil.handleAudioLogging(mediaProperties.
|
|
6791
|
+
MeetingUtil.handleVideoLogging(mediaProperties.videoStream);
|
|
6792
|
+
MeetingUtil.handleAudioLogging(mediaProperties.audioStream);
|
|
6737
6793
|
}
|
|
6738
6794
|
|
|
6739
6795
|
/**
|
|
@@ -6973,7 +7029,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6973
7029
|
}
|
|
6974
7030
|
|
|
6975
7031
|
/**
|
|
6976
|
-
* Method to enable or disable the 'Music mode' effect on audio
|
|
7032
|
+
* Method to enable or disable the 'Music mode' effect on audio stream
|
|
6977
7033
|
*
|
|
6978
7034
|
* @param {boolean} shouldEnableMusicMode
|
|
6979
7035
|
* @returns {Promise}
|
|
@@ -6986,12 +7042,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6986
7042
|
}
|
|
6987
7043
|
|
|
6988
7044
|
if (shouldEnableMusicMode) {
|
|
6989
|
-
await this.
|
|
7045
|
+
await this.sendSlotManager.setCodecParameters(MediaType.AudioMain, {
|
|
6990
7046
|
maxaveragebitrate: '64000',
|
|
6991
7047
|
maxplaybackrate: '48000',
|
|
6992
7048
|
});
|
|
6993
7049
|
} else {
|
|
6994
|
-
await this.
|
|
7050
|
+
await this.sendSlotManager.deleteCodecParameters(MediaType.AudioMain, [
|
|
6995
7051
|
'maxaveragebitrate',
|
|
6996
7052
|
'maxplaybackrate',
|
|
6997
7053
|
]);
|
|
@@ -7013,10 +7069,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7013
7069
|
|
|
7014
7070
|
return this.mediaProperties.webrtcMediaConnection
|
|
7015
7071
|
.update({
|
|
7072
|
+
// TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now
|
|
7016
7073
|
localTracks: {
|
|
7017
|
-
audio: this.mediaProperties.
|
|
7018
|
-
video: this.mediaProperties.
|
|
7019
|
-
screenShareVideo: this.mediaProperties.
|
|
7074
|
+
audio: this.mediaProperties.audioStream?.outputTrack || null,
|
|
7075
|
+
video: this.mediaProperties.videoStream?.outputTrack || null,
|
|
7076
|
+
screenShareVideo: this.mediaProperties.shareVideoStream?.outputTrack || null,
|
|
7077
|
+
screenShareAudio: this.mediaProperties.shareAudioStream?.outputTrack || null,
|
|
7020
7078
|
},
|
|
7021
7079
|
direction: {
|
|
7022
7080
|
audio: Media.getDirection(
|
|
@@ -7055,56 +7113,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7055
7113
|
}
|
|
7056
7114
|
|
|
7057
7115
|
/**
|
|
7058
|
-
* Publishes a
|
|
7116
|
+
* Publishes a stream.
|
|
7059
7117
|
*
|
|
7060
|
-
* @param {
|
|
7118
|
+
* @param {MediaType} mediaType of the stream
|
|
7119
|
+
* @param {LocalStream} stream to publish
|
|
7061
7120
|
* @returns {Promise}
|
|
7062
7121
|
*/
|
|
7063
|
-
private async
|
|
7064
|
-
if (!
|
|
7122
|
+
private async publishStream(mediaType: MediaType, stream?: LocalStream) {
|
|
7123
|
+
if (!stream) {
|
|
7065
7124
|
return;
|
|
7066
7125
|
}
|
|
7067
7126
|
|
|
7068
7127
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
7069
|
-
if (this.isMultistream) {
|
|
7070
|
-
await this.
|
|
7071
|
-
} else {
|
|
7072
|
-
track.setPublished(true); // for multistream, this call is done by WCME
|
|
7128
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
7129
|
+
await this.sendSlotManager.publishStream(mediaType, stream);
|
|
7073
7130
|
}
|
|
7131
|
+
|
|
7132
|
+
this.emitPublishStateChangeEvent({
|
|
7133
|
+
isPublished: true,
|
|
7134
|
+
mediaType,
|
|
7135
|
+
stream,
|
|
7136
|
+
functionName: 'publishStream',
|
|
7137
|
+
});
|
|
7074
7138
|
}
|
|
7075
7139
|
}
|
|
7076
7140
|
|
|
7077
7141
|
/**
|
|
7078
|
-
* Un-publishes a
|
|
7142
|
+
* Un-publishes a stream.
|
|
7079
7143
|
*
|
|
7080
|
-
* @param {
|
|
7144
|
+
* @param {MediaType} mediaType of the stream
|
|
7145
|
+
* @param {LocalStream} stream to unpublish
|
|
7081
7146
|
* @returns {Promise}
|
|
7082
7147
|
*/
|
|
7083
|
-
private async
|
|
7084
|
-
if (!
|
|
7148
|
+
private async unpublishStream(mediaType: MediaType, stream?: LocalStream) {
|
|
7149
|
+
if (!stream) {
|
|
7085
7150
|
return;
|
|
7086
7151
|
}
|
|
7087
7152
|
|
|
7088
7153
|
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
7089
|
-
await this.
|
|
7090
|
-
} else {
|
|
7091
|
-
track.setPublished(false); // for multistream, this call is done by WCME
|
|
7154
|
+
await this.sendSlotManager.unpublishStream(mediaType);
|
|
7092
7155
|
}
|
|
7156
|
+
|
|
7157
|
+
this.emitPublishStateChangeEvent({
|
|
7158
|
+
isPublished: false,
|
|
7159
|
+
mediaType,
|
|
7160
|
+
stream,
|
|
7161
|
+
functionName: 'unpublishStream',
|
|
7162
|
+
});
|
|
7093
7163
|
}
|
|
7094
7164
|
|
|
7095
7165
|
/**
|
|
7096
|
-
* Publishes specified local
|
|
7166
|
+
* Publishes specified local streams in the meeting
|
|
7097
7167
|
*
|
|
7098
|
-
* @param {Object}
|
|
7168
|
+
* @param {Object} streams
|
|
7099
7169
|
* @returns {Promise}
|
|
7100
7170
|
*/
|
|
7101
|
-
async
|
|
7171
|
+
async publishStreams(streams: LocalStreams): Promise<void> {
|
|
7102
7172
|
this.checkMediaConnection();
|
|
7103
7173
|
if (
|
|
7104
|
-
!
|
|
7105
|
-
!
|
|
7106
|
-
!
|
|
7107
|
-
!
|
|
7174
|
+
!streams.microphone &&
|
|
7175
|
+
!streams.camera &&
|
|
7176
|
+
!streams.screenShare?.audio &&
|
|
7177
|
+
!streams.screenShare?.video
|
|
7108
7178
|
) {
|
|
7109
7179
|
// nothing to do
|
|
7110
7180
|
return;
|
|
@@ -7112,24 +7182,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7112
7182
|
|
|
7113
7183
|
let floorRequestNeeded = false;
|
|
7114
7184
|
|
|
7115
|
-
|
|
7116
|
-
|
|
7185
|
+
// Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting
|
|
7186
|
+
if (this.isMultistream && streams.screenShare?.audio) {
|
|
7187
|
+
await this.setLocalShareAudioStream(streams.screenShare.audio);
|
|
7117
7188
|
|
|
7118
7189
|
floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
|
|
7119
7190
|
}
|
|
7120
7191
|
|
|
7121
|
-
if (
|
|
7122
|
-
await this.
|
|
7192
|
+
if (streams.screenShare?.video) {
|
|
7193
|
+
await this.setLocalShareVideoStream(streams.screenShare?.video);
|
|
7123
7194
|
|
|
7124
7195
|
floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
|
|
7125
7196
|
}
|
|
7126
7197
|
|
|
7127
|
-
if (
|
|
7128
|
-
await this.
|
|
7198
|
+
if (streams.microphone) {
|
|
7199
|
+
await this.setLocalAudioStream(streams.microphone);
|
|
7129
7200
|
}
|
|
7130
7201
|
|
|
7131
|
-
if (
|
|
7132
|
-
await this.
|
|
7202
|
+
if (streams.camera) {
|
|
7203
|
+
await this.setLocalVideoStream(streams.camera);
|
|
7133
7204
|
}
|
|
7134
7205
|
|
|
7135
7206
|
if (!this.isMultistream) {
|
|
@@ -7145,31 +7216,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7145
7216
|
}
|
|
7146
7217
|
|
|
7147
7218
|
/**
|
|
7148
|
-
* Un-publishes specified local
|
|
7219
|
+
* Un-publishes specified local streams in the meeting
|
|
7149
7220
|
*
|
|
7150
|
-
* @param {Array<
|
|
7221
|
+
* @param {Array<LocalStream>} streams
|
|
7151
7222
|
* @returns {Promise}
|
|
7152
7223
|
*/
|
|
7153
|
-
async
|
|
7224
|
+
async unpublishStreams(streams: LocalStream[]): Promise<void> {
|
|
7154
7225
|
this.checkMediaConnection();
|
|
7155
7226
|
|
|
7156
7227
|
const promises = [];
|
|
7157
7228
|
|
|
7158
|
-
for (const
|
|
7159
|
-
if (
|
|
7160
|
-
promises.push(this.
|
|
7229
|
+
for (const stream of streams.filter((t) => !!t)) {
|
|
7230
|
+
if (stream === this.mediaProperties.shareAudioStream) {
|
|
7231
|
+
promises.push(this.setLocalShareAudioStream(undefined));
|
|
7161
7232
|
}
|
|
7162
7233
|
|
|
7163
|
-
if (
|
|
7164
|
-
promises.push(this.
|
|
7234
|
+
if (stream === this.mediaProperties.shareVideoStream) {
|
|
7235
|
+
promises.push(this.setLocalShareVideoStream(undefined));
|
|
7165
7236
|
}
|
|
7166
7237
|
|
|
7167
|
-
if (
|
|
7168
|
-
promises.push(this.
|
|
7238
|
+
if (stream === this.mediaProperties.audioStream) {
|
|
7239
|
+
promises.push(this.setLocalAudioStream(undefined));
|
|
7169
7240
|
}
|
|
7170
7241
|
|
|
7171
|
-
if (
|
|
7172
|
-
promises.push(this.
|
|
7242
|
+
if (stream === this.mediaProperties.videoStream) {
|
|
7243
|
+
promises.push(this.setLocalVideoStream(undefined));
|
|
7173
7244
|
}
|
|
7174
7245
|
}
|
|
7175
7246
|
|
|
@@ -7181,8 +7252,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7181
7252
|
|
|
7182
7253
|
// we're allowing for the SDK to support just audio share as well
|
|
7183
7254
|
// so a share could be active with only video, only audio, or both
|
|
7184
|
-
// we're only releasing the floor if both
|
|
7185
|
-
if (!this.mediaProperties.
|
|
7255
|
+
// we're only releasing the floor if both streams have ended
|
|
7256
|
+
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
7186
7257
|
try {
|
|
7187
7258
|
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
7188
7259
|
} catch (e) {
|