@webex/plugin-meetings 3.0.0-beta.170 → 3.0.0-beta.172

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 (40) hide show
  1. package/README.md +0 -6
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/index.js +12 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/media/index.js +3 -3
  9. package/dist/media/index.js.map +1 -1
  10. package/dist/media/properties.js +22 -5
  11. package/dist/media/properties.js.map +1 -1
  12. package/dist/meeting/index.js +529 -415
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/member/index.js +15 -0
  15. package/dist/member/index.js.map +1 -1
  16. package/dist/member/types.js +11 -1
  17. package/dist/member/types.js.map +1 -1
  18. package/dist/member/util.js +15 -0
  19. package/dist/member/util.js.map +1 -1
  20. package/dist/reconnection-manager/index.js +1 -2
  21. package/dist/reconnection-manager/index.js.map +1 -1
  22. package/dist/types/index.d.ts +1 -1
  23. package/dist/types/media/properties.d.ts +10 -3
  24. package/dist/types/meeting/index.d.ts +41 -9
  25. package/dist/types/member/index.d.ts +2 -1
  26. package/dist/types/member/types.d.ts +11 -0
  27. package/package.json +20 -20
  28. package/src/index.ts +2 -0
  29. package/src/media/index.ts +11 -5
  30. package/src/media/properties.ts +24 -5
  31. package/src/meeting/index.ts +210 -77
  32. package/src/member/index.ts +17 -1
  33. package/src/member/types.ts +14 -0
  34. package/src/member/util.ts +23 -1
  35. package/src/reconnection-manager/index.ts +4 -1
  36. package/test/integration/spec/journey.js +9 -9
  37. package/test/unit/spec/media/index.ts +13 -3
  38. package/test/unit/spec/meeting/index.js +106 -15
  39. package/test/unit/spec/member/index.js +36 -13
  40. package/test/unit/spec/member/util.js +31 -0
