@webex/plugin-meetings 3.0.0-beta.250 → 3.0.0-beta.251
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 +408 -345
- 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/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 +56 -49
- package/dist/types/meeting/muteState.d.ts +16 -16
- package/dist/types/meeting/util.d.ts +2 -2
- 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 +395 -312
- package/src/meeting/muteState.ts +35 -34
- package/src/meeting/util.ts +11 -10
- 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 +768 -769
- package/test/unit/spec/meeting/muteState.js +113 -75
- package/test/unit/spec/meeting/utils.js +14 -16
- package/test/unit/spec/meetings/index.js +2 -2
- 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
|
}
|
|
@@ -2272,9 +2275,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2272
2275
|
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2273
2276
|
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2274
2277
|
) {
|
|
2275
|
-
await this.
|
|
2276
|
-
this.mediaProperties.
|
|
2277
|
-
this.mediaProperties.
|
|
2278
|
+
await this.unpublishStreams([
|
|
2279
|
+
this.mediaProperties.shareVideoStream,
|
|
2280
|
+
this.mediaProperties.shareAudioStream,
|
|
2278
2281
|
]);
|
|
2279
2282
|
}
|
|
2280
2283
|
} finally {
|
|
@@ -2778,11 +2781,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2778
2781
|
|
|
2779
2782
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
2780
2783
|
if (this.wirelessShare) {
|
|
2781
|
-
if (this.mediaProperties.
|
|
2782
|
-
await this.
|
|
2784
|
+
if (this.mediaProperties.shareVideoStream) {
|
|
2785
|
+
await this.setLocalShareVideoStream(undefined);
|
|
2783
2786
|
}
|
|
2784
|
-
if (this.mediaProperties.
|
|
2785
|
-
await this.
|
|
2787
|
+
if (this.mediaProperties.shareAudioStream) {
|
|
2788
|
+
await this.setLocalShareAudioStream(undefined);
|
|
2786
2789
|
}
|
|
2787
2790
|
}
|
|
2788
2791
|
// when multiple WEB deviceType join with same user
|
|
@@ -3365,11 +3368,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3365
3368
|
}
|
|
3366
3369
|
|
|
3367
3370
|
/**
|
|
3368
|
-
* Removes remote audio, video and share
|
|
3371
|
+
* Removes remote audio, video and share streams from class instance's mediaProperties
|
|
3369
3372
|
* @returns {undefined}
|
|
3370
3373
|
*/
|
|
3371
|
-
|
|
3372
|
-
this.mediaProperties.
|
|
3374
|
+
unsetRemoteStreams() {
|
|
3375
|
+
this.mediaProperties.unsetRemoteStreams();
|
|
3373
3376
|
}
|
|
3374
3377
|
|
|
3375
3378
|
/**
|
|
@@ -3384,17 +3387,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3384
3387
|
LoggerProxy.logger.warn(
|
|
3385
3388
|
'Meeting:index#closeRemoteStream --> [DEPRECATION WARNING]: closeRemoteStream has been deprecated after v1.89.3'
|
|
3386
3389
|
);
|
|
3387
|
-
this.
|
|
3390
|
+
this.closeRemoteStreams();
|
|
3388
3391
|
}
|
|
3389
3392
|
|
|
3390
3393
|
/**
|
|
3391
|
-
* Removes the remote
|
|
3394
|
+
* Removes the remote streams on the class instance and triggers an event
|
|
3392
3395
|
* to developers
|
|
3393
3396
|
* @returns {undefined}
|
|
3394
3397
|
* @memberof Meeting
|
|
3395
3398
|
*/
|
|
3396
|
-
|
|
3397
|
-
const {
|
|
3399
|
+
closeRemoteStreams() {
|
|
3400
|
+
const {remoteAudioStream, remoteVideoStream, remoteShareStream, shareAudioStream} =
|
|
3401
|
+
this.mediaProperties;
|
|
3398
3402
|
|
|
3399
3403
|
/**
|
|
3400
3404
|
* Triggers an event to the developer
|
|
@@ -3408,7 +3412,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3408
3412
|
this,
|
|
3409
3413
|
{
|
|
3410
3414
|
file: 'meeting/index',
|
|
3411
|
-
function: '
|
|
3415
|
+
function: 'closeRemoteStreams',
|
|
3412
3416
|
},
|
|
3413
3417
|
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3414
3418
|
{
|
|
@@ -3418,193 +3422,240 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3418
3422
|
};
|
|
3419
3423
|
|
|
3420
3424
|
/**
|
|
3421
|
-
* Stops a media
|
|
3422
|
-
* @param {
|
|
3423
|
-
* @param {string} type Media
|
|
3425
|
+
* Stops a media stream and emits an event
|
|
3426
|
+
* @param {RemoteStream} stream Media stream to stop
|
|
3427
|
+
* @param {string} type Media stream type
|
|
3424
3428
|
* @returns {Promise}
|
|
3425
3429
|
* @inner
|
|
3426
3430
|
*/
|
|
3427
3431
|
// 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
|
-
}
|
|
3432
|
+
const stopStream = (stream: RemoteStream, type: string) => {
|
|
3433
|
+
return Media.stopStream(stream).then(() => {
|
|
3434
|
+
triggerMediaStoppedEvent(type);
|
|
3440
3435
|
});
|
|
3441
3436
|
};
|
|
3442
3437
|
|
|
3443
3438
|
return Promise.all([
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3439
|
+
stopStream(remoteAudioStream, EVENT_TYPES.REMOTE_AUDIO),
|
|
3440
|
+
stopStream(remoteVideoStream, EVENT_TYPES.REMOTE_VIDEO),
|
|
3441
|
+
stopStream(remoteShareStream, EVENT_TYPES.REMOTE_SHARE),
|
|
3442
|
+
stopStream(shareAudioStream, EVENT_TYPES.REMOTE_SHARE_AUDIO),
|
|
3447
3443
|
]);
|
|
3448
3444
|
}
|
|
3449
3445
|
|
|
3450
3446
|
/**
|
|
3451
|
-
* Stores the reference to a new microphone
|
|
3452
|
-
* on it, cleans up previous
|
|
3447
|
+
* Stores the reference to a new microphone stream, sets up the required event listeners
|
|
3448
|
+
* on it, cleans up previous stream, etc.
|
|
3453
3449
|
*
|
|
3454
|
-
* @param {
|
|
3450
|
+
* @param {LocalMicrophoneStream | null} localStream local microphone stream
|
|
3455
3451
|
* @returns {Promise<void>}
|
|
3456
3452
|
*/
|
|
3457
|
-
private async
|
|
3458
|
-
const
|
|
3453
|
+
private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
|
|
3454
|
+
const oldStream = this.mediaProperties.audioStream;
|
|
3459
3455
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3456
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3457
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3462
3458
|
|
|
3463
3459
|
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3464
|
-
this.mediaProperties.
|
|
3460
|
+
this.mediaProperties.setLocalAudioStream(localStream);
|
|
3465
3461
|
|
|
3466
|
-
this.audio.
|
|
3462
|
+
this.audio.handleLocalStreamChange(this);
|
|
3467
3463
|
|
|
3468
|
-
|
|
3469
|
-
|
|
3464
|
+
localStream?.on(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3465
|
+
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3470
3466
|
|
|
3471
|
-
if (!this.isMultistream || !
|
|
3472
|
-
// for multistream WCME automatically un-publishes the old
|
|
3473
|
-
await this.
|
|
3467
|
+
if (!this.isMultistream || !localStream) {
|
|
3468
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3469
|
+
await this.unpublishStream(MediaType.AudioMain, oldStream);
|
|
3474
3470
|
}
|
|
3475
|
-
await this.
|
|
3471
|
+
await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
|
|
3476
3472
|
}
|
|
3477
3473
|
|
|
3478
3474
|
/**
|
|
3479
|
-
* Stores the reference to a new camera
|
|
3480
|
-
* on it, cleans up previous
|
|
3475
|
+
* Stores the reference to a new camera stream, sets up the required event listeners
|
|
3476
|
+
* on it, cleans up previous stream, etc.
|
|
3481
3477
|
*
|
|
3482
|
-
* @param {
|
|
3478
|
+
* @param {LocalCameraStream | null} localStream local camera stream
|
|
3483
3479
|
* @returns {Promise<void>}
|
|
3484
3480
|
*/
|
|
3485
|
-
private async
|
|
3486
|
-
const
|
|
3481
|
+
private async setLocalVideoStream(localStream?: LocalCameraStream) {
|
|
3482
|
+
const oldStream = this.mediaProperties.videoStream;
|
|
3487
3483
|
|
|
3488
|
-
|
|
3489
|
-
|
|
3484
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3485
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3490
3486
|
|
|
3491
3487
|
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3492
|
-
this.mediaProperties.
|
|
3488
|
+
this.mediaProperties.setLocalVideoStream(localStream);
|
|
3493
3489
|
|
|
3494
|
-
this.video.
|
|
3490
|
+
this.video.handleLocalStreamChange(this);
|
|
3495
3491
|
|
|
3496
|
-
|
|
3497
|
-
|
|
3492
|
+
localStream?.on(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3493
|
+
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3498
3494
|
|
|
3499
|
-
if (!this.isMultistream || !
|
|
3500
|
-
// for multistream WCME automatically un-publishes the old
|
|
3501
|
-
await this.
|
|
3495
|
+
if (!this.isMultistream || !localStream) {
|
|
3496
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3497
|
+
await this.unpublishStream(MediaType.VideoMain, oldStream);
|
|
3502
3498
|
}
|
|
3503
|
-
await this.
|
|
3499
|
+
await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
|
|
3504
3500
|
}
|
|
3505
3501
|
|
|
3506
3502
|
/**
|
|
3507
|
-
* Stores the reference to a new screen share
|
|
3508
|
-
* on it, cleans up previous
|
|
3503
|
+
* Stores the reference to a new screen share stream, sets up the required event listeners
|
|
3504
|
+
* on it, cleans up previous stream, etc.
|
|
3505
|
+
* It also sends the floor grant/release request.
|
|
3509
3506
|
*
|
|
3510
|
-
* @param {
|
|
3507
|
+
* @param {LocalDisplayStream | undefined} localDisplayStream local display stream
|
|
3511
3508
|
* @returns {Promise<void>}
|
|
3512
3509
|
*/
|
|
3513
|
-
private async
|
|
3514
|
-
const
|
|
3510
|
+
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3511
|
+
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3515
3512
|
|
|
3516
|
-
|
|
3517
|
-
|
|
3513
|
+
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3514
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3518
3515
|
|
|
3519
|
-
this.mediaProperties.
|
|
3516
|
+
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3520
3517
|
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
this.
|
|
3518
|
+
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3519
|
+
localDisplayStream?.on(
|
|
3520
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3521
|
+
this.localOutputTrackChangeHandler
|
|
3525
3522
|
);
|
|
3526
3523
|
|
|
3527
|
-
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.
|
|
3524
|
+
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
|
|
3528
3525
|
|
|
3529
|
-
if (!this.isMultistream || !
|
|
3530
|
-
// for multistream WCME automatically un-publishes the old
|
|
3531
|
-
await this.
|
|
3526
|
+
if (!this.isMultistream || !localDisplayStream) {
|
|
3527
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3528
|
+
await this.unpublishStream(MediaType.VideoSlides, oldStream);
|
|
3532
3529
|
}
|
|
3533
|
-
await this.
|
|
3530
|
+
await this.publishStream(MediaType.VideoSlides, this.mediaProperties.shareVideoStream);
|
|
3534
3531
|
}
|
|
3535
3532
|
|
|
3536
3533
|
/**
|
|
3537
|
-
* Stores the reference to a new screen share audio
|
|
3538
|
-
* on it, cleans up previous
|
|
3534
|
+
* Stores the reference to a new screen share audio stream, sets up the required event listeners
|
|
3535
|
+
* on it, cleans up previous stream, etc.
|
|
3539
3536
|
*
|
|
3540
|
-
* @param {
|
|
3537
|
+
* @param {LocalSystemAudioStream | undefined} localSystemAudioStream local system audio stream
|
|
3541
3538
|
* @returns {Promise<void>}
|
|
3542
3539
|
*/
|
|
3543
|
-
private async
|
|
3544
|
-
const
|
|
3540
|
+
private async setLocalShareAudioStream(localSystemAudioStream?: LocalSystemAudioStream) {
|
|
3541
|
+
const oldStream = this.mediaProperties.shareAudioStream;
|
|
3545
3542
|
|
|
3546
|
-
|
|
3547
|
-
|
|
3543
|
+
oldStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3544
|
+
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3548
3545
|
|
|
3549
|
-
this.mediaProperties.
|
|
3546
|
+
this.mediaProperties.setLocalShareAudioStream(localSystemAudioStream);
|
|
3550
3547
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
this.
|
|
3548
|
+
localSystemAudioStream?.on(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3549
|
+
localSystemAudioStream?.on(
|
|
3550
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3551
|
+
this.localOutputTrackChangeHandler
|
|
3555
3552
|
);
|
|
3556
3553
|
|
|
3557
|
-
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.
|
|
3554
|
+
this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
|
|
3558
3555
|
|
|
3559
|
-
if (!this.isMultistream || !
|
|
3560
|
-
|
|
3556
|
+
if (!this.isMultistream || !localSystemAudioStream) {
|
|
3557
|
+
// for multistream WCME automatically un-publishes the old stream when we publish a new one
|
|
3558
|
+
await this.unpublishStream(MediaType.AudioSlides, oldStream);
|
|
3561
3559
|
}
|
|
3562
|
-
await this.
|
|
3560
|
+
await this.publishStream(MediaType.AudioSlides, this.mediaProperties.shareAudioStream);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
/**
|
|
3564
|
+
* Handles the local audio stream publish state change event
|
|
3565
|
+
* @internal
|
|
3566
|
+
* @param {Object} options parameters functionName, isPublished, mediaType and stream needed to trigger event
|
|
3567
|
+
* @returns {undefined}
|
|
3568
|
+
*/
|
|
3569
|
+
private emitPublishStateChangeEvent(options: {
|
|
3570
|
+
functionName: string;
|
|
3571
|
+
isPublished: boolean;
|
|
3572
|
+
mediaType: MediaType;
|
|
3573
|
+
stream: MediaStream;
|
|
3574
|
+
}) {
|
|
3575
|
+
const {functionName, isPublished, mediaType, stream} = options;
|
|
3576
|
+
Trigger.trigger(
|
|
3577
|
+
this,
|
|
3578
|
+
{
|
|
3579
|
+
file: 'meeting/index',
|
|
3580
|
+
function: functionName,
|
|
3581
|
+
},
|
|
3582
|
+
EVENT_TRIGGERS.MEETING_STREAM_PUBLISH_STATE_CHANGED,
|
|
3583
|
+
{
|
|
3584
|
+
isPublished,
|
|
3585
|
+
mediaType,
|
|
3586
|
+
stream,
|
|
3587
|
+
}
|
|
3588
|
+
);
|
|
3563
3589
|
}
|
|
3564
3590
|
|
|
3565
3591
|
/**
|
|
3566
|
-
* Removes references to local
|
|
3592
|
+
* Removes references to local streams. This function should be called
|
|
3567
3593
|
* on cleanup when we leave the meeting etc.
|
|
3568
3594
|
*
|
|
3569
3595
|
* @internal
|
|
3570
3596
|
* @returns {void}
|
|
3571
3597
|
*/
|
|
3572
|
-
public
|
|
3573
|
-
const {
|
|
3598
|
+
public cleanupLocalStreams() {
|
|
3599
|
+
const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
|
|
3574
3600
|
|
|
3575
|
-
|
|
3576
|
-
|
|
3601
|
+
audioStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
|
|
3602
|
+
audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3577
3603
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3604
|
+
videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3605
|
+
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3580
3606
|
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
this.
|
|
3607
|
+
shareAudioStream?.off(StreamEventNames.MuteStateChange, this.handleShareAudioStreamEnded);
|
|
3608
|
+
shareAudioStream?.off(
|
|
3609
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3610
|
+
this.localOutputTrackChangeHandler
|
|
3585
3611
|
);
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
this.underlyingLocalTrackChangeHandler
|
|
3612
|
+
shareVideoStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamEnded);
|
|
3613
|
+
shareVideoStream?.off(
|
|
3614
|
+
LocalStreamEventNames.OutputTrackChange,
|
|
3615
|
+
this.localOutputTrackChangeHandler
|
|
3591
3616
|
);
|
|
3592
3617
|
|
|
3593
|
-
this.mediaProperties.
|
|
3594
|
-
this.mediaProperties.
|
|
3595
|
-
this.mediaProperties.
|
|
3596
|
-
this.mediaProperties.
|
|
3618
|
+
this.mediaProperties.setLocalAudioStream(undefined);
|
|
3619
|
+
this.mediaProperties.setLocalVideoStream(undefined);
|
|
3620
|
+
this.mediaProperties.setLocalShareAudioStream(undefined);
|
|
3621
|
+
this.mediaProperties.setLocalShareVideoStream(undefined);
|
|
3597
3622
|
|
|
3598
3623
|
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3599
3624
|
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3600
3625
|
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3601
3626
|
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3627
|
+
if (audioStream) {
|
|
3628
|
+
this.emitPublishStateChangeEvent({
|
|
3629
|
+
functionName: 'cleanupLocalStreams',
|
|
3630
|
+
isPublished: false,
|
|
3631
|
+
mediaType: MediaType.AudioMain,
|
|
3632
|
+
stream: audioStream,
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
if (videoStream) {
|
|
3636
|
+
this.emitPublishStateChangeEvent({
|
|
3637
|
+
functionName: 'cleanupLocalStreams',
|
|
3638
|
+
isPublished: false,
|
|
3639
|
+
mediaType: MediaType.VideoMain,
|
|
3640
|
+
stream: videoStream,
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
if (shareVideoStream) {
|
|
3644
|
+
this.emitPublishStateChangeEvent({
|
|
3645
|
+
functionName: 'cleanupLocalStreams',
|
|
3646
|
+
isPublished: false,
|
|
3647
|
+
mediaType: MediaType.VideoSlides,
|
|
3648
|
+
stream: shareVideoStream,
|
|
3649
|
+
});
|
|
3650
|
+
}
|
|
3651
|
+
if (shareAudioStream) {
|
|
3652
|
+
this.emitPublishStateChangeEvent({
|
|
3653
|
+
functionName: 'cleanupLocalStreams',
|
|
3654
|
+
isPublished: false,
|
|
3655
|
+
mediaType: MediaType.AudioSlides,
|
|
3656
|
+
stream: shareAudioStream,
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3608
3659
|
}
|
|
3609
3660
|
|
|
3610
3661
|
/**
|
|
@@ -3671,6 +3722,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3671
3722
|
|
|
3672
3723
|
this.receiveSlotManager.reset();
|
|
3673
3724
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3725
|
+
this.sendSlotManager.reset();
|
|
3674
3726
|
}
|
|
3675
3727
|
|
|
3676
3728
|
this.audio = null;
|
|
@@ -3744,7 +3796,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3744
3796
|
this.audio
|
|
3745
3797
|
.handleClientRequest(this, true)
|
|
3746
3798
|
.then(() => {
|
|
3747
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.
|
|
3799
|
+
MeetingUtil.handleAudioLogging(this.mediaProperties.audioStream);
|
|
3748
3800
|
// @ts-ignore
|
|
3749
3801
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3750
3802
|
name: 'client.muted',
|
|
@@ -3794,7 +3846,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3794
3846
|
this.audio
|
|
3795
3847
|
.handleClientRequest(this, false)
|
|
3796
3848
|
.then(() => {
|
|
3797
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.
|
|
3849
|
+
MeetingUtil.handleAudioLogging(this.mediaProperties.audioStream);
|
|
3798
3850
|
// @ts-ignore
|
|
3799
3851
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3800
3852
|
name: 'client.unmuted',
|
|
@@ -3843,7 +3895,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3843
3895
|
this.video
|
|
3844
3896
|
.handleClientRequest(this, true)
|
|
3845
3897
|
.then(() => {
|
|
3846
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.
|
|
3898
|
+
MeetingUtil.handleVideoLogging(this.mediaProperties.videoStream);
|
|
3847
3899
|
// @ts-ignore
|
|
3848
3900
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3849
3901
|
name: 'client.muted',
|
|
@@ -3892,7 +3944,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3892
3944
|
this.video
|
|
3893
3945
|
.handleClientRequest(this, false)
|
|
3894
3946
|
.then(() => {
|
|
3895
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.
|
|
3947
|
+
MeetingUtil.handleVideoLogging(this.mediaProperties.videoStream);
|
|
3896
3948
|
// @ts-ignore
|
|
3897
3949
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
3898
3950
|
name: 'client.unmuted',
|
|
@@ -3928,7 +3980,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3928
3980
|
* joinWithMedia({
|
|
3929
3981
|
* joinOptions: {resourceId: 'resourceId' },
|
|
3930
3982
|
* mediaOptions: {
|
|
3931
|
-
*
|
|
3983
|
+
* localStreams: { microphone: microphoneStream, camera: cameraStream }
|
|
3932
3984
|
* }
|
|
3933
3985
|
* })
|
|
3934
3986
|
*/
|
|
@@ -4700,7 +4752,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4700
4752
|
});
|
|
4701
4753
|
|
|
4702
4754
|
this.locusInfo.once(LOCUSINFO.EVENTS.SELF_OBSERVING, async () => {
|
|
4703
|
-
// Clean up the camera , microphone
|
|
4755
|
+
// Clean up the camera , microphone stream and re initiate it
|
|
4704
4756
|
|
|
4705
4757
|
try {
|
|
4706
4758
|
if (this.screenShareFloorState === ScreenShareFloorStatus.GRANTED) {
|
|
@@ -4717,7 +4769,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4717
4769
|
},
|
|
4718
4770
|
};
|
|
4719
4771
|
|
|
4720
|
-
this.
|
|
4772
|
+
this.cleanupLocalStreams();
|
|
4721
4773
|
|
|
4722
4774
|
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4723
4775
|
this.mediaProperties.unsetRemoteMedia();
|
|
@@ -4999,46 +5051,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4999
5051
|
)}`
|
|
5000
5052
|
);
|
|
5001
5053
|
|
|
5002
|
-
|
|
5054
|
+
if (event.track) {
|
|
5055
|
+
const mediaTrack = event.track;
|
|
5056
|
+
const remoteStream = new RemoteStream(MediaUtil.createMediaStream([mediaTrack]));
|
|
5003
5057
|
|
|
5004
|
-
|
|
5005
|
-
|
|
5058
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
5059
|
+
let eventType;
|
|
5006
5060
|
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
if (event.track) {
|
|
5061
|
+
switch (event.type) {
|
|
5062
|
+
case RemoteTrackType.AUDIO:
|
|
5063
|
+
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
5064
|
+
this.mediaProperties.setRemoteAudioStream(remoteStream);
|
|
5065
|
+
break;
|
|
5066
|
+
case RemoteTrackType.VIDEO:
|
|
5067
|
+
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
5068
|
+
this.mediaProperties.setRemoteVideoStream(remoteStream);
|
|
5069
|
+
break;
|
|
5070
|
+
case RemoteTrackType.SCREENSHARE_VIDEO:
|
|
5018
5071
|
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
5019
|
-
this.mediaProperties.
|
|
5072
|
+
this.mediaProperties.setRemoteShareStream(remoteStream);
|
|
5073
|
+
break;
|
|
5074
|
+
default: {
|
|
5075
|
+
LoggerProxy.logger.log(
|
|
5076
|
+
'Meeting:index#setupMediaConnectionListeners --> unexpected track'
|
|
5077
|
+
);
|
|
5020
5078
|
}
|
|
5021
|
-
break;
|
|
5022
|
-
default: {
|
|
5023
|
-
LoggerProxy.logger.log(
|
|
5024
|
-
'Meeting:index#setupMediaConnectionListeners --> unexpected track'
|
|
5025
|
-
);
|
|
5026
5079
|
}
|
|
5027
|
-
}
|
|
5028
5080
|
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5081
|
+
if (eventType && mediaTrack) {
|
|
5082
|
+
Trigger.trigger(
|
|
5083
|
+
this,
|
|
5084
|
+
{
|
|
5085
|
+
file: 'meeting/index',
|
|
5086
|
+
function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED',
|
|
5087
|
+
},
|
|
5088
|
+
EVENT_TRIGGERS.MEDIA_READY,
|
|
5089
|
+
{
|
|
5090
|
+
type: eventType,
|
|
5091
|
+
stream: remoteStream.outputStream,
|
|
5092
|
+
}
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5042
5095
|
}
|
|
5043
5096
|
});
|
|
5044
5097
|
|
|
@@ -5127,7 +5180,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5127
5180
|
}
|
|
5128
5181
|
});
|
|
5129
5182
|
|
|
5130
|
-
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (
|
|
5183
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (csis) => {
|
|
5131
5184
|
Trigger.trigger(
|
|
5132
5185
|
this,
|
|
5133
5186
|
{
|
|
@@ -5136,8 +5189,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5136
5189
|
},
|
|
5137
5190
|
EVENT_TRIGGERS.ACTIVE_SPEAKER_CHANGED,
|
|
5138
5191
|
{
|
|
5139
|
-
|
|
5140
|
-
memberIds: msg.csis
|
|
5192
|
+
memberIds: csis
|
|
5141
5193
|
// @ts-ignore
|
|
5142
5194
|
.map((csi) => this.members.findMemberByCsi(csi)?.id)
|
|
5143
5195
|
.filter((item) => item !== undefined),
|
|
@@ -5281,10 +5333,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5281
5333
|
}
|
|
5282
5334
|
|
|
5283
5335
|
/**
|
|
5284
|
-
* Creates a webrtc media connection and publishes
|
|
5336
|
+
* Creates a webrtc media connection and publishes streams to it
|
|
5285
5337
|
*
|
|
5286
5338
|
* @param {Object} turnServerInfo TURN server information
|
|
5287
5339
|
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
5340
|
+
* @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
|
|
5288
5341
|
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
5289
5342
|
*/
|
|
5290
5343
|
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
@@ -5310,18 +5363,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5310
5363
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
5311
5364
|
this.setupMediaConnectionListeners();
|
|
5312
5365
|
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5366
|
+
if (this.isMultistream) {
|
|
5367
|
+
const [audioEnabled, videoEnabled, shareEnabled] = [
|
|
5368
|
+
this.mediaProperties.mediaDirection.sendAudio ||
|
|
5369
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
5370
|
+
this.mediaProperties.mediaDirection.sendVideo ||
|
|
5371
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
5372
|
+
this.mediaProperties.mediaDirection.sendShare ||
|
|
5373
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
5374
|
+
];
|
|
5375
|
+
|
|
5376
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoMain, audioEnabled);
|
|
5377
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioMain, videoEnabled);
|
|
5378
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoSlides, shareEnabled);
|
|
5379
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioSlides, shareEnabled);
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5382
|
+
// publish the streams
|
|
5383
|
+
if (this.mediaProperties.audioStream) {
|
|
5384
|
+
await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
|
|
5316
5385
|
}
|
|
5317
|
-
if (this.mediaProperties.
|
|
5318
|
-
await this.
|
|
5386
|
+
if (this.mediaProperties.videoStream) {
|
|
5387
|
+
await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
|
|
5319
5388
|
}
|
|
5320
|
-
if (this.mediaProperties.
|
|
5321
|
-
await this.
|
|
5389
|
+
if (this.mediaProperties.shareVideoStream) {
|
|
5390
|
+
await this.publishStream(MediaType.VideoSlides, this.mediaProperties.shareVideoStream);
|
|
5322
5391
|
}
|
|
5323
|
-
if (this.isMultistream && this.mediaProperties.
|
|
5324
|
-
await this.
|
|
5392
|
+
if (this.isMultistream && this.mediaProperties.shareAudioStream) {
|
|
5393
|
+
await this.publishStream(MediaType.AudioSlides, this.mediaProperties.shareAudioStream);
|
|
5325
5394
|
}
|
|
5326
5395
|
|
|
5327
5396
|
return mc;
|
|
@@ -5375,10 +5444,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5375
5444
|
}
|
|
5376
5445
|
|
|
5377
5446
|
const {
|
|
5378
|
-
|
|
5447
|
+
localStreams,
|
|
5379
5448
|
audioEnabled = true,
|
|
5380
5449
|
videoEnabled = true,
|
|
5381
|
-
|
|
5450
|
+
shareAudioEnabled = true,
|
|
5451
|
+
shareVideoEnabled = true,
|
|
5382
5452
|
remoteMediaManagerConfig,
|
|
5383
5453
|
bundlePolicy,
|
|
5384
5454
|
allowMediaInLobby,
|
|
@@ -5416,7 +5486,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5416
5486
|
options: {meetingId: this.id},
|
|
5417
5487
|
});
|
|
5418
5488
|
|
|
5419
|
-
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any
|
|
5489
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
|
|
5420
5490
|
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5421
5491
|
this.mediaProperties.setMediaDirection({
|
|
5422
5492
|
sendAudio: audioEnabled,
|
|
@@ -5424,7 +5494,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5424
5494
|
sendShare: false,
|
|
5425
5495
|
receiveAudio: audioEnabled,
|
|
5426
5496
|
receiveVideo: videoEnabled,
|
|
5427
|
-
receiveShare,
|
|
5497
|
+
receiveShare: shareAudioEnabled || shareVideoEnabled,
|
|
5428
5498
|
});
|
|
5429
5499
|
|
|
5430
5500
|
this.locusMediaRequest = new LocusMediaRequest(
|
|
@@ -5451,19 +5521,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5451
5521
|
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5452
5522
|
const promises = [];
|
|
5453
5523
|
|
|
5454
|
-
// setup all the references to local
|
|
5524
|
+
// setup all the references to local streams in this.mediaProperties before creating media connection
|
|
5455
5525
|
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5456
|
-
if (
|
|
5457
|
-
promises.push(this.
|
|
5526
|
+
if (localStreams?.microphone) {
|
|
5527
|
+
promises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
5458
5528
|
}
|
|
5459
|
-
if (
|
|
5460
|
-
promises.push(this.
|
|
5529
|
+
if (localStreams?.camera) {
|
|
5530
|
+
promises.push(this.setLocalVideoStream(localStreams.camera));
|
|
5461
5531
|
}
|
|
5462
|
-
if (
|
|
5463
|
-
promises.push(this.
|
|
5532
|
+
if (localStreams?.screenShare?.video) {
|
|
5533
|
+
promises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
5464
5534
|
}
|
|
5465
|
-
if (
|
|
5466
|
-
promises.push(this.
|
|
5535
|
+
if (localStreams?.screenShare?.audio) {
|
|
5536
|
+
promises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
5467
5537
|
}
|
|
5468
5538
|
|
|
5469
5539
|
return Promise.all(promises)
|
|
@@ -5595,7 +5665,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5595
5665
|
})
|
|
5596
5666
|
)
|
|
5597
5667
|
.then(() => {
|
|
5598
|
-
if (this.mediaProperties.
|
|
5668
|
+
if (this.mediaProperties.hasLocalShareStream()) {
|
|
5599
5669
|
return this.enqueueScreenShareFloorRequest();
|
|
5600
5670
|
}
|
|
5601
5671
|
|
|
@@ -5805,11 +5875,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5805
5875
|
public async updateMedia(options: {
|
|
5806
5876
|
audioEnabled?: boolean;
|
|
5807
5877
|
videoEnabled?: boolean;
|
|
5808
|
-
|
|
5878
|
+
shareAudioEnabled?: boolean;
|
|
5879
|
+
shareVideoEnabled?: boolean;
|
|
5809
5880
|
}) {
|
|
5810
5881
|
this.checkMediaConnection();
|
|
5811
5882
|
|
|
5812
|
-
const {audioEnabled, videoEnabled,
|
|
5883
|
+
const {audioEnabled, videoEnabled, shareAudioEnabled, shareVideoEnabled} = options;
|
|
5813
5884
|
|
|
5814
5885
|
LoggerProxy.logger.log(
|
|
5815
5886
|
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
@@ -5820,40 +5891,40 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5820
5891
|
}
|
|
5821
5892
|
|
|
5822
5893
|
if (this.isMultistream) {
|
|
5823
|
-
if (
|
|
5894
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
5824
5895
|
throw new Error(
|
|
5825
|
-
'
|
|
5826
|
-
);
|
|
5827
|
-
}
|
|
5828
|
-
|
|
5829
|
-
if (receiveShare !== undefined) {
|
|
5830
|
-
throw new Error(
|
|
5831
|
-
'toggling receiveShare in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5896
|
+
'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5832
5897
|
);
|
|
5833
5898
|
}
|
|
5899
|
+
} else if (shareAudioEnabled !== undefined) {
|
|
5900
|
+
throw new Error(
|
|
5901
|
+
'toggling shareAudioEnabled in a transcoded meeting is not supported as of now'
|
|
5902
|
+
);
|
|
5834
5903
|
}
|
|
5835
5904
|
|
|
5836
5905
|
if (audioEnabled !== undefined) {
|
|
5837
5906
|
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5838
5907
|
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5839
5908
|
this.audio.enable(this, audioEnabled);
|
|
5909
|
+
if (this.isMultistream) {
|
|
5910
|
+
this.sendSlotManager.setActive(MediaType.AudioMain, audioEnabled);
|
|
5911
|
+
}
|
|
5840
5912
|
}
|
|
5841
5913
|
|
|
5842
5914
|
if (videoEnabled !== undefined) {
|
|
5843
5915
|
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5844
5916
|
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5845
5917
|
this.video.enable(this, videoEnabled);
|
|
5918
|
+
if (this.isMultistream) {
|
|
5919
|
+
this.sendSlotManager.setActive(MediaType.VideoMain, videoEnabled);
|
|
5920
|
+
}
|
|
5846
5921
|
}
|
|
5847
5922
|
|
|
5848
|
-
if (
|
|
5849
|
-
this.mediaProperties.mediaDirection.receiveShare =
|
|
5923
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
5924
|
+
this.mediaProperties.mediaDirection.receiveShare = !!(shareAudioEnabled || shareVideoEnabled);
|
|
5850
5925
|
}
|
|
5851
5926
|
|
|
5852
|
-
if (this.isMultistream) {
|
|
5853
|
-
if (audioEnabled !== undefined) {
|
|
5854
|
-
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5855
|
-
}
|
|
5856
|
-
} else {
|
|
5927
|
+
if (!this.isMultistream) {
|
|
5857
5928
|
await this.updateTranscodedMediaConnection();
|
|
5858
5929
|
}
|
|
5859
5930
|
|
|
@@ -6188,15 +6259,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6188
6259
|
*/
|
|
6189
6260
|
private requestScreenShareFloor() {
|
|
6190
6261
|
if (
|
|
6191
|
-
!this.mediaProperties.
|
|
6262
|
+
!this.mediaProperties.hasLocalShareStream() ||
|
|
6192
6263
|
!this.mediaProperties.mediaDirection.sendShare
|
|
6193
6264
|
) {
|
|
6194
6265
|
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
|
-
})`
|
|
6266
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share stream anymore (shareStream=${
|
|
6267
|
+
this.mediaProperties.shareVideoStream ? 'yes' : 'no'
|
|
6268
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
6200
6269
|
);
|
|
6201
6270
|
this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
|
|
6202
6271
|
|
|
@@ -6496,12 +6565,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6496
6565
|
} = {} as any
|
|
6497
6566
|
) {
|
|
6498
6567
|
const {main, content} = renderInfo;
|
|
6499
|
-
const {mediaDirection,
|
|
6568
|
+
const {mediaDirection, remoteShareStream, remoteVideoStream} = this.mediaProperties;
|
|
6500
6569
|
|
|
6501
6570
|
const layoutInfo = cloneDeep(this.lastVideoLayoutInfo);
|
|
6502
6571
|
|
|
6503
6572
|
// TODO: We need a real time value for Audio, Video and Share send indicator
|
|
6504
|
-
if (mediaDirection.receiveVideo !== true || !
|
|
6573
|
+
if (mediaDirection.receiveVideo !== true || !remoteVideoStream) {
|
|
6505
6574
|
return this.rejectWithErrorLog(
|
|
6506
6575
|
'Meeting:index#changeVideoLayout --> cannot change video layout, you are not recieving any video/share stream'
|
|
6507
6576
|
);
|
|
@@ -6532,7 +6601,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6532
6601
|
}
|
|
6533
6602
|
|
|
6534
6603
|
if (content) {
|
|
6535
|
-
if (this.mediaProperties.mediaDirection.receiveShare &&
|
|
6604
|
+
if (this.mediaProperties.mediaDirection.receiveShare && remoteShareStream) {
|
|
6536
6605
|
const contentWidth = Math.round(content.width);
|
|
6537
6606
|
const contentHeight = Math.round(content.height);
|
|
6538
6607
|
|
|
@@ -6630,23 +6699,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6630
6699
|
* @memberof Meeting
|
|
6631
6700
|
* @returns {undefined}
|
|
6632
6701
|
*/
|
|
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.
|
|
6702
|
+
private handleShareAudioStreamEnded = async () => {
|
|
6703
|
+
// current share audio stream has ended, but there might be an active
|
|
6704
|
+
// share video stream. we only leave from wireless share if share has
|
|
6705
|
+
// completely ended, which means no share audio or video streams active
|
|
6706
|
+
if (this.wirelessShare && !this.mediaProperties.shareVideoStream) {
|
|
6638
6707
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6639
6708
|
} else {
|
|
6640
6709
|
try {
|
|
6641
|
-
await this.
|
|
6710
|
+
await this.unpublishStreams([this.mediaProperties.shareAudioStream]);
|
|
6642
6711
|
} catch (error) {
|
|
6643
6712
|
LoggerProxy.logger.log(
|
|
6644
|
-
'Meeting:index#
|
|
6713
|
+
'Meeting:index#handleShareAudioStreamEnded --> Error stopping share: ',
|
|
6645
6714
|
error
|
|
6646
6715
|
);
|
|
6647
6716
|
}
|
|
6648
6717
|
}
|
|
6649
|
-
this.triggerStoppedSharing();
|
|
6650
6718
|
};
|
|
6651
6719
|
|
|
6652
6720
|
/**
|
|
@@ -6655,18 +6723,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6655
6723
|
* @memberof Meeting
|
|
6656
6724
|
* @returns {undefined}
|
|
6657
6725
|
*/
|
|
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.
|
|
6726
|
+
private handleShareVideoStreamEnded = async () => {
|
|
6727
|
+
// current share video stream has ended, but there might be an active
|
|
6728
|
+
// share audio stream. we only leave from wireless share if share has
|
|
6729
|
+
// completely ended, which means no share audio or video streams active
|
|
6730
|
+
if (this.wirelessShare && !this.mediaProperties.shareAudioStream) {
|
|
6663
6731
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6664
6732
|
} else {
|
|
6665
6733
|
try {
|
|
6666
|
-
await this.
|
|
6734
|
+
await this.unpublishStreams([this.mediaProperties.shareVideoStream]);
|
|
6667
6735
|
} catch (error) {
|
|
6668
6736
|
LoggerProxy.logger.log(
|
|
6669
|
-
'Meeting:index#
|
|
6737
|
+
'Meeting:index#handleShareVideoStreamEnded --> Error stopping share: ',
|
|
6670
6738
|
error
|
|
6671
6739
|
);
|
|
6672
6740
|
}
|
|
@@ -6681,12 +6749,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6681
6749
|
* @memberof Meeting
|
|
6682
6750
|
*/
|
|
6683
6751
|
private triggerStoppedSharing = () => {
|
|
6684
|
-
if (!this.mediaProperties.
|
|
6752
|
+
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
6685
6753
|
Trigger.trigger(
|
|
6686
6754
|
this,
|
|
6687
6755
|
{
|
|
6688
6756
|
file: 'meeting/index',
|
|
6689
|
-
function: '
|
|
6757
|
+
function: 'handleShareStreamEnded',
|
|
6690
6758
|
},
|
|
6691
6759
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
6692
6760
|
{
|
|
@@ -6729,11 +6797,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6729
6797
|
* @returns {undefined}
|
|
6730
6798
|
*/
|
|
6731
6799
|
private handleMediaLogging(mediaProperties: {
|
|
6732
|
-
|
|
6733
|
-
|
|
6800
|
+
audioStream?: LocalMicrophoneStream;
|
|
6801
|
+
videoStream?: LocalCameraStream;
|
|
6734
6802
|
}) {
|
|
6735
|
-
MeetingUtil.handleVideoLogging(mediaProperties.
|
|
6736
|
-
MeetingUtil.handleAudioLogging(mediaProperties.
|
|
6803
|
+
MeetingUtil.handleVideoLogging(mediaProperties.videoStream);
|
|
6804
|
+
MeetingUtil.handleAudioLogging(mediaProperties.audioStream);
|
|
6737
6805
|
}
|
|
6738
6806
|
|
|
6739
6807
|
/**
|
|
@@ -6973,7 +7041,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6973
7041
|
}
|
|
6974
7042
|
|
|
6975
7043
|
/**
|
|
6976
|
-
* Method to enable or disable the 'Music mode' effect on audio
|
|
7044
|
+
* Method to enable or disable the 'Music mode' effect on audio stream
|
|
6977
7045
|
*
|
|
6978
7046
|
* @param {boolean} shouldEnableMusicMode
|
|
6979
7047
|
* @returns {Promise}
|
|
@@ -6986,12 +7054,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6986
7054
|
}
|
|
6987
7055
|
|
|
6988
7056
|
if (shouldEnableMusicMode) {
|
|
6989
|
-
await this.
|
|
7057
|
+
await this.sendSlotManager.setCodecParameters(MediaType.AudioMain, {
|
|
6990
7058
|
maxaveragebitrate: '64000',
|
|
6991
7059
|
maxplaybackrate: '48000',
|
|
6992
7060
|
});
|
|
6993
7061
|
} else {
|
|
6994
|
-
await this.
|
|
7062
|
+
await this.sendSlotManager.deleteCodecParameters(MediaType.AudioMain, [
|
|
6995
7063
|
'maxaveragebitrate',
|
|
6996
7064
|
'maxplaybackrate',
|
|
6997
7065
|
]);
|
|
@@ -7013,10 +7081,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7013
7081
|
|
|
7014
7082
|
return this.mediaProperties.webrtcMediaConnection
|
|
7015
7083
|
.update({
|
|
7084
|
+
// TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now
|
|
7016
7085
|
localTracks: {
|
|
7017
|
-
audio: this.mediaProperties.
|
|
7018
|
-
video: this.mediaProperties.
|
|
7019
|
-
screenShareVideo: this.mediaProperties.
|
|
7086
|
+
audio: this.mediaProperties.audioStream?.outputTrack || null,
|
|
7087
|
+
video: this.mediaProperties.videoStream?.outputTrack || null,
|
|
7088
|
+
screenShareVideo: this.mediaProperties.shareVideoStream?.outputTrack || null,
|
|
7089
|
+
screenShareAudio: this.mediaProperties.shareAudioStream?.outputTrack || null,
|
|
7020
7090
|
},
|
|
7021
7091
|
direction: {
|
|
7022
7092
|
audio: Media.getDirection(
|
|
@@ -7055,56 +7125,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7055
7125
|
}
|
|
7056
7126
|
|
|
7057
7127
|
/**
|
|
7058
|
-
* Publishes a
|
|
7128
|
+
* Publishes a stream.
|
|
7059
7129
|
*
|
|
7060
|
-
* @param {
|
|
7130
|
+
* @param {MediaType} mediaType of the stream
|
|
7131
|
+
* @param {LocalStream} stream to publish
|
|
7061
7132
|
* @returns {Promise}
|
|
7062
7133
|
*/
|
|
7063
|
-
private async
|
|
7064
|
-
if (!
|
|
7134
|
+
private async publishStream(mediaType: MediaType, stream?: LocalStream) {
|
|
7135
|
+
if (!stream) {
|
|
7065
7136
|
return;
|
|
7066
7137
|
}
|
|
7067
7138
|
|
|
7068
7139
|
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
|
|
7140
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
7141
|
+
await this.sendSlotManager.publishStream(mediaType, stream);
|
|
7073
7142
|
}
|
|
7143
|
+
|
|
7144
|
+
this.emitPublishStateChangeEvent({
|
|
7145
|
+
isPublished: true,
|
|
7146
|
+
mediaType,
|
|
7147
|
+
stream,
|
|
7148
|
+
functionName: 'publishStream',
|
|
7149
|
+
});
|
|
7074
7150
|
}
|
|
7075
7151
|
}
|
|
7076
7152
|
|
|
7077
7153
|
/**
|
|
7078
|
-
* Un-publishes a
|
|
7154
|
+
* Un-publishes a stream.
|
|
7079
7155
|
*
|
|
7080
|
-
* @param {
|
|
7156
|
+
* @param {MediaType} mediaType of the stream
|
|
7157
|
+
* @param {LocalStream} stream to unpublish
|
|
7081
7158
|
* @returns {Promise}
|
|
7082
7159
|
*/
|
|
7083
|
-
private async
|
|
7084
|
-
if (!
|
|
7160
|
+
private async unpublishStream(mediaType: MediaType, stream?: LocalStream) {
|
|
7161
|
+
if (!stream) {
|
|
7085
7162
|
return;
|
|
7086
7163
|
}
|
|
7087
7164
|
|
|
7088
7165
|
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
7089
|
-
await this.
|
|
7090
|
-
} else {
|
|
7091
|
-
track.setPublished(false); // for multistream, this call is done by WCME
|
|
7166
|
+
await this.sendSlotManager.unpublishStream(mediaType);
|
|
7092
7167
|
}
|
|
7168
|
+
|
|
7169
|
+
this.emitPublishStateChangeEvent({
|
|
7170
|
+
isPublished: false,
|
|
7171
|
+
mediaType,
|
|
7172
|
+
stream,
|
|
7173
|
+
functionName: 'unpublishStream',
|
|
7174
|
+
});
|
|
7093
7175
|
}
|
|
7094
7176
|
|
|
7095
7177
|
/**
|
|
7096
|
-
* Publishes specified local
|
|
7178
|
+
* Publishes specified local streams in the meeting
|
|
7097
7179
|
*
|
|
7098
|
-
* @param {Object}
|
|
7180
|
+
* @param {Object} streams
|
|
7099
7181
|
* @returns {Promise}
|
|
7100
7182
|
*/
|
|
7101
|
-
async
|
|
7183
|
+
async publishStreams(streams: LocalStreams): Promise<void> {
|
|
7102
7184
|
this.checkMediaConnection();
|
|
7103
7185
|
if (
|
|
7104
|
-
!
|
|
7105
|
-
!
|
|
7106
|
-
!
|
|
7107
|
-
!
|
|
7186
|
+
!streams.microphone &&
|
|
7187
|
+
!streams.camera &&
|
|
7188
|
+
!streams.screenShare?.audio &&
|
|
7189
|
+
!streams.screenShare?.video
|
|
7108
7190
|
) {
|
|
7109
7191
|
// nothing to do
|
|
7110
7192
|
return;
|
|
@@ -7112,24 +7194,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7112
7194
|
|
|
7113
7195
|
let floorRequestNeeded = false;
|
|
7114
7196
|
|
|
7115
|
-
|
|
7116
|
-
|
|
7197
|
+
// Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting
|
|
7198
|
+
if (this.isMultistream && streams.screenShare?.audio) {
|
|
7199
|
+
await this.setLocalShareAudioStream(streams.screenShare.audio);
|
|
7117
7200
|
|
|
7118
7201
|
floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
|
|
7119
7202
|
}
|
|
7120
7203
|
|
|
7121
|
-
if (
|
|
7122
|
-
await this.
|
|
7204
|
+
if (streams.screenShare?.video) {
|
|
7205
|
+
await this.setLocalShareVideoStream(streams.screenShare?.video);
|
|
7123
7206
|
|
|
7124
7207
|
floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
|
|
7125
7208
|
}
|
|
7126
7209
|
|
|
7127
|
-
if (
|
|
7128
|
-
await this.
|
|
7210
|
+
if (streams.microphone) {
|
|
7211
|
+
await this.setLocalAudioStream(streams.microphone);
|
|
7129
7212
|
}
|
|
7130
7213
|
|
|
7131
|
-
if (
|
|
7132
|
-
await this.
|
|
7214
|
+
if (streams.camera) {
|
|
7215
|
+
await this.setLocalVideoStream(streams.camera);
|
|
7133
7216
|
}
|
|
7134
7217
|
|
|
7135
7218
|
if (!this.isMultistream) {
|
|
@@ -7145,31 +7228,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7145
7228
|
}
|
|
7146
7229
|
|
|
7147
7230
|
/**
|
|
7148
|
-
* Un-publishes specified local
|
|
7231
|
+
* Un-publishes specified local streams in the meeting
|
|
7149
7232
|
*
|
|
7150
|
-
* @param {Array<
|
|
7233
|
+
* @param {Array<LocalStream>} streams
|
|
7151
7234
|
* @returns {Promise}
|
|
7152
7235
|
*/
|
|
7153
|
-
async
|
|
7236
|
+
async unpublishStreams(streams: LocalStream[]): Promise<void> {
|
|
7154
7237
|
this.checkMediaConnection();
|
|
7155
7238
|
|
|
7156
7239
|
const promises = [];
|
|
7157
7240
|
|
|
7158
|
-
for (const
|
|
7159
|
-
if (
|
|
7160
|
-
promises.push(this.
|
|
7241
|
+
for (const stream of streams.filter((t) => !!t)) {
|
|
7242
|
+
if (stream === this.mediaProperties.shareAudioStream) {
|
|
7243
|
+
promises.push(this.setLocalShareAudioStream(undefined));
|
|
7161
7244
|
}
|
|
7162
7245
|
|
|
7163
|
-
if (
|
|
7164
|
-
promises.push(this.
|
|
7246
|
+
if (stream === this.mediaProperties.shareVideoStream) {
|
|
7247
|
+
promises.push(this.setLocalShareVideoStream(undefined));
|
|
7165
7248
|
}
|
|
7166
7249
|
|
|
7167
|
-
if (
|
|
7168
|
-
promises.push(this.
|
|
7250
|
+
if (stream === this.mediaProperties.audioStream) {
|
|
7251
|
+
promises.push(this.setLocalAudioStream(undefined));
|
|
7169
7252
|
}
|
|
7170
7253
|
|
|
7171
|
-
if (
|
|
7172
|
-
promises.push(this.
|
|
7254
|
+
if (stream === this.mediaProperties.videoStream) {
|
|
7255
|
+
promises.push(this.setLocalVideoStream(undefined));
|
|
7173
7256
|
}
|
|
7174
7257
|
}
|
|
7175
7258
|
|
|
@@ -7181,8 +7264,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7181
7264
|
|
|
7182
7265
|
// we're allowing for the SDK to support just audio share as well
|
|
7183
7266
|
// 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.
|
|
7267
|
+
// we're only releasing the floor if both streams have ended
|
|
7268
|
+
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
7186
7269
|
try {
|
|
7187
7270
|
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
7188
7271
|
} catch (e) {
|