@webex/plugin-meetings 3.0.0-beta.20 → 3.0.0-beta.22

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.
@@ -27,7 +27,7 @@ import Roap from '../roap/index';
27
27
  import Media from '../media';
28
28
  import MediaProperties from '../media/properties';
29
29
  import MeetingStateMachine from './state';
30
- import createMuteState from './muteState';
30
+ import {createMuteState} from './muteState';
31
31
  import createEffectsState from './effectsState';
32
32
  import LocusInfo from '../locus-info';
33
33
  import Metrics from '../metrics';
@@ -94,10 +94,9 @@ import {
94
94
  RemoteMediaManager,
95
95
  Event as RemoteMediaManagerEvent,
96
96
  } from '../multistream/remoteMediaManager';
97
- import {MultistreamMedia} from '../multistream/multistreamMedia';
98
97
  import {
99
98
  Reaction,
100
- ReactionType,
99
+ ReactionServerType,
101
100
  SkinToneType,
102
101
  ProcessedReaction,
103
102
  RelayEvent,
@@ -470,7 +469,6 @@ export default class Meeting extends StatelessWebexPlugin {
470
469
  keepAliveTimerId: NodeJS.Timeout;
471
470
  lastVideoLayoutInfo: any;
472
471
  locusInfo: any;
473
- media: MultistreamMedia;
474
472
  mediaProperties: MediaProperties;
475
473
  mediaRequestManagers: {
476
474
  audio: MediaRequestManager;
@@ -1104,8 +1102,6 @@ export default class Meeting extends StatelessWebexPlugin {
1104
1102
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
1105
1103
  this.hasJoinedOnce = false;
1106
1104
 
1107
- this.media = new MultistreamMedia(this);
1108
-
1109
1105
  /**
1110
1106
  * helper class for managing remote streams
1111
1107
  */
@@ -1945,7 +1941,7 @@ export default class Meeting extends StatelessWebexPlugin {
1945
1941
  */
1946
1942
  private setUpLocusMediaSharesListener() {
1947
1943
  // Will get triggered on local and remote share
1948
- this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
1944
+ this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, async (payload) => {
1949
1945
  const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
1950
1946
  const previousContentShare = payload.previous?.content;
1951
1947
  const previousWhiteboardShare = payload.previous?.whiteboard;
@@ -1978,14 +1974,20 @@ export default class Meeting extends StatelessWebexPlugin {
1978
1974
  contentShare.disposition === FLOOR_ACTION.GRANTED
1979
1975
  ) {
1980
1976
  if (this.mediaProperties.shareTrack?.readyState === 'ended') {
1981
- this.stopShare({
1982
- skipSignalingCheck: true,
1983
- }).catch((error) => {
1977
+ try {
1978
+ if (this.isMultistream) {
1979
+ await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
1980
+ } else {
1981
+ await this.stopShare({
1982
+ skipSignalingCheck: true,
1983
+ });
1984
+ }
1985
+ } catch (error) {
1984
1986
  LoggerProxy.logger.log(
1985
1987
  'Meeting:index#setUpLocusMediaSharesListener --> Error stopping share: ',
1986
1988
  error
1987
1989
  );
1988
- });
1990
+ }
1989
1991
  } else {
1990
1992
  // CONTENT - sharing content local
1991
1993
  newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
@@ -2077,19 +2079,23 @@ export default class Meeting extends StatelessWebexPlugin {
2077
2079
  );
2078
2080
  };
2079
2081
 
2080
- // if a remote participant is stealing the presentation from us
2081
- if (
2082
- !this.mediaProperties.mediaDirection?.sendShare ||
2083
- oldShareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE
2084
- ) {
2082
+ try {
2083
+ // if a remote participant is stealing the presentation from us
2084
+ if (
2085
+ this.mediaProperties.mediaDirection?.sendShare &&
2086
+ oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
2087
+ ) {
2088
+ if (this.isMultistream) {
2089
+ await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
2090
+ } else {
2091
+ await this.updateShare({
2092
+ sendShare: false,
2093
+ receiveShare: this.mediaProperties.mediaDirection.receiveShare,
2094
+ });
2095
+ }
2096
+ }
2097
+ } finally {
2085
2098
  sendStartedSharingRemote();
2086
- } else {
2087
- this.updateShare({
2088
- sendShare: false,
2089
- receiveShare: this.mediaProperties.mediaDirection.receiveShare,
2090
- }).finally(() => {
2091
- sendStartedSharingRemote();
2092
- });
2093
2099
  }
2094
2100
  break;
2095
2101
  }
@@ -3066,6 +3072,8 @@ export default class Meeting extends StatelessWebexPlugin {
3066
3072
  );
3067
3073
  this.mediaProperties.setLocalAudioTrack(audioTrack);
3068
3074
  if (this.audio) this.audio.applyClientStateLocally(this);
3075
+ } else {
3076
+ this.mediaProperties.setLocalAudioTrack(null);
3069
3077
  }
3070
3078
 
3071
3079
  if (emitEvent) {
@@ -3112,6 +3120,8 @@ export default class Meeting extends StatelessWebexPlugin {
3112
3120
  'Meeting:index#setLocalVideoTrack --> Video settings.',
3113
3121
  JSON.stringify(this.mediaProperties.mediaSettings.video)
3114
3122
  );
3123
+ } else {
3124
+ this.mediaProperties.setLocalVideoTrack(null);
3115
3125
  }
3116
3126
 
3117
3127
  if (emitEvent) {
@@ -3139,35 +3149,32 @@ export default class Meeting extends StatelessWebexPlugin {
3139
3149
 
3140
3150
  /**
3141
3151
  * Sets the local media stream on the class and emits an event to the developer
3142
- * @param {MediaStream} localShare the local media stream
3152
+ * @param {MediaStreamTrack} localShareTrack the local media stream
3143
3153
  * @returns {undefined}
3144
3154
  * @public
3145
3155
  * @memberof Meeting
3146
3156
  */
3147
- public setLocalShareTrack(localShare: MediaStream) {
3157
+ public setLocalShareTrack(localShareTrack: MediaStreamTrack) {
3148
3158
  let settings = null;
3149
3159
 
3150
- if (localShare) {
3151
- this.mediaProperties.setLocalShareTrack(MeetingUtil.getTrack(localShare).videoTrack);
3152
- const contentTracks = this.mediaProperties.shareTrack;
3153
-
3154
- if (contentTracks) {
3155
- settings = contentTracks.getSettings();
3156
- this.mediaProperties.setMediaSettings('screen', {
3157
- aspectRatio: settings.aspectRatio,
3158
- frameRate: settings.frameRate,
3159
- height: settings.height,
3160
- width: settings.width,
3161
- displaySurface: settings.displaySurface,
3162
- cursor: settings.cursor,
3163
- });
3164
- LoggerProxy.logger.log(
3165
- 'Meeting:index#setLocalShareTrack --> Screen settings.',
3166
- JSON.stringify(this.mediaProperties.mediaSettings.screen)
3167
- );
3168
- }
3160
+ if (localShareTrack) {
3161
+ this.mediaProperties.setLocalShareTrack(localShareTrack);
3162
+
3163
+ settings = localShareTrack.getSettings();
3164
+ this.mediaProperties.setMediaSettings('screen', {
3165
+ aspectRatio: settings.aspectRatio,
3166
+ frameRate: settings.frameRate,
3167
+ height: settings.height,
3168
+ width: settings.width,
3169
+ displaySurface: settings.displaySurface,
3170
+ cursor: settings.cursor,
3171
+ });
3172
+ LoggerProxy.logger.log(
3173
+ 'Meeting:index#setLocalShareTrack --> Screen settings.',
3174
+ JSON.stringify(this.mediaProperties.mediaSettings.screen)
3175
+ );
3169
3176
 
3170
- contentTracks.onended = () => this.handleShareTrackEnded(localShare);
3177
+ localShareTrack.addEventListener('ended', this.handleShareTrackEnded);
3171
3178
 
3172
3179
  Trigger.trigger(
3173
3180
  this,
@@ -3178,9 +3185,12 @@ export default class Meeting extends StatelessWebexPlugin {
3178
3185
  EVENT_TRIGGERS.MEDIA_READY,
3179
3186
  {
3180
3187
  type: EVENT_TYPES.LOCAL_SHARE,
3181
- stream: localShare,
3188
+ track: localShareTrack,
3182
3189
  }
3183
3190
  );
3191
+ } else if (this.mediaProperties.shareTrack) {
3192
+ this.mediaProperties.shareTrack.removeEventListener('ended', this.handleShareTrackEnded);
3193
+ this.mediaProperties.setLocalShareTrack(null);
3184
3194
  }
3185
3195
  }
3186
3196
 
@@ -5715,7 +5725,7 @@ export default class Meeting extends StatelessWebexPlugin {
5715
5725
 
5716
5726
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
5717
5727
 
5718
- this.setLocalShareTrack(stream);
5728
+ this.setLocalShareTrack(track);
5719
5729
 
5720
5730
  return MeetingUtil.validateOptions({sendShare, localShare: stream})
5721
5731
  .then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
@@ -5772,7 +5782,7 @@ export default class Meeting extends StatelessWebexPlugin {
5772
5782
  this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
5773
5783
  // Validation is already done in addMedia so no need to check if the lenght is greater then 0
5774
5784
  this.setLocalTracks(localStream);
5775
- this.setLocalShareTrack(localShare);
5785
+ this.setLocalShareTrack(MeetingUtil.getTrack(localShare).videoTrack);
5776
5786
  }
5777
5787
 
5778
5788
  /**
@@ -6560,9 +6570,23 @@ export default class Meeting extends StatelessWebexPlugin {
6560
6570
  * @param {MediaStream} localShare
6561
6571
  * @returns {undefined}
6562
6572
  */
6563
- private handleShareTrackEnded(localShare: MediaStream) {
6573
+ private handleShareTrackEnded = async () => {
6564
6574
  if (this.wirelessShare) {
6565
6575
  this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
6576
+ } else if (this.isMultistream) {
6577
+ try {
6578
+ if (this.mediaProperties.mediaDirection.sendShare) {
6579
+ await this.releaseScreenShareFloor();
6580
+ }
6581
+ } catch (error) {
6582
+ LoggerProxy.logger.log(
6583
+ 'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
6584
+ error
6585
+ );
6586
+ } finally {
6587
+ this.setLocalShareTrack(null);
6588
+ this.mediaProperties.mediaDirection.sendShare = false;
6589
+ }
6566
6590
  } else {
6567
6591
  // Skip checking for a stable peerConnection
6568
6592
  // to allow immediately stopping screenshare
@@ -6585,10 +6609,9 @@ export default class Meeting extends StatelessWebexPlugin {
6585
6609
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
6586
6610
  {
6587
6611
  type: EVENT_TYPES.LOCAL_SHARE,
6588
- stream: localShare,
6589
6612
  }
6590
6613
  );
6591
- }
6614
+ };
6592
6615
 
6593
6616
  /**
6594
6617
  * Emits the 'network:quality' event
@@ -7068,13 +7091,13 @@ export default class Meeting extends StatelessWebexPlugin {
7068
7091
  /**
7069
7092
  * Send a reaction inside the meeting.
7070
7093
  *
7071
- * @param {ReactionType} reactionType - type of reaction to be sent. Example: "thumbs_up"
7094
+ * @param {ReactionServerType} reactionType - type of reaction to be sent. Example: "thumbs_up"
7072
7095
  * @param {SkinToneType} skinToneType - skin tone for the reaction. Example: "medium_dark"
7073
7096
  * @returns {Promise}
7074
7097
  * @public
7075
7098
  * @memberof Meeting
7076
7099
  */
7077
- public sendReaction(reactionType: ReactionType, skinToneType?: SkinToneType) {
7100
+ public sendReaction(reactionType: ReactionServerType, skinToneType?: SkinToneType) {
7078
7101
  const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
7079
7102
  const participantId = this.members.selfId;
7080
7103
 
@@ -7117,4 +7140,111 @@ export default class Meeting extends StatelessWebexPlugin {
7117
7140
  requestingParticipantId: this.members.selfId,
7118
7141
  });
7119
7142
  }
7143
+
7144
+ /**
7145
+ * Throws if we don't have a media connection created
7146
+ *
7147
+ * @returns {void}
7148
+ */
7149
+ private checkMediaConnection() {
7150
+ if (this.mediaProperties?.webrtcMediaConnection) {
7151
+ return;
7152
+ }
7153
+ throw new Error('Webrtc media connection is missing, call addMedia() first');
7154
+ }
7155
+
7156
+ /**
7157
+ * Publishes specified local tracks in the meeting
7158
+ *
7159
+ * @param {Object} tracks
7160
+ * @returns {Promise}
7161
+ */
7162
+ async publishTracks(tracks: {
7163
+ microphone?: MediaStreamTrack;
7164
+ camera?: MediaStreamTrack;
7165
+ screenShare: {
7166
+ audio?: MediaStreamTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
7167
+ video?: MediaStreamTrack;
7168
+ };
7169
+ }): Promise<void> {
7170
+ this.checkMediaConnection();
7171
+
7172
+ if (tracks.screenShare?.video) {
7173
+ // we are starting a screen share
7174
+ this.setLocalShareTrack(tracks.screenShare.video);
7175
+
7176
+ await this.requestScreenShareFloor();
7177
+ this.mediaProperties.mediaDirection.sendShare = true;
7178
+
7179
+ await this.mediaProperties.webrtcMediaConnection.publishTrack(
7180
+ tracks.screenShare.video,
7181
+ 'slides'
7182
+ );
7183
+ }
7184
+
7185
+ if (tracks.microphone) {
7186
+ this.setLocalAudioTrack(tracks.microphone);
7187
+ this.mediaProperties.mediaDirection.sendAudio = true;
7188
+
7189
+ // audio mute state could be undefined if you have not sent audio before
7190
+ this.audio = this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
7191
+
7192
+ await this.mediaProperties.webrtcMediaConnection.publishTrack(tracks.microphone, 'main');
7193
+ }
7194
+
7195
+ if (tracks.camera) {
7196
+ this.setLocalVideoTrack(tracks.camera);
7197
+ this.mediaProperties.mediaDirection.sendVideo = true;
7198
+
7199
+ // video state could be undefined if you have not sent video before
7200
+ this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
7201
+
7202
+ await this.mediaProperties.webrtcMediaConnection.publishTrack(tracks.camera, 'main');
7203
+ }
7204
+ }
7205
+
7206
+ /**
7207
+ * Un-publishes specified local tracks in the meeting
7208
+ *
7209
+ * @param {Array<MediaStreamTrack>} tracks
7210
+ * @returns {Promise}
7211
+ */
7212
+ async unpublishTracks(tracks: MediaStreamTrack[]): Promise<void> {
7213
+ this.checkMediaConnection();
7214
+
7215
+ const unpublishPromises = [];
7216
+
7217
+ for (const track of tracks) {
7218
+ if (track === this.mediaProperties.shareTrack) {
7219
+ this.setLocalShareTrack(null);
7220
+
7221
+ this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
7222
+ this.mediaProperties.mediaDirection.sendShare = false;
7223
+
7224
+ unpublishPromises.push(
7225
+ this.mediaProperties.webrtcMediaConnection.unpublishTrack(track, 'slides')
7226
+ );
7227
+ }
7228
+
7229
+ if (track === this.mediaProperties.audioTrack) {
7230
+ this.setLocalAudioTrack(null);
7231
+ this.mediaProperties.mediaDirection.sendAudio = false;
7232
+
7233
+ unpublishPromises.push(
7234
+ this.mediaProperties.webrtcMediaConnection.unpublishTrack(track, 'main')
7235
+ );
7236
+ }
7237
+
7238
+ if (track === this.mediaProperties.videoTrack) {
7239
+ this.setLocalVideoTrack(null);
7240
+ this.mediaProperties.mediaDirection.sendVideo = false;
7241
+
7242
+ unpublishPromises.push(
7243
+ this.mediaProperties.webrtcMediaConnection.unpublishTrack(track, 'main')
7244
+ );
7245
+ }
7246
+ }
7247
+
7248
+ await Promise.all(unpublishPromises);
7249
+ }
7120
7250
  }
@@ -9,7 +9,8 @@ import {AUDIO, VIDEO} from '../constants';
9
9
  If we ever need to support it, search for REMOTE_MUTE_VIDEO_MISSING_IMPLEMENTATION string to find the places that need updating
10
10
  */
11
11
 
12
- const createMuteState = (type, meeting, mediaDirection) => {
12
+ // eslint-disable-next-line import/prefer-default-export
13
+ export const createMuteState = (type, meeting, mediaDirection) => {
13
14
  if (type === AUDIO && !mediaDirection.sendAudio) {
14
15
  return null;
15
16
  }
@@ -350,5 +351,3 @@ class MuteState {
350
351
  return this.isSelf();
351
352
  }
352
353
  }
353
-
354
- export default createMuteState;
@@ -1,6 +1,6 @@
1
- import {Reaction, ReactionType, SkinTone, SkinToneType} from './reactions.type';
1
+ import {Reaction, ReactionServerType, SkinTone, SkinToneType} from './reactions.type';
2
2
 
3
- const Reactions: Record<ReactionType, Reaction> = {
3
+ const Reactions: Record<ReactionServerType, Reaction> = {
4
4
  smile: {
5
5
  type: 'smile',
6
6
  codepoints: '1F642',
@@ -31,12 +31,12 @@ const Reactions: Record<ReactionType, Reaction> = {
31
31
  codepoints: '1F44F',
32
32
  shortcodes: ':clap:',
33
33
  },
34
- thumbs_up: {
34
+ thumb_up: {
35
35
  type: 'thumb_up',
36
36
  codepoints: '1F44D',
37
37
  shortcodes: ':thumbsup:',
38
38
  },
39
- thumbs_down: {
39
+ thumb_down: {
40
40
  type: 'thumb_down',
41
41
  codepoints: '1F44E',
42
42
  shortcodes: ':thumbsdown:',
@@ -13,15 +13,15 @@ export type Reaction = EmoticonData & {
13
13
  };
14
14
 
15
15
  // eslint-disable-next-line no-shadow
16
- export enum ReactionType {
16
+ export enum ReactionServerType {
17
17
  smile = 'smile',
18
18
  sad = 'sad',
19
19
  wow = 'wow',
20
20
  haha = 'haha',
21
21
  celebrate = 'celebrate',
22
22
  clap = 'clap',
23
- thumbs_up = 'thumbs_up',
24
- thumbs_down = 'thumbs_down',
23
+ thumb_up = 'thumb_up',
24
+ thumb_down = 'thumb_down',
25
25
  heart = 'heart',
26
26
  fire = 'fire',
27
27
  prayer = 'prayer',