@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.
Files changed (52) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +3 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +30 -24
  6. package/dist/index.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/media/index.js +18 -19
  10. package/dist/media/index.js.map +1 -1
  11. package/dist/media/properties.js +52 -52
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/index.js +408 -345
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/muteState.js +39 -38
  16. package/dist/meeting/muteState.js.map +1 -1
  17. package/dist/meeting/util.js +9 -9
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/multistream/sendSlotManager.js +233 -0
  20. package/dist/multistream/sendSlotManager.js.map +1 -0
  21. package/dist/reconnection-manager/index.js +10 -10
  22. package/dist/reconnection-manager/index.js.map +1 -1
  23. package/dist/types/constants.d.ts +2 -0
  24. package/dist/types/index.d.ts +1 -1
  25. package/dist/types/media/index.d.ts +2 -2
  26. package/dist/types/media/properties.d.ts +24 -24
  27. package/dist/types/meeting/index.d.ts +56 -49
  28. package/dist/types/meeting/muteState.d.ts +16 -16
  29. package/dist/types/meeting/util.d.ts +2 -2
  30. package/dist/types/multistream/sendSlotManager.d.ts +61 -0
  31. package/dist/types/reconnection-manager/index.d.ts +2 -2
  32. package/package.json +20 -20
  33. package/src/constants.ts +2 -0
  34. package/src/index.ts +14 -13
  35. package/src/media/index.ts +32 -34
  36. package/src/media/properties.ts +47 -46
  37. package/src/meeting/index.ts +395 -312
  38. package/src/meeting/muteState.ts +35 -34
  39. package/src/meeting/util.ts +11 -10
  40. package/src/multistream/sendSlotManager.ts +170 -0
  41. package/src/reconnection-manager/index.ts +8 -8
  42. package/test/integration/spec/converged-space-meetings.js +7 -7
  43. package/test/integration/spec/journey.js +85 -103
  44. package/test/integration/spec/space-meeting.js +9 -9
  45. package/test/unit/spec/media/index.ts +23 -66
  46. package/test/unit/spec/meeting/index.js +768 -769
  47. package/test/unit/spec/meeting/muteState.js +113 -75
  48. package/test/unit/spec/meeting/utils.js +14 -16
  49. package/test/unit/spec/meetings/index.js +2 -2
  50. package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
  51. package/test/unit/spec/reconnection-manager/index.js +4 -3
  52. package/test/utils/integrationTestUtils.js +4 -4