@@ -19,6 +19,7 @@ import {
19
19
  LocalTrack,
20
20
  LocalCameraTrack,
21
21
  LocalDisplayTrack,
22
+ LocalSystemAudioTrack,
22
23
  LocalMicrophoneTrack,
23
24
  LocalTrackEvents,
24
25
  TrackMuteEvent,
@@ -145,7 +146,7 @@ export type LocalTracks = {
145
146
  microphone?: LocalMicrophoneTrack;
146
147
  camera?: LocalCameraTrack;
147
148
  screenShare?: {
148
- audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
149
+ audio?: LocalSystemAudioTrack;
149
150
  video?: LocalDisplayTrack;
150
151
  };
151
152
  annotationInfo?: AnnotationInfo;
@@ -167,6 +168,12 @@ export const MEDIA_UPDATE_TYPE = {
167
168
  UPDATE_MEDIA: 'UPDATE_MEDIA',
168
169
  };
169
170
 
171
+ export enum ScreenShareFloorStatus {
172
+ PENDING = 'floor_request_pending',
173
+ GRANTED = 'floor_request_granted',
174
+ RELEASED = 'floor_released',
175
+ }
176
+
170
177
  /**
171
178
  * MediaDirection
172
179
  * @typedef {Object} MediaDirection
@@ -484,7 +491,6 @@ export default class Meeting extends StatelessWebexPlugin {
484
491
  inMeetingActions: InMeetingActions;
485
492
  isLocalShareLive: boolean;
486
493
  isRoapInProgress: boolean;
487
- isSharing: boolean;
488
494
  keepAliveTimerId: NodeJS.Timeout;
489
495
  lastVideoLayoutInfo: any;
490
496
  locusInfo: any;
@@ -511,6 +517,7 @@ export default class Meeting extends StatelessWebexPlugin {
511
517
  receiveSlotManager: ReceiveSlotManager;
512
518
  selfUserPolicies: any;
513
519
  shareStatus: string;
520
+ screenShareFloorState: ScreenShareFloorStatus;
514
521
  statsAnalyzer: StatsAnalyzer;
515
522
  transcription: Transcription;
516
523
  updateMediaConnections: (mediaConnections: any[]) => void;
@@ -964,24 +971,20 @@ export default class Meeting extends StatelessWebexPlugin {
964
971
  */
965
972
  this.inMeetingActions = new InMeetingActions();
966
973
  /**
967
- * This is deprecated, please use shareStatus instead.
968
974
  * @instance
969
- * @type {Boolean}
975
+ * @type {string}
970
976
  * @readonly
971
977
  * @public
972
978
  * @memberof Meeting
973
- * @deprecated after v1.118.13
974
979
  */
975
- this.isSharing = false;
980
+ this.shareStatus = SHARE_STATUS.NO_SHARE;
976
981
  /**
977
982
  * @instance
978
- * @type {string}
979
- * @readonly
980
- * @public
981
- * @memberof Meeting
983
+ * @type {ScreenShareFloorStatus}
984
+ * @private
985
+ * @memberof
982
986
  */
983
- this.shareStatus = SHARE_STATUS.NO_SHARE;
984
-
987
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
985
988
  /**
986
989
  * @instance
987
990
  * @type {Array}
@@ -2224,7 +2227,10 @@ export default class Meeting extends StatelessWebexPlugin {
2224
2227
  this.mediaProperties.mediaDirection?.sendShare &&
2225
2228
  oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
2226
2229
  ) {
2227
- await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
2230
+ await this.unpublishTracks([
2231
+ this.mediaProperties.shareVideoTrack,
2232
+ this.mediaProperties.shareAudioTrack,
2233
+ ]);
2228
2234
  }
2229
2235
  } finally {
2230
2236
  sendStartedSharingRemote();
@@ -2902,8 +2908,11 @@ export default class Meeting extends StatelessWebexPlugin {
2902
2908
 
2903
2909
  // TODO: Handle sharing and wireless sharing when meeting end
2904
2910
  if (this.wirelessShare) {
2905
- if (this.mediaProperties.shareTrack) {
2906
- await this.setLocalShareTrack(undefined);
2911
+ if (this.mediaProperties.shareVideoTrack) {
2912
+ await this.setLocalShareVideoTrack(undefined);
2913
+ }
2914
+ if (this.mediaProperties.shareAudioTrack) {
2915
+ await this.setLocalShareAudioTrack(undefined);
2907
2916
  }
2908
2917
  }
2909
2918
  // when multiple WEB deviceType join with same user
@@ -3359,34 +3368,62 @@ export default class Meeting extends StatelessWebexPlugin {
3359
3368
  }
3360
3369
 
3361
3370
  /**
3362
- * Stores the reference to a new screen share track, sets up the required event listeners
3371
+ * Stores the reference to a new screen share video track, sets up the required event listeners
3363
3372
  * on it, cleans up previous track, etc.
3364
- * It also sends the floor grant/release request.
3365
3373
  *
3366
3374
  * @param {LocalDisplayTrack | undefined} localDisplayTrack local camera track
3367
3375
  * @returns {Promise<void>}
3368
3376
  */
3369
- private async setLocalShareTrack(localDisplayTrack?: LocalDisplayTrack) {
3370
- const oldTrack = this.mediaProperties.shareTrack;
3377
+ private async setLocalShareVideoTrack(localDisplayTrack?: LocalDisplayTrack) {
3378
+ const oldTrack = this.mediaProperties.shareVideoTrack;
3371
3379
 
3372
- oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
3380
+ oldTrack?.off(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3373
3381
  oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3374
3382
 
3375
- this.mediaProperties.setLocalShareTrack(localDisplayTrack);
3383
+ this.mediaProperties.setLocalShareVideoTrack(localDisplayTrack);
3376
3384
 
3377
- localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
3385
+ localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3378
3386
  localDisplayTrack?.on(
3379
3387
  LocalTrackEvents.UnderlyingTrackChange,
3380
3388
  this.underlyingLocalTrackChangeHandler
3381
3389
  );
3382
3390
 
3383
- this.mediaProperties.mediaDirection.sendShare = !!localDisplayTrack;
3391
+ this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareTrack();
3384
3392
 
3385
3393
  if (!this.isMultistream || !localDisplayTrack) {
3386
3394
  // for multistream WCME automatically un-publishes the old track when we publish a new one
3387
3395
  await this.unpublishTrack(oldTrack);
3388
3396
  }
3389
- await this.publishTrack(this.mediaProperties.shareTrack);
3397
+ await this.publishTrack(this.mediaProperties.shareVideoTrack);
3398
+ }
3399
+
3400
+ /**
3401
+ * Stores the reference to a new screen share audio track, sets up the required event listeners
3402
+ * on it, cleans up previous track, etc.
3403
+ *
3404
+ * @param {LocalSystemAudioTrack | undefined} localSystemAudioTrack local system audio track
3405
+ * @returns {Promise<void>}
3406
+ */
3407
+ private async setLocalShareAudioTrack(localSystemAudioTrack?: LocalSystemAudioTrack) {
3408
+ const oldTrack = this.mediaProperties.shareAudioTrack;
3409
+
3410
+ oldTrack?.off(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3411
+ oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3412
+
3413
+ this.mediaProperties.setLocalShareAudioTrack(localSystemAudioTrack);
3414
+
3415
+ localSystemAudioTrack?.on(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3416
+ localSystemAudioTrack?.on(
3417
+ LocalTrackEvents.UnderlyingTrackChange,
3418
+ this.underlyingLocalTrackChangeHandler
3419
+ );
3420
+
3421
+ this.mediaProperties.mediaDirection.sendShare = this.mediaProperties.hasLocalShareTrack();
3422
+
3423
+ if (!this.isMultistream || !localSystemAudioTrack) {
3424
+ await this.unpublishTrack(oldTrack);
3425
+ }
3426
+ await this.publishTrack(this.mediaProperties.shareAudioTrack);
3390
3427
  }
3391
3428
 
3392
3429
  /**
@@ -3397,7 +3434,7 @@ export default class Meeting extends StatelessWebexPlugin {
3397
3434
  * @returns {void}
3398
3435
  */
3399
3436
  public cleanupLocalTracks() {
3400
- const {audioTrack, videoTrack, shareTrack} = this.mediaProperties;
3437
+ const {audioTrack, videoTrack, shareVideoTrack, shareAudioTrack} = this.mediaProperties;
3401
3438
 
3402
3439
  audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
3403
3440
  audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
@@ -3405,12 +3442,22 @@ export default class Meeting extends StatelessWebexPlugin {
3405
3442
  videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
3406
3443
  videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3407
3444
 
3408
- shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
3409
- shareTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
3445
+ shareVideoTrack?.off(LocalTrackEvents.Ended, this.handleShareVideoTrackEnded);
3446
+ shareVideoTrack?.off(
3447
+ LocalTrackEvents.UnderlyingTrackChange,
3448
+ this.underlyingLocalTrackChangeHandler
3449
+ );
3450
+
3451
+ shareAudioTrack?.off(LocalTrackEvents.Ended, this.handleShareAudioTrackEnded);
3452
+ shareAudioTrack?.off(
3453
+ LocalTrackEvents.UnderlyingTrackChange,
3454
+ this.underlyingLocalTrackChangeHandler
3455
+ );
3410
3456
 
3411
3457
  this.mediaProperties.setLocalAudioTrack(undefined);
3412
3458
  this.mediaProperties.setLocalVideoTrack(undefined);
3413
- this.mediaProperties.setLocalShareTrack(undefined);
3459
+ this.mediaProperties.setLocalShareVideoTrack(undefined);
3460
+ this.mediaProperties.setLocalShareAudioTrack(undefined);
3414
3461
 
3415
3462
  this.mediaProperties.mediaDirection.sendAudio = false;
3416
3463
  this.mediaProperties.mediaDirection.sendVideo = false;
@@ -3420,7 +3467,8 @@ export default class Meeting extends StatelessWebexPlugin {
3420
3467
  // (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
3421
3468
  audioTrack?.setPublished(false);
3422
3469
  videoTrack?.setPublished(false);
3423
- shareTrack?.setPublished(false);
3470
+ shareVideoTrack?.setPublished(false);
3471
+ shareAudioTrack?.setPublished(false);
3424
3472
  }
3425
3473
 
3426
3474
  /**
@@ -3522,6 +3570,18 @@ export default class Meeting extends StatelessWebexPlugin {
3522
3570
  this.correlationId = id;
3523
3571
  }
3524
3572
 
3573
+ /**
3574
+ * Enqueue request for screenshare floor and set the status to pending
3575
+ * @returns {Promise}
3576
+ * @private
3577
+ * @memberof Meeting
3578
+ */
3579
+ private enqueueScreenShareFloorRequest() {
3580
+ this.screenShareFloorState = ScreenShareFloorStatus.PENDING;
3581
+
3582
+ return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
3583
+ }
3584
+
3525
3585
  /**
3526
3586
  * Mute the audio for a meeting
3527
3587
  * @returns {Promise} resolves the data from muting audio {mute, self} or rejects if there is no audio set
@@ -4501,7 +4561,7 @@ export default class Meeting extends StatelessWebexPlugin {
4501
4561
  // Clean up the camera , microphone track and re initiate it
4502
4562
 
4503
4563
  try {
4504
- if (this.isSharing) {
4564
+ if (this.screenShareFloorState === ScreenShareFloorStatus.GRANTED) {
4505
4565
  await this.releaseScreenShareFloor();
4506
4566
  }
4507
4567
  const mediaSettings = {
@@ -5106,8 +5166,11 @@ export default class Meeting extends StatelessWebexPlugin {
5106
5166
  if (this.mediaProperties.videoTrack) {
5107
5167
  await this.publishTrack(this.mediaProperties.videoTrack);
5108
5168
  }
5109
- if (this.mediaProperties.shareTrack) {
5110
- await this.publishTrack(this.mediaProperties.shareTrack);
5169
+ if (this.mediaProperties.shareVideoTrack) {
5170
+ await this.publishTrack(this.mediaProperties.shareVideoTrack);
5171
+ }
5172
+ if (this.isMultistream && this.mediaProperties.shareAudioTrack) {
5173
+ await this.publishTrack(this.mediaProperties.shareAudioTrack);
5111
5174
  }
5112
5175
 
5113
5176
  return mc;
@@ -5243,7 +5306,10 @@ export default class Meeting extends StatelessWebexPlugin {
5243
5306
  promises.push(this.setLocalVideoTrack(localTracks.camera));
5244
5307
  }
5245
5308
  if (localTracks?.screenShare?.video) {
5246
- promises.push(this.setLocalShareTrack(localTracks.screenShare.video));
5309
+ promises.push(this.setLocalShareVideoTrack(localTracks.screenShare.video));
5310
+ }
5311
+ if (this.isMultistream && localTracks?.screenShare?.audio) {
5312
+ promises.push(this.setLocalShareAudioTrack(localTracks.screenShare.audio));
5247
5313
  }
5248
5314
 
5249
5315
  return Promise.all(promises)
@@ -5373,9 +5439,11 @@ export default class Meeting extends StatelessWebexPlugin {
5373
5439
  })
5374
5440
  )
5375
5441
  .then(() => {
5376
- if (localTracks?.screenShare?.video) {
5377
- this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
5442
+ if (this.mediaProperties.hasLocalShareTrack()) {
5443
+ return this.enqueueScreenShareFloorRequest();
5378
5444
  }
5445
+
5446
+ return Promise.resolve();
5379
5447
  })
5380
5448
  .then(() => this.mediaProperties.getCurrentConnectionType())
5381
5449
  .then((connectionType) => {
@@ -5821,7 +5889,7 @@ export default class Meeting extends StatelessWebexPlugin {
5821
5889
  return this.meetingRequest
5822
5890
  .changeMeetingFloor(body)
5823
5891
  .then(() => {
5824
- this.isSharing = false;
5892
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
5825
5893
 
5826
5894
  return Promise.resolve();
5827
5895
  })
@@ -5902,16 +5970,23 @@ export default class Meeting extends StatelessWebexPlugin {
5902
5970
  * @memberof Meeting
5903
5971
  */
5904
5972
  private requestScreenShareFloor() {
5905
- if (!this.mediaProperties.shareTrack || !this.mediaProperties.mediaDirection.sendShare) {
5973
+ if (
5974
+ !this.mediaProperties.hasLocalShareTrack() ||
5975
+ !this.mediaProperties.mediaDirection.sendShare
5976
+ ) {
5906
5977
  LoggerProxy.logger.log(
5907
- `Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareTrack=${
5908
- this.mediaProperties.shareTrack ? 'yes' : 'no'
5909
- }, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
5978
+ `Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareVideoTrack=${
5979
+ this.mediaProperties.shareVideoTrack ? 'yes' : 'no'
5980
+ }, shareAudioTrack=${this.mediaProperties.shareAudioTrack ? 'yes' : 'no'}, sendShare=${
5981
+ this.mediaProperties.mediaDirection.sendShare
5982
+ })`
5910
5983
  );
5984
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
5911
5985
 
5912
5986
  return Promise.resolve({});
5913
5987
  }
5914
5988
  if (this.state === MEETING_STATE.STATES.JOINED) {
5989
+ this.screenShareFloorState = ScreenShareFloorStatus.PENDING;
5915
5990
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5916
5991
 
5917
5992
  if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
@@ -5934,7 +6009,7 @@ export default class Meeting extends StatelessWebexPlugin {
5934
6009
  annotationInfo: this.annotationInfo,
5935
6010
  })
5936
6011
  .then(() => {
5937
- this.isSharing = true;
6012
+ this.screenShareFloorState = ScreenShareFloorStatus.GRANTED;
5938
6013
 
5939
6014
  return Promise.resolve();
5940
6015
  })
@@ -5948,11 +6023,18 @@ export default class Meeting extends StatelessWebexPlugin {
5948
6023
  stack: error.stack,
5949
6024
  });
5950
6025
 
6026
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
6027
+
5951
6028
  return Promise.reject(error);
5952
6029
  });
5953
6030
  }
6031
+ if (!content) {
6032
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
6033
+
6034
+ return Promise.reject(new ParameterError('Cannot share without content.'));
6035
+ }
5954
6036
 
5955
- return Promise.reject(new ParameterError('Cannot share without content.'));
6037
+ return Promise.resolve();
5956
6038
  }
5957
6039
  this.floorGrantPending = true;
5958
6040
 
@@ -5982,6 +6064,10 @@ export default class Meeting extends StatelessWebexPlugin {
5982
6064
  private releaseScreenShareFloor() {
5983
6065
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5984
6066
 
6067
+ if (this.screenShareFloorState === ScreenShareFloorStatus.RELEASED) {
6068
+ return Promise.resolve();
6069
+ }
6070
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
5985
6071
  if (content) {
5986
6072
  // @ts-ignore
5987
6073
  this.webex.internal.newMetrics.submitClientEvent({
@@ -5994,8 +6080,6 @@ export default class Meeting extends StatelessWebexPlugin {
5994
6080
 
5995
6081
  if (content.floor?.beneficiary.id !== this.selfId) {
5996
6082
  // remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
5997
- this.isSharing = false;
5998
-
5999
6083
  return Promise.resolve();
6000
6084
  }
6001
6085
 
@@ -6018,15 +6102,10 @@ export default class Meeting extends StatelessWebexPlugin {
6018
6102
  });
6019
6103
 
6020
6104
  return Promise.reject(error);
6021
- })
6022
- .finally(() => {
6023
- this.isSharing = false;
6024
6105
  });
6025
6106
  }
6026
6107
 
6027
6108
  // according to Locus there is no content, so we don't need to release the floor (it's probably already been released)
6028
- this.isSharing = false;
6029
-
6030
6109
  return Promise.resolve();
6031
6110
  }
6032
6111
 
@@ -6330,37 +6409,75 @@ export default class Meeting extends StatelessWebexPlugin {
6330
6409
  }
6331
6410
 
6332
6411
  /**
6333
- * Functionality for when a share is ended.
6412
+ * Functionality for when a share audio is ended.
6334
6413
  * @private
6335
6414
  * @memberof Meeting
6336
- * @param {MediaStream} localShare
6337
6415
  * @returns {undefined}
6338
6416
  */
6339
- private handleShareTrackEnded = async () => {
6340
- if (this.wirelessShare) {
6417
+ private handleShareAudioTrackEnded = async () => {
6418
+ // current share audio track has ended, but there might be an active
6419
+ // share video track. we only leave from wireless share if share has
6420
+ // completely ended, which means no share audio or video tracks active
6421
+ if (this.wirelessShare && !this.mediaProperties.shareVideoTrack) {
6341
6422
  this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
6342
6423
  } else {
6343
6424
  try {
6344
- await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo: screen share audio (SPARK-399690)
6425
+ await this.unpublishTracks([this.mediaProperties.shareAudioTrack]);
6345
6426
  } catch (error) {
6346
6427
  LoggerProxy.logger.log(
6347
- 'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
6428
+ 'Meeting:index#handleShareAudioTrackEnded --> Error stopping share: ',
6348
6429
  error
6349
6430
  );
6350
6431
  }
6351
6432
  }
6433
+ this.triggerStoppedSharing();
6434
+ };
6352
6435
 
6353
- Trigger.trigger(
6354
- this,
6355
- {
6356
- file: 'meeting/index',
6357
- function: 'handleShareTrackEnded',
6358
- },
6359
- EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
6360
- {
6361
- reason: SHARE_STOPPED_REASON.TRACK_ENDED,
6436
+ /**
6437
+ * Functionality for when a share video is ended.
6438
+ * @private
6439
+ * @memberof Meeting
6440
+ * @returns {undefined}
6441
+ */
6442
+ private handleShareVideoTrackEnded = async () => {
6443
+ // current share video track has ended, but there might be an active
6444
+ // share audio track. we only leave from wireless share if share has
6445
+ // completely ended, which means no share audio or video tracks active
6446
+ if (this.wirelessShare && !this.mediaProperties.shareAudioTrack) {
6447
+ this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
6448
+ } else {
6449
+ try {
6450
+ await this.unpublishTracks([this.mediaProperties.shareVideoTrack]);
6451
+ } catch (error) {
6452
+ LoggerProxy.logger.log(
6453
+ 'Meeting:index#handleShareVideoTrackEnded --> Error stopping share: ',
6454
+ error
6455
+ );
6362
6456
  }
6363
- );
6457
+ }
6458
+ this.triggerStoppedSharing();
6459
+ };
6460
+
6461
+ /**
6462
+ * Emits meeting:stoppedSharingLocal
6463
+ * @private
6464
+ * @returns {undefined}
6465
+ * @memberof Meeting
6466
+ */
6467
+ private triggerStoppedSharing = () => {
6468
+ if (!this.mediaProperties.hasLocalShareTrack()) {
6469
+ Trigger.trigger(
6470
+ this,
6471
+ {
6472
+ file: 'meeting/index',
6473
+ function: 'handleShareTrackEnded',
6474
+ },
6475
+ EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
6476
+ {
6477
+ reason: SHARE_STOPPED_REASON.TRACK_ENDED,
6478
+ }
6479
+ );
6480
+ }
6364
6481
  };
6365
6482
 
6366
6483
  /**
@@ -6499,7 +6616,7 @@ export default class Meeting extends StatelessWebexPlugin {
6499
6616
  clearMeetingData = () => {
6500
6617
  this.audio = null;
6501
6618
  this.video = null;
6502
- this.isSharing = false;
6619
+ this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
6503
6620
  if (this.shareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
6504
6621
  this.shareStatus = SHARE_STATUS.NO_SHARE;
6505
6622
  }
@@ -6683,7 +6800,7 @@ export default class Meeting extends StatelessWebexPlugin {
6683
6800
  localTracks: {
6684
6801
  audio: this.mediaProperties.audioTrack?.underlyingTrack || null,
6685
6802
  video: this.mediaProperties.videoTrack?.underlyingTrack || null,
6686
- screenShareVideo: this.mediaProperties.shareTrack?.underlyingTrack || null,
6803
+ screenShareVideo: this.mediaProperties.shareVideoTrack?.underlyingTrack || null,
6687
6804
  },
6688
6805
  direction: {
6689
6806
  audio: Media.getDirection(
@@ -6783,9 +6900,15 @@ export default class Meeting extends StatelessWebexPlugin {
6783
6900
  let floorRequestNeeded = false;
6784
6901
 
6785
6902
  if (tracks.screenShare?.video) {
6786
- await this.setLocalShareTrack(tracks.screenShare?.video);
6903
+ await this.setLocalShareVideoTrack(tracks.screenShare?.video);
6787
6904
 
6788
- floorRequestNeeded = true;
6905
+ floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
6906
+ }
6907
+
6908
+ if (this.isMultistream && tracks.screenShare?.audio) {
6909
+ await this.setLocalShareAudioTrack(tracks.screenShare.audio);
6910
+
6911
+ floorRequestNeeded = this.screenShareFloorState === ScreenShareFloorStatus.RELEASED;
6789
6912
  }
6790
6913
 
6791
6914
  if (tracks.microphone) {
@@ -6804,7 +6927,7 @@ export default class Meeting extends StatelessWebexPlugin {
6804
6927
  // we're sending the http request to Locus to request the screen share floor
6805
6928
  // only after the SDP update, because that's how it's always been done for transcoded meetings
6806
6929
  // and also if sharing from the start, we need confluence to have been created
6807
- await this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
6930
+ await this.enqueueScreenShareFloorRequest();
6808
6931
  }
6809
6932
  }
6810
6933
 
@@ -6820,13 +6943,12 @@ export default class Meeting extends StatelessWebexPlugin {
6820
6943
  const promises = [];
6821
6944
 
6822
6945
  for (const track of tracks.filter((t) => !!t)) {
6823
- if (track === this.mediaProperties.shareTrack) {
6824
- try {
6825
- this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
6826
- } catch (e) {
6827
- // nothing to do here, error is logged already inside releaseScreenShareFloor()
6828
- }
6829
- promises.push(this.setLocalShareTrack(undefined));
6946
+ if (track === this.mediaProperties.shareVideoTrack) {
6947
+ promises.push(this.setLocalShareVideoTrack(undefined));
6948
+ }
6949
+
6950
+ if (track === this.mediaProperties.shareAudioTrack) {
6951
+ promises.push(this.setLocalShareAudioTrack(undefined));
6830
6952
  }
6831
6953
 
6832
6954
  if (track === this.mediaProperties.audioTrack) {
@@ -6843,5 +6965,16 @@ export default class Meeting extends StatelessWebexPlugin {
6843
6965
  }
6844
6966
 
6845
6967
  await Promise.all(promises);
6968
+
6969
+ // we're allowing for the SDK to support just audio share as well
6970
+ // so a share could be active with only video, only audio, or both
6971
+ // we're only releasing the floor if both tracks have ended
6972
+ if (!this.mediaProperties.hasLocalShareTrack()) {
6973
+ try {
6974
+ this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
6975
+ } catch (e) {
6976
+ // nothing to do here, error is logged already inside releaseScreenShareFloor()
6977
+ }
6978
+ }
6846
6979
  }
6847
6980
  }
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
  import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_} from '../constants';
5
- import {IExternalRoles, ParticipantWithRoles} from './types';
5
+ import {IExternalRoles, IMediaStatus, ParticipantWithRoles} from './types';
6
6
 
7
7
  import MemberUtil from './util';
8
8
 
@@ -30,6 +30,7 @@ export default class Member {
30
30
  isUser: any;
31
31
  isVideoMuted: any;
32
32
  roles: IExternalRoles;
33
+ mediaStatus: IMediaStatus;
33
34
  name: any;
34
35
  participant: any;
35
36
  status: any;
@@ -247,6 +248,19 @@ export default class Member {
247
248
  * @memberof Member
248
249
  */
249
250
  this.roles = null;
251
+
252
+ /**
253
+ * @instance
254
+ * @type {IMediaStatus}
255
+ * @public
256
+ * @memberof Member
257
+ * @example {audio: MediaStatus.RECVONLY, video: MediaStatus.SENDRECV}
258
+ */
259
+ this.mediaStatus = {
260
+ audio: null,
261
+ video: null,
262
+ };
263
+
250
264
  // TODO: more participant types
251
265
  // such as native client, web client, is a device, what type of phone, etc
252
266
  this.processParticipant(participant);
@@ -325,6 +339,8 @@ export default class Member {
325
339
  this.isAudioMuted,
326
340
  this.type
327
341
  );
342
+
343
+ this.mediaStatus = MemberUtil.extractMediaStatus(this.participant);
328
344
  }
329
345
 
330
346
  /**
@@ -22,3 +22,17 @@ export type ParticipantWithRoles = {
22
22
  };
23
23
  };
24
24
  };
25
+
26
+ // values are inherited from locus so don't update these
27
+ export enum MediaStatus {
28
+ RECVONLY = 'RECVONLY', // participant only receiving and not sending
29
+ SENDONLY = 'SENDONLY', // participant only sending and not receiving
30
+ SENDRECV = 'SENDRECV', // participant both sending and receiving
31
+ INACTIVE = 'INACTIVE', // participant is not connected to media source
32
+ UNKNOWN = 'UNKNOWN', // participant has not added media in the meeting
33
+ }
34
+
35
+ export interface IMediaStatus {
36
+ audio: MediaStatus;
37
+ video: MediaStatus;
38
+ }
@@ -1,4 +1,10 @@
1
- import {IExternalRoles, ParticipantWithRoles, ServerRoles, ServerRoleShape} from './types';
1
+ import {
2
+ IExternalRoles,
3
+ ParticipantWithRoles,
4
+ ServerRoles,
5
+ ServerRoleShape,
6
+ IMediaStatus,
7
+ } from './types';
2
8
  import {
3
9
  _USER_,
4
10
  _RESOURCE_ROOM_,
@@ -347,6 +353,22 @@ MemberUtil.extractId = (participant: any) => {
347
353
  return null;
348
354
  };
349
355
 
356
+ /**
357
+ * extracts the media status from nested participant object
358
+ * @param {Object} participant the locus participant
359
+ * @returns {Object}
360
+ */
361
+ MemberUtil.extractMediaStatus = (participant: any): IMediaStatus => {
362
+ if (!participant) {
363
+ throw new ParameterError('Media status could not be extracted, participant is undefined.');
364
+ }
365
+
366
+ return {
367
+ audio: participant.status.audioStatus,
368
+ video: participant.status.videoStatus,
369
+ };
370
+ };
371
+
350
372
  /**
351
373
  * @param {Object} participant the locus participant
352
374
  * @returns {String}
@@ -239,7 +239,10 @@ export default class ReconnectionManager {
239
239
  * @memberof ReconnectionManager
240
240
  */
241
241
  private async stopLocalShareTrack(reason: string) {
242
- await this.meeting.unpublishTracks([this.meeting.mediaProperties.shareTrack]); // todo screen share audio SPARK-399690
242
+ await this.meeting.unpublishTracks([
243
+ this.meeting.mediaProperties.shareVideoTrack,
244
+ this.meeting.mediaProperties.shareAudioTrack,
245
+ ]);
243
246
  Trigger.trigger(
244
247
  this.meeting,
245
248
  {