@@ -20,13 +20,14 @@ import {
20
20
 
21
21
  import {
22
22
  getDevices,
23
- LocalTrack,
24
- LocalCameraTrack,
25
- LocalDisplayTrack,
26
- LocalSystemAudioTrack,
27
- LocalMicrophoneTrack,
28
- LocalTrackEvents,
29
- TrackMuteEvent,
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 LocalTracks = {
150
- microphone?: LocalMicrophoneTrack;
151
- camera?: LocalCameraTrack;
150
+ export type LocalStreams = {
151
+ microphone?: LocalMicrophoneStream;
152
+ camera?: LocalCameraStream;
152
153
  screenShare?: {
153
- audio?: LocalSystemAudioTrack;
154
- video?: LocalDisplayTrack;
154
+ audio?: LocalSystemAudioStream;
155
+ video?: LocalDisplayStream;
155
156
  };
156
157
  };
157
158
 
158
159
  export type AddMediaOptions = {
159
- localTracks?: LocalTracks;
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
- receiveShare?: boolean; // if not specified, default value true is used
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
- localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
552
- localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
553
- underlyingLocalTrackChangeHandler: () => void;
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.localAudioTrackMuteStateHandler = (event) => {
1230
- this.audio.handleLocalTrackMuteStateChange(this, event.trackState.muted);
1232
+ this.localAudioStreamMuteStateHandler = (muted: boolean) => {
1233
+ this.audio.handleLocalStreamMuteStateChange(this, muted);
1231
1234
  };
1232
1235
 
1233
- this.localVideoTrackMuteStateHandler = (event) => {
1234
- this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
1236
+ this.localVideoStreamMuteStateHandler = (muted: boolean) => {
1237
+ this.video.handleLocalStreamMuteStateChange(this, muted);
1235
1238
  };
1236
1239
 
1237
- // The handling of underlying track changes should be done inside
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 tracks
1241
- this.underlyingLocalTrackChangeHandler = () => {
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.unpublishTracks([
2276
- this.mediaProperties.shareVideoTrack,
2277
- this.mediaProperties.shareAudioTrack,
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.shareVideoTrack) {
2782
- await this.setLocalShareVideoTrack(undefined);
2784
+ if (this.mediaProperties.shareVideoStream) {
2785
+ await this.setLocalShareVideoStream(undefined);
2783
2786
  }
2784
- if (this.mediaProperties.shareAudioTrack) {
2785
- await this.setLocalShareAudioTrack(undefined);
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 tracks from class instance's mediaProperties
3371
+ * Removes remote audio, video and share streams from class instance's mediaProperties
3369
3372
  * @returns {undefined}
3370
3373
  */
3371
- unsetRemoteTracks() {
3372
- this.mediaProperties.unsetRemoteTracks();
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.closeRemoteTracks();
3390
+ this.closeRemoteStreams();
3388
3391
  }
3389
3392
 
3390
3393
  /**
3391
- * Removes the remote tracks on the class instance and triggers an event
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
- closeRemoteTracks() {
3397
- const {remoteAudioTrack, remoteVideoTrack, remoteShare} = this.mediaProperties;
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: 'closeRemoteTracks',
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 track and emits an event
3422
- * @param {MediaStreamTrack} track Media track to stop
3423
- * @param {string} type Media track type
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 stopTrack = (track: MediaStreamTrack, type: string) => {
3429
- return Media.stopTracks(track).then(() => {
3430
- const isTrackStopped = track && track.readyState === ENDED;
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
- stopTrack(remoteAudioTrack, EVENT_TYPES.REMOTE_AUDIO),
3445
- stopTrack(remoteVideoTrack, EVENT_TYPES.REMOTE_VIDEO),
3446
- stopTrack(remoteShare, EVENT_TYPES.REMOTE_SHARE),
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 track, sets up the required event listeners
3452
- * on it, cleans up previous track, etc.
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 {LocalMicrophoneTrack | null} localTrack local microphone track
3450
+ * @param {LocalMicrophoneStream | null} localStream local microphone stream
3455
3451
  * @returns {Promise<void>}
3456
3452
  */
3457
- private async setLocalAudioTrack(localTrack?: LocalMicrophoneTrack) {
3458
- const oldTrack = this.mediaProperties.audioTrack;
3453
+ private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
3454
+ const oldStream = this.mediaProperties.audioStream;
3459
3455
 
3460
- oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
3461
- oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
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.setLocalAudioTrack(localTrack);
3460
+ this.mediaProperties.setLocalAudioStream(localStream);
3465
3461
 
3466
- this.audio.handleLocalTrackChange(this);
3462
+ this.audio.handleLocalStreamChange(this);
3467
3463
 
3468
- localTrack?.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
3469
- localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3464
+ localStream?.on(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3465
+ localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3470
3466
 
3471
- if (!this.isMultistream || !localTrack) {
3472
- // for multistream WCME automatically un-publishes the old track when we publish a new one
3473
- await this.unpublishTrack(oldTrack);
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.publishTrack(this.mediaProperties.audioTrack);
3471
+ await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
3476
3472
  }
3477
3473
 
3478
3474
  /**
3479
- * Stores the reference to a new camera track, sets up the required event listeners
3480
- * on it, cleans up previous track, etc.
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 {LocalCameraTrack | null} localTrack local camera track
3478
+ * @param {LocalCameraStream | null} localStream local camera stream
3483
3479
  * @returns {Promise<void>}
3484
3480
  */
3485
- private async setLocalVideoTrack(localTrack?: LocalCameraTrack) {
3486
- const oldTrack = this.mediaProperties.videoTrack;
3481
+ private async setLocalVideoStream(localStream?: LocalCameraStream) {
3482
+ const oldStream = this.mediaProperties.videoStream;
3487
3483
 
3488
- oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
3489
- oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
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.setLocalVideoTrack(localTrack);
3488
+ this.mediaProperties.setLocalVideoStream(localStream);
3493
3489
 
3494
- this.video.handleLocalTrackChange(this);
3490
+ this.video.handleLocalStreamChange(this);
3495
3491
 
3496
- localTrack?.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
3497
- localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3492
+ localStream?.on(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3493
+ localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3498
3494
 
3499
- if (!this.isMultistream || !localTrack) {
3500
- // for multistream WCME automatically un-publishes the old track when we publish a new one
3501
- await this.unpublishTrack(oldTrack);
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.publishTrack(this.mediaProperties.videoTrack);
3499
+ await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
3504
3500
  }
3505
3501
 
3506
3502
  /**
3507
- * Stores the reference to a new screen share video track, sets up the required event listeners
3508
- * on it, cleans up previous track, etc.
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 {LocalDisplayTrack | undefined} localDisplayTrack local camera track
3507
+ * @param {LocalDisplayStream | undefined} localDisplayStream local display stream
3511
3508
  * @returns {Promise<void>}
3512
3509
  */
3513
- private async setLocalShareVideoTrack(localDisplayTrack?: LocalDisplayTrack) {
3514
- const oldTrack = this.mediaProperties.shareVideoTrack;
3510
+ private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
3511
+ const oldStream = this.mediaProperties.shareVideoStream;
3515
3512
 
3516
- oldTrack?.off(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3517
- oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3513
+ oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
3514
+ oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3518
3515
 
3519
- this.mediaProperties.setLocalShareVideoTrack(localDisplayTrack);
3516
+ this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
3520
3517
 
3521
- localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3522
- localDisplayTrack?.on(
3523
- LocalTrackEvents.UnderlyingTrackChange,
3524
- this.underlyingLocalTrackChangeHandler
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.hasLocalShareTrack();
3524
+ this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
3528
3525
 
3529
- if (!this.isMultistream || !localDisplayTrack) {
3530
- // for multistream WCME automatically un-publishes the old track when we publish a new one
3531
- await this.unpublishTrack(oldTrack);
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.publishTrack(this.mediaProperties.shareVideoTrack);
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 track, sets up the required event listeners
3538
- * on it, cleans up previous track, etc.
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 {LocalSystemAudioTrack | undefined} localSystemAudioTrack local system audio track
3537
+ * @param {LocalSystemAudioStream | undefined} localSystemAudioStream local system audio stream
3541
3538
  * @returns {Promise<void>}
3542
3539
  */
3543
- private async setLocalShareAudioTrack(localSystemAudioTrack?: LocalSystemAudioTrack) {
3544
- const oldTrack = this.mediaProperties.shareAudioTrack;
3540
+ private async setLocalShareAudioStream(localSystemAudioStream?: LocalSystemAudioStream) {
3541
+ const oldStream = this.mediaProperties.shareAudioStream;
3545
3542
 
3546
- oldTrack?.off(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3547
- oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3543
+ oldStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
3544
+ oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3548
3545
 
3549
- this.mediaProperties.setLocalShareAudioTrack(localSystemAudioTrack);
3546
+ this.mediaProperties.setLocalShareAudioStream(localSystemAudioStream);
3550
3547
 
3551
- localSystemAudioTrack?.on(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3552
- localSystemAudioTrack?.on(
3553
- LocalTrackEvents.UnderlyingTrackChange,
3554
- this.underlyingLocalTrackChangeHandler
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.hasLocalShareTrack();
3554
+ this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareStream();
3558
3555
 
3559
- if (!this.isMultistream || !localSystemAudioTrack) {
3560
- await this.unpublishTrack(oldTrack);
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.publishTrack(this.mediaProperties.shareAudioTrack);
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 tracks. This function should be called
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 cleanupLocalTracks() {
3573
- const {audioTrack, videoTrack, shareVideoTrack, shareAudioTrack} = this.mediaProperties;
3598
+ public cleanupLocalStreams() {
3599
+ const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
3574
3600
 
3575
- audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
3576
- audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3601
+ audioStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3602
+ audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3577
3603
 
3578
- videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
3579
- videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3604
+ videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3605
+ videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3580
3606
 
3581
- shareVideoTrack?.off(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3582
- shareVideoTrack?.off(
3583
- LocalTrackEvents.UnderlyingTrackChange,
3584
- this.underlyingLocalTrackChangeHandler
3607
+ shareAudioStream?.off(StreamEventNames.MuteStateChange, this.handleShareAudioStreamEnded);
3608
+ shareAudioStream?.off(
3609
+ LocalStreamEventNames.OutputTrackChange,
3610
+ this.localOutputTrackChangeHandler
3585
3611
  );
3586
-
3587
- shareAudioTrack?.off(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3588
- shareAudioTrack?.off(
3589
- LocalTrackEvents.UnderlyingTrackChange,
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.setLocalAudioTrack(undefined);
3594
- this.mediaProperties.setLocalVideoTrack(undefined);
3595
- this.mediaProperties.setLocalShareVideoTrack(undefined);
3596
- this.mediaProperties.setLocalShareAudioTrack(undefined);
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
- // WCME doesn't unpublish tracks when multistream connection is closed, so we do it here
3603
- // (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
3604
- audioTrack?.setPublished(false);
3605
- videoTrack?.setPublished(false);
3606
- shareVideoTrack?.setPublished(false);
3607
- shareAudioTrack?.setPublished(false);
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.audioTrack);
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.audioTrack);
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.videoTrack);
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.videoTrack);
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
- * localTracks: { microphone: microphoneTrack, camera: cameraTrack }
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 track and re initiate it
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.cleanupLocalTracks();
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
- const mediaTrack = event.track;
5054
+ if (event.track) {
5055
+ const mediaTrack = event.track;
5056
+ const remoteStream = new RemoteStream(MediaUtil.createMediaStream([mediaTrack]));
5003
5057
 
5004
- // eslint-disable-next-line @typescript-eslint/no-shadow
5005
- let eventType;
5058
+ // eslint-disable-next-line @typescript-eslint/no-shadow
5059
+ let eventType;
5006
5060
 
5007
- switch (event.type) {
5008
- case RemoteTrackType.AUDIO:
5009
- eventType = EVENT_TYPES.REMOTE_AUDIO;
5010
- this.mediaProperties.setRemoteAudioTrack(event.track);
5011
- break;
5012
- case RemoteTrackType.VIDEO:
5013
- eventType = EVENT_TYPES.REMOTE_VIDEO;
5014
- this.mediaProperties.setRemoteVideoTrack(event.track);
5015
- break;
5016
- case RemoteTrackType.SCREENSHARE_VIDEO:
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.setRemoteShare(event.track);
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
- if (eventType && mediaTrack) {
5030
- Trigger.trigger(
5031
- this,
5032
- {
5033
- file: 'meeting/index',
5034
- function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED',
5035
- },
5036
- EVENT_TRIGGERS.MEDIA_READY,
5037
- {
5038
- type: eventType,
5039
- stream: MediaUtil.createMediaStream([mediaTrack]),
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, (msg) => {
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
- seqNum: msg.seqNum,
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 tracks to it
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
- // publish the tracks
5314
- if (this.mediaProperties.audioTrack) {
5315
- await this.publishTrack(this.mediaProperties.audioTrack);
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.videoTrack) {
5318
- await this.publishTrack(this.mediaProperties.videoTrack);
5386
+ if (this.mediaProperties.videoStream) {
5387
+ await this.publishStream(MediaType.VideoMain, this.mediaProperties.videoStream);
5319
5388
  }
5320
- if (this.mediaProperties.shareVideoTrack) {
5321
- await this.publishTrack(this.mediaProperties.shareVideoTrack);
5389
+ if (this.mediaProperties.shareVideoStream) {
5390
+ await this.publishStream(MediaType.VideoSlides, this.mediaProperties.shareVideoStream);
5322
5391
  }
5323
- if (this.isMultistream && this.mediaProperties.shareAudioTrack) {
5324
- await this.publishTrack(this.mediaProperties.shareAudioTrack);
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
- localTracks,
5447
+ localStreams,
5379
5448
  audioEnabled = true,
5380
5449
  videoEnabled = true,
5381
- receiveShare = true,
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 tracks are published
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 tracks in this.mediaProperties before creating media connection
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 (localTracks?.microphone) {
5457
- promises.push(this.setLocalAudioTrack(localTracks.microphone));
5526
+ if (localStreams?.microphone) {
5527
+ promises.push(this.setLocalAudioStream(localStreams.microphone));
5458
5528
  }
5459
- if (localTracks?.camera) {
5460
- promises.push(this.setLocalVideoTrack(localTracks.camera));
5529
+ if (localStreams?.camera) {
5530
+ promises.push(this.setLocalVideoStream(localStreams.camera));
5461
5531
  }
5462
- if (localTracks?.screenShare?.video) {
5463
- promises.push(this.setLocalShareVideoTrack(localTracks.screenShare.video));
5532
+ if (localStreams?.screenShare?.video) {
5533
+ promises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
5464
5534
  }
5465
- if (this.isMultistream && localTracks?.screenShare?.audio) {
5466
- promises.push(this.setLocalShareAudioTrack(localTracks.screenShare.audio));
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.hasLocalShareTrack()) {
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
- receiveShare?: boolean;
5878
+ shareAudioEnabled?: boolean;
5879
+ shareVideoEnabled?: boolean;
5809
5880
  }) {
5810
5881
  this.checkMediaConnection();
5811
5882
 
5812
- const {audioEnabled, videoEnabled, receiveShare} = options;
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 (videoEnabled !== undefined) {
5894
+ if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
5824
5895
  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) {
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 (receiveShare !== undefined) {
5849
- this.mediaProperties.mediaDirection.receiveShare = 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.hasLocalShareTrack() ||
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 track anymore (shareVideoTrack=${
6196
- this.mediaProperties.shareVideoTrack ? 'yes' : 'no'
6197
- }, shareAudioTrack=${this.mediaProperties.shareAudioTrack ? 'yes' : 'no'}, sendShare=${
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, remoteShare, remoteVideoTrack} = this.mediaProperties;
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 || !remoteVideoTrack) {
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 && remoteShare) {
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 handleShareAudioTrackEnded = async () => {
6634
- // current share audio track has ended, but there might be an active
6635
- // share video track. we only leave from wireless share if share has
6636
- // completely ended, which means no share audio or video tracks active
6637
- if (this.wirelessShare && !this.mediaProperties.shareVideoTrack) {
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.unpublishTracks([this.mediaProperties.shareAudioTrack]);
6710
+ await this.unpublishStreams([this.mediaProperties.shareAudioStream]);
6642
6711
  } catch (error) {
6643
6712
  LoggerProxy.logger.log(
6644
- 'Meeting:index#handleShareAudioTrackEnded --> Error stopping share: ',
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 handleShareVideoTrackEnded = async () => {
6659
- // current share video track has ended, but there might be an active
6660
- // share audio track. we only leave from wireless share if share has
6661
- // completely ended, which means no share audio or video tracks active
6662
- if (this.wirelessShare && !this.mediaProperties.shareAudioTrack) {
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.unpublishTracks([this.mediaProperties.shareVideoTrack]);
6734
+ await this.unpublishStreams([this.mediaProperties.shareVideoStream]);
6667
6735
  } catch (error) {
6668
6736
  LoggerProxy.logger.log(
6669
- 'Meeting:index#handleShareVideoTrackEnded --> Error stopping share: ',
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.hasLocalShareTrack()) {
6752
+ if (!this.mediaProperties.hasLocalShareStream()) {
6685
6753
  Trigger.trigger(
6686
6754
  this,
6687
6755
  {
6688
6756
  file: 'meeting/index',
6689
- function: 'handleShareTrackEnded',
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
- audioTrack?: LocalMicrophoneTrack;
6733
- videoTrack?: LocalCameraTrack;
6800
+ audioStream?: LocalMicrophoneStream;
6801
+ videoStream?: LocalCameraStream;
6734
6802
  }) {
6735
- MeetingUtil.handleVideoLogging(mediaProperties.videoTrack);
6736
- MeetingUtil.handleAudioLogging(mediaProperties.audioTrack);
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 track
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.mediaProperties.webrtcMediaConnection.setCodecParameters(MediaType.AudioMain, {
7057
+ await this.sendSlotManager.setCodecParameters(MediaType.AudioMain, {
6990
7058
  maxaveragebitrate: '64000',
6991
7059
  maxplaybackrate: '48000',
6992
7060
  });
6993
7061
  } else {
6994
- await this.mediaProperties.webrtcMediaConnection.deleteCodecParameters(MediaType.AudioMain, [
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.audioTrack?.underlyingTrack || null,
7018
- video: this.mediaProperties.videoTrack?.underlyingTrack || null,
7019
- screenShareVideo: this.mediaProperties.shareVideoTrack?.underlyingTrack || null,
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 track.
7128
+ * Publishes a stream.
7059
7129
  *
7060
- * @param {LocalTrack} track to publish
7130
+ * @param {MediaType} mediaType of the stream
7131
+ * @param {LocalStream} stream to publish
7061
7132
  * @returns {Promise}
7062
7133
  */
7063
- private async publishTrack(track?: LocalTrack) {
7064
- if (!track) {
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.mediaProperties.webrtcMediaConnection.publishTrack(track);
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 track.
7154
+ * Un-publishes a stream.
7079
7155
  *
7080
- * @param {LocalTrack} track to unpublish
7156
+ * @param {MediaType} mediaType of the stream
7157
+ * @param {LocalStream} stream to unpublish
7081
7158
  * @returns {Promise}
7082
7159
  */
7083
- private async unpublishTrack(track?: LocalTrack) {
7084
- if (!track) {
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.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
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 tracks in the meeting
7178
+ * Publishes specified local streams in the meeting
7097
7179
  *
7098
- * @param {Object} tracks
7180
+ * @param {Object} streams
7099
7181
  * @returns {Promise}
7100
7182
  */
7101
- async publishTracks(tracks: LocalTracks): Promise<void> {
7183
+ async publishStreams(streams: LocalStreams): Promise<void> {
7102
7184
  this.checkMediaConnection();
7103
7185
  if (
7104
- !tracks.microphone &&
7105
- !tracks.camera &&
7106
- !tracks.screenShare?.audio &&
7107
- !tracks.screenShare?.video
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
- if (tracks.screenShare?.video) {
7116
- await this.setLocalShareVideoTrack(tracks.screenShare?.video);
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 (this.isMultistream && tracks.screenShare?.audio) {
7122
- await this.setLocalShareAudioTrack(tracks.screenShare.audio);
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 (tracks.microphone) {
7128
- await this.setLocalAudioTrack(tracks.microphone);
7210
+ if (streams.microphone) {
7211
+ await this.setLocalAudioStream(streams.microphone);
7129
7212
  }
7130
7213
 
7131
- if (tracks.camera) {
7132
- await this.setLocalVideoTrack(tracks.camera);
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 tracks in the meeting
7231
+ * Un-publishes specified local streams in the meeting
7149
7232
  *
7150
- * @param {Array<MediaStreamTrack>} tracks
7233
+ * @param {Array<LocalStream>} streams
7151
7234
  * @returns {Promise}
7152
7235
  */
7153
- async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
7236
+ async unpublishStreams(streams: LocalStream[]): Promise<void> {
7154
7237
  this.checkMediaConnection();
7155
7238
 
7156
7239
  const promises = [];
7157
7240
 
7158
- for (const track of tracks.filter((t) => !!t)) {
7159
- if (track === this.mediaProperties.shareVideoTrack) {
7160
- promises.push(this.setLocalShareVideoTrack(undefined));
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 (track === this.mediaProperties.shareAudioTrack) {
7164
- promises.push(this.setLocalShareAudioTrack(undefined));
7246
+ if (stream === this.mediaProperties.shareVideoStream) {
7247
+ promises.push(this.setLocalShareVideoStream(undefined));
7165
7248
  }
7166
7249
 
7167
- if (track === this.mediaProperties.audioTrack) {
7168
- promises.push(this.setLocalAudioTrack(undefined));
7250
+ if (stream === this.mediaProperties.audioStream) {
7251
+ promises.push(this.setLocalAudioStream(undefined));
7169
7252
  }
7170
7253
 
7171
- if (track === this.mediaProperties.videoTrack) {
7172
- promises.push(this.setLocalVideoTrack(undefined));
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 tracks have ended
7185
- if (!this.mediaProperties.hasLocalShareTrack()) {
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) {