@webex/plugin-meetings 3.0.0-beta.85 → 3.0.0-beta.87

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 (38) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/index.js +63 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/media/index.js +7 -4
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/media/properties.js.map +1 -1
  8. package/dist/meeting/index.js +139 -88
  9. package/dist/meeting/index.js.map +1 -1
  10. package/dist/meeting/muteState.js +169 -26
  11. package/dist/meeting/muteState.js.map +1 -1
  12. package/dist/meeting/util.js.map +1 -1
  13. package/dist/meetings/index.js +20 -2
  14. package/dist/meetings/index.js.map +1 -1
  15. package/dist/types/controls-options-manager/util.d.ts +0 -102
  16. package/dist/types/index.d.ts +6 -5
  17. package/dist/types/media/properties.d.ts +1 -1
  18. package/dist/types/meeting/index.d.ts +8 -5
  19. package/dist/types/meeting/muteState.d.ts +58 -5
  20. package/dist/types/meetings/index.d.ts +1 -0
  21. package/dist/types/multistream/remoteMedia.d.ts +1 -29
  22. package/dist/types/multistream/remoteMediaGroup.d.ts +0 -9
  23. package/package.json +19 -18
  24. package/src/{index.js → index.ts} +15 -0
  25. package/src/media/index.ts +17 -18
  26. package/src/media/properties.ts +3 -7
  27. package/src/meeting/index.ts +108 -51
  28. package/src/meeting/muteState.ts +158 -15
  29. package/src/meeting/util.ts +1 -1
  30. package/src/meetings/index.ts +14 -0
  31. package/test/integration/spec/converged-space-meetings.js +4 -3
  32. package/test/integration/spec/journey.js +4 -3
  33. package/test/integration/spec/space-meeting.js +4 -3
  34. package/test/unit/spec/media/index.ts +30 -2
  35. package/test/unit/spec/meeting/index.js +24 -28
  36. package/test/unit/spec/meeting/muteState.js +226 -13
  37. package/test/utils/integrationTestUtils.js +64 -0
  38. package/test/utils/testUtils.js +0 -57
@@ -1,4 +1,5 @@
1
- export declare const createMuteState: (type: any, meeting: any, mediaDirection: any) => MuteState;
1
+ import { ServerMuteReason } from '@webex/media-helpers';
2
+ export declare const createMuteState: (type: any, meeting: any, mediaDirection: any, sdkOwnsLocalTrack: boolean) => MuteState;
2
3
  /** The purpose of this class is to manage the local and remote mute state and make sure that the server state always matches
3
4
  the last requested state by the client.
4
5
 
@@ -9,13 +10,42 @@ declare class MuteState {
9
10
  pendingPromiseResolve: any;
10
11
  state: any;
11
12
  type: any;
13
+ sdkOwnsLocalTrack: boolean;
14
+ ignoreMuteStateChange: boolean;
12
15
  /**
13
16
  * Constructor
14
17
  *
15
18
  * @param {String} type - audio or video
16
19
  * @param {Object} meeting - the meeting object (used for reading current remote mute status)
20
+ * @param {boolean} sdkOwnsLocalTrack - if false, then client app owns the local track (for now that's the case only for multistream meetings)
17
21
  */
18
- constructor(type: string, meeting: any);
22
+ constructor(type: string, meeting: any, sdkOwnsLocalTrack: boolean);
23
+ /**
24
+ * Starts the mute state machine. Needs to be called after a new MuteState instance is created.
25
+ *
26
+ * @param {Object} meeting - the meeting object
27
+ * @returns {void}
28
+ */
29
+ init(meeting: any): void;
30
+ /**
31
+ * This method needs to be called whenever the local audio/video track has changed.
32
+ * It reapplies the remote mute state onto the new track and also reads the current
33
+ * local mute state from the track and updates the internal state machine and sends
34
+ * any required requests to the server.
35
+ *
36
+ * @param {Object} meeting - the meeting object
37
+ * @returns {void}
38
+ */
39
+ handleLocalTrackChange(meeting: any): void;
40
+ /**
41
+ * Mutes/unmutes local track
42
+ *
43
+ * @param {Object} meeting - the meeting object
44
+ * @param {Boolean} mute - true to mute the track, false to unmute it
45
+ * @param {ServerMuteReason} reason - reason for muting/unmuting
46
+ * @returns {void}
47
+ */
48
+ private muteLocalTrack;
19
49
  /**
20
50
  * Handles mute/unmute request from the client/user. Returns a promise that's resolved once the server update is completed or
21
51
  * at the point that this request becomese superseded by another client request.
@@ -30,16 +60,26 @@ declare class MuteState {
30
60
  * @param {Boolean} [mute] true for muting, false for unmuting request
31
61
  * @returns {Promise}
32
62
  */
33
- handleClientRequest(meeting?: object, mute?: boolean): Promise<unknown>;
63
+ handleClientRequest(meeting: object, mute?: boolean): Promise<unknown>;
64
+ /**
65
+ * This method should be called when the local track mute state is changed
66
+ * @public
67
+ * @memberof MuteState
68
+ * @param {Object} [meeting] the meeting object
69
+ * @param {Boolean} [mute] true for muting, false for unmuting request
70
+ * @returns {void}
71
+ */
72
+ handleLocalTrackMuteStateChange(meeting?: object, mute?: boolean): void;
34
73
  /**
35
74
  * Applies the current mute state to the local track (by enabling or disabling it accordingly)
36
75
  *
37
76
  * @public
38
77
  * @param {Object} [meeting] the meeting object
78
+ * @param {ServerMuteReason} reason - reason why we're applying our client state to the local track
39
79
  * @memberof MuteState
40
80
  * @returns {void}
41
81
  */
42
- applyClientStateLocally(meeting?: any): void;
82
+ applyClientStateLocally(meeting?: any, reason?: ServerMuteReason): void;
43
83
  /**
44
84
  * Updates the server local and remote mute values so that they match the current client desired state.
45
85
  *
@@ -67,16 +107,29 @@ declare class MuteState {
67
107
  * @returns {Promise}
68
108
  */
69
109
  private sendRemoteMuteRequestToServer;
110
+ /** Sets the mute state of the local track according to what server thinks is our state
111
+ * @param {Object} meeting - the meeting object
112
+ * @param {ServerMuteReason} serverMuteReason - reason why we're applying server mute to the local track
113
+ * @returns {void}
114
+ */
115
+ private applyServerMuteToLocalTrack;
116
+ /** Applies the current value for unmute allowed to the underlying track
117
+ *
118
+ * @param {Meeting} meeting
119
+ * @returns {void}
120
+ */
121
+ private applyUnmuteAllowedToTrack;
70
122
  /**
71
123
  * This method should be called whenever the server remote mute state is changed
72
124
  *
73
125
  * @public
74
126
  * @memberof MuteState
127
+ * @param {Meeting} meeting
75
128
  * @param {Boolean} [muted] true if user is remotely muted, false otherwise
76
129
  * @param {Boolean} [unmuteAllowed] indicates if user is allowed to unmute self (false when "hard mute" feature is used)
77
130
  * @returns {undefined}
78
131
  */
79
- handleServerRemoteMuteUpdate(muted?: boolean, unmuteAllowed?: boolean): void;
132
+ handleServerRemoteMuteUpdate(meeting: any, muted?: boolean, unmuteAllowed?: boolean): void;
80
133
  /**
81
134
  * This method should be called whenever we receive from the server a requirement to locally unmute
82
135
  *
@@ -60,6 +60,7 @@ export default class Meetings extends WebexPlugin {
60
60
  request: any;
61
61
  geoHintInfo: any;
62
62
  meetingInfo: any;
63
+ mediaHelpers: any;
63
64
  namespace: string;
64
65
  /**
65
66
  * Initializes the Meetings Plugin
@@ -1,7 +1,7 @@
1
1
  import { MediaType, SourceState } from '@webex/internal-media-core';
2
2
  import EventsScope from '../common/events/events-scope';
3
3
  import { MediaRequestManager } from './mediaRequestManager';
4
- import { CSI, ReceiveSlot } from './receiveSlot';
4
+ import { ReceiveSlot } from './receiveSlot';
5
5
  export declare const RemoteMediaEvents: {
6
6
  SourceUpdate: string;
7
7
  Stopped: string;
@@ -44,29 +44,6 @@ export declare class RemoteMedia extends EventsScope {
44
44
  * @param height height of the video element
45
45
  */
46
46
  setSizeHint(width: any, height: any): void;
47
- /**
48
- * Invalidates the remote media by clearing the reference to a receive slot and
49
- * cancelling the media request.
50
- * After this call the remote media is unusable.
51
- *
52
- * @param {boolean} commit - whether to commit the cancellation of the media request
53
- * @internal
54
- */
55
- stop(commit?: boolean): void;
56
- /**
57
- * Sends a new media request. This method can only be used for receiver-selected policy,
58
- * because only in that policy we have a 1-1 relationship between RemoteMedia and MediaRequest
59
- * and the request id is then stored in this RemoteMedia instance.
60
- * For active-speaker policy, the same request is shared among many RemoteMedia instances,
61
- * so it's managed through RemoteMediaGroup
62
- *
63
- * @internal
64
- */
65
- sendMediaRequest(csi: CSI, commit: boolean): void;
66
- /**
67
- * @internal
68
- */
69
- cancelMediaRequest(commit: boolean): void;
70
47
  /**
71
48
  * registers event listeners on the receive slot and forwards all the events
72
49
  */
@@ -91,10 +68,5 @@ export declare class RemoteMedia extends EventsScope {
91
68
  * Getter for remote media stream
92
69
  */
93
70
  get stream(): MediaStream;
94
- /**
95
- * @internal
96
- * @returns {ReceiveSlot}
97
- */
98
- getUnderlyingReceiveSlot(): ReceiveSlot;
99
71
  }
100
72
  export {};
@@ -35,15 +35,6 @@ export declare class RemoteMediaGroup {
35
35
  isPinned(remoteMedia: RemoteMedia): boolean;
36
36
  private sendActiveSpeakerMediaRequest;
37
37
  private cancelActiveSpeakerMediaRequest;
38
- /**
39
- * Invalidates the remote media group by clearing the references to the receive slots
40
- * used by all remote media from that group and cancelling all media requests.
41
- * After this call the remote media group is unusable.
42
- *
43
- * @param{boolean} commit whether to commit the cancellation of media requests
44
- * @internal
45
- */
46
- stop(commit?: boolean): void;
47
38
  /**
48
39
  * Checks if a given RemoteMedia instance belongs to this group.
49
40
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "3.0.0-beta.85",
3
+ "version": "3.0.0-beta.87",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
@@ -32,12 +32,12 @@
32
32
  "build": "yarn run -T tsc --declaration true --declarationDir ./dist/types"
33
33
  },
34
34
  "devDependencies": {
35
- "@webex/plugin-meetings": "3.0.0-beta.85",
36
- "@webex/test-helper-chai": "3.0.0-beta.85",
37
- "@webex/test-helper-mocha": "3.0.0-beta.85",
38
- "@webex/test-helper-mock-webex": "3.0.0-beta.85",
39
- "@webex/test-helper-retry": "3.0.0-beta.85",
40
- "@webex/test-helper-test-users": "3.0.0-beta.85",
35
+ "@webex/plugin-meetings": "3.0.0-beta.87",
36
+ "@webex/test-helper-chai": "3.0.0-beta.87",
37
+ "@webex/test-helper-mocha": "3.0.0-beta.87",
38
+ "@webex/test-helper-mock-webex": "3.0.0-beta.87",
39
+ "@webex/test-helper-retry": "3.0.0-beta.87",
40
+ "@webex/test-helper-test-users": "3.0.0-beta.87",
41
41
  "chai": "^4.3.4",
42
42
  "chai-as-promised": "^7.1.1",
43
43
  "jsdom-global": "3.0.2",
@@ -46,18 +46,19 @@
46
46
  "typescript": "^4.7.4"
47
47
  },
48
48
  "dependencies": {
49
- "@webex/common": "3.0.0-beta.85",
49
+ "@webex/common": "3.0.0-beta.87",
50
50
  "@webex/internal-media-core": "1.36.0",
51
- "@webex/internal-plugin-conversation": "3.0.0-beta.85",
52
- "@webex/internal-plugin-device": "3.0.0-beta.85",
53
- "@webex/internal-plugin-llm": "3.0.0-beta.85",
54
- "@webex/internal-plugin-mercury": "3.0.0-beta.85",
55
- "@webex/internal-plugin-metrics": "3.0.0-beta.85",
56
- "@webex/internal-plugin-support": "3.0.0-beta.85",
57
- "@webex/internal-plugin-user": "3.0.0-beta.85",
58
- "@webex/plugin-people": "3.0.0-beta.85",
59
- "@webex/plugin-rooms": "3.0.0-beta.85",
60
- "@webex/webex-core": "3.0.0-beta.85",
51
+ "@webex/internal-plugin-conversation": "3.0.0-beta.87",
52
+ "@webex/internal-plugin-device": "3.0.0-beta.87",
53
+ "@webex/internal-plugin-llm": "3.0.0-beta.87",
54
+ "@webex/internal-plugin-mercury": "3.0.0-beta.87",
55
+ "@webex/internal-plugin-metrics": "3.0.0-beta.87",
56
+ "@webex/internal-plugin-support": "3.0.0-beta.87",
57
+ "@webex/internal-plugin-user": "3.0.0-beta.87",
58
+ "@webex/media-helpers": "3.0.0-beta.87",
59
+ "@webex/plugin-people": "3.0.0-beta.87",
60
+ "@webex/plugin-rooms": "3.0.0-beta.87",
61
+ "@webex/webex-core": "3.0.0-beta.87",
61
62
  "ampersand-collection": "^2.0.2",
62
63
  "bowser": "^2.11.0",
63
64
  "btoa": "^1.2.1",
@@ -8,6 +8,21 @@ registerPlugin('meetings', Meetings, {
8
8
  config,
9
9
  });
10
10
 
11
+ export {
12
+ LocalTrack,
13
+ LocalDisplayTrack,
14
+ LocalTrackEvents,
15
+ type TrackMuteEvent,
16
+ type ServerMuteReason,
17
+ LocalMicrophoneTrackEvents,
18
+ LocalCameraTrackEvents,
19
+ LocalMicrophoneTrack,
20
+ LocalCameraTrack,
21
+ createMicrophoneTrack,
22
+ createCameraTrack,
23
+ createDisplayTrack,
24
+ } from '@webex/media-helpers';
25
+
11
26
  export default Meetings;
12
27
 
13
28
  export * as CONSTANTS from './constants';
@@ -3,13 +3,8 @@
3
3
  */
4
4
  /* globals navigator */
5
5
 
6
- import {
7
- LocalCameraTrack,
8
- LocalDisplayTrack,
9
- LocalMicrophoneTrack,
10
- RoapMediaConnection,
11
- MultistreamRoapMediaConnection,
12
- } from '@webex/internal-media-core';
6
+ import {RoapMediaConnection, MultistreamRoapMediaConnection} from '@webex/internal-media-core';
7
+ import {LocalCameraTrack, LocalDisplayTrack, LocalMicrophoneTrack} from '@webex/media-helpers';
13
8
  import LoggerProxy from '../common/logs/logger-proxy';
14
9
  import {AUDIO_INPUT, VIDEO_INPUT, MEDIA_TRACK_CONSTRAINT} from '../constants';
15
10
  import Config from '../config';
@@ -19,6 +14,8 @@ import BrowserDetection from '../common/browser-detection';
19
14
 
20
15
  const {isBrowser} = BrowserDetection();
21
16
 
17
+ type MultistreamConnectionConfig = ConstructorParameters<typeof MultistreamRoapMediaConnection>[0];
18
+
22
19
  export type BundlePolicy = ConstructorParameters<
23
20
  typeof MultistreamRoapMediaConnection
24
21
  >[0]['bundlePolicy'];
@@ -166,17 +163,19 @@ Media.createMediaConnection = (
166
163
  }
167
164
 
168
165
  if (isMultistream) {
169
- return new MultistreamRoapMediaConnection(
170
- {
171
- iceServers,
172
- enableMainAudio:
173
- mediaProperties.mediaDirection?.sendAudio || mediaProperties.mediaDirection?.receiveAudio,
174
- enableMainVideo:
175
- mediaProperties.mediaDirection?.sendVideo || mediaProperties.mediaDirection?.receiveVideo,
176
- bundlePolicy,
177
- },
178
- debugId
179
- );
166
+ const config: MultistreamConnectionConfig = {
167
+ iceServers,
168
+ enableMainAudio:
169
+ mediaProperties.mediaDirection?.sendAudio || mediaProperties.mediaDirection?.receiveAudio,
170
+ enableMainVideo:
171
+ mediaProperties.mediaDirection?.sendVideo || mediaProperties.mediaDirection?.receiveVideo,
172
+ };
173
+
174
+ if (bundlePolicy) {
175
+ config.bundlePolicy = bundlePolicy;
176
+ }
177
+
178
+ return new MultistreamRoapMediaConnection(config, debugId);
180
179
  }
181
180
 
182
181
  if (!mediaProperties) {
@@ -1,10 +1,6 @@
1
- import {
2
- ConnectionState,
3
- Event,
4
- LocalCameraTrack,
5
- LocalMicrophoneTrack,
6
- LocalDisplayTrack,
7
- } from '@webex/internal-media-core';
1
+ import {ConnectionState, Event} from '@webex/internal-media-core';
2
+
3
+ import {LocalCameraTrack, LocalMicrophoneTrack, LocalDisplayTrack} from '@webex/media-helpers';
8
4
 
9
5
  import {MEETINGS, PC_BAIL_TIMEOUT, QUALITY_LEVELS} from '../constants';
10
6
  import LoggerProxy from '../common/logs/logger-proxy';
@@ -7,13 +7,18 @@ import {
7
7
  Errors,
8
8
  ErrorType,
9
9
  Event,
10
+ MediaType,
11
+ RemoteTrackType,
12
+ } from '@webex/internal-media-core';
13
+
14
+ import {
15
+ LocalTrack,
10
16
  LocalCameraTrack,
11
17
  LocalDisplayTrack,
12
18
  LocalMicrophoneTrack,
13
19
  LocalTrackEvents,
14
- MediaType,
15
- RemoteTrackType,
16
- } from '@webex/internal-media-core';
20
+ TrackMuteEvent,
21
+ } from '@webex/media-helpers';
17
22
 
18
23
  import {
19
24
  MeetingNotActiveError,
@@ -519,6 +524,8 @@ export default class Meeting extends StatelessWebexPlugin {
519
524
  resourceUrl: string;
520
525
  selfId: string;
521
526
  state: any;
527
+ localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
528
+ localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
522
529
  webexMeetingId: string;
523
530
 
524
531
  namespace = MEETINGS;
@@ -1163,6 +1170,14 @@ export default class Meeting extends StatelessWebexPlugin {
1163
1170
  * helper class for managing remote streams
1164
1171
  */
1165
1172
  this.remoteMediaManager = null;
1173
+
1174
+ this.localAudioTrackMuteStateHandler = (event) => {
1175
+ this.audio.handleLocalTrackMuteStateChange(this, event.trackState.muted);
1176
+ };
1177
+
1178
+ this.localVideoTrackMuteStateHandler = (event) => {
1179
+ this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
1180
+ };
1166
1181
  }
1167
1182
 
1168
1183
  /**
@@ -2056,14 +2071,14 @@ export default class Meeting extends StatelessWebexPlugin {
2056
2071
  this.selfId === contentShare.beneficiaryId &&
2057
2072
  contentShare.disposition === FLOOR_ACTION.GRANTED
2058
2073
  ) {
2059
- // @ts-ignore originalTrack is private - this will be fixed when SPARK-399694 are SPARK-399695 done
2074
+ // @ts-ignore originalTrack is private - this will be fixed when SPARK-399695 is done
2060
2075
  const localShareTrack = this.mediaProperties.shareTrack?.originalTrack;
2061
2076
 
2062
2077
  // todo: remove this block of code and instead make sure we have LocalTrackEvents.Ended listener always registered (SPARK-399695)
2063
2078
  if (localShareTrack?.readyState === 'ended') {
2064
2079
  try {
2065
2080
  if (this.isMultistream) {
2066
- await this.unpublishTracks([localShareTrack]); // todo screen share audio (SPARK-399690)
2081
+ await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
2067
2082
  } else {
2068
2083
  await this.stopShare({
2069
2084
  skipSignalingCheck: true,
@@ -2173,8 +2188,7 @@ export default class Meeting extends StatelessWebexPlugin {
2173
2188
  oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
2174
2189
  ) {
2175
2190
  if (this.isMultistream) {
2176
- // @ts-ignore originalTrack is private - this will be fixed in SPARK-399694
2177
- await this.unpublishTracks([this.mediaProperties.shareTrack?.originalTrack]); // todo screen share audio (SPARK-399690)
2191
+ await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
2178
2192
  } else {
2179
2193
  await this.updateShare({
2180
2194
  sendShare: false,
@@ -2491,7 +2505,7 @@ export default class Meeting extends StatelessWebexPlugin {
2491
2505
  if (this.video) {
2492
2506
  payload.muted = payload.muted ?? this.video.isRemotelyMuted();
2493
2507
  payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
2494
- this.video.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
2508
+ this.video.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
2495
2509
  }
2496
2510
  Trigger.trigger(
2497
2511
  this,
@@ -2512,7 +2526,7 @@ export default class Meeting extends StatelessWebexPlugin {
2512
2526
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
2513
2527
  if (payload) {
2514
2528
  if (this.audio) {
2515
- this.audio.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
2529
+ this.audio.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
2516
2530
  }
2517
2531
  // with "mute on entry" server will send us remote mute even if we don't have media configured,
2518
2532
  // so if being muted by others, always send the notification,
@@ -3223,6 +3237,10 @@ export default class Meeting extends StatelessWebexPlugin {
3223
3237
  * @memberof Meeting
3224
3238
  */
3225
3239
  private setLocalAudioTrack(rawAudioTrack: MediaStreamTrack | null, emitEvent = true) {
3240
+ if (this.isMultistream) {
3241
+ throw new Error('this method is only supposed to be used for transcoded meetings');
3242
+ }
3243
+
3226
3244
  if (rawAudioTrack) {
3227
3245
  const settings = rawAudioTrack.getSettings();
3228
3246
 
@@ -3259,6 +3277,10 @@ export default class Meeting extends StatelessWebexPlugin {
3259
3277
  * @memberof Meeting
3260
3278
  */
3261
3279
  private setLocalVideoTrack(rawVideoTrack: MediaStreamTrack | null, emitEvent = true) {
3280
+ if (this.isMultistream) {
3281
+ throw new Error('this method is only supposed to be used for transcoded meetings');
3282
+ }
3283
+
3262
3284
  if (rawVideoTrack) {
3263
3285
  const {aspectRatio, frameRate, height, width, deviceId} = rawVideoTrack.getSettings();
3264
3286
 
@@ -3372,7 +3394,7 @@ export default class Meeting extends StatelessWebexPlugin {
3372
3394
  );
3373
3395
  } else if (this.mediaProperties.shareTrack) {
3374
3396
  this.mediaProperties.shareTrack.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
3375
- this.mediaProperties.shareTrack.stop(); // todo: this line should be removed once SPARK-399694 are SPARK-399695 are done
3397
+ this.mediaProperties.shareTrack.stop(); // todo: this line should be removed once SPARK-399695 is done
3376
3398
  this.mediaProperties.setLocalShareTrack(null);
3377
3399
  }
3378
3400
  }
@@ -5775,7 +5797,7 @@ export default class Meeting extends StatelessWebexPlugin {
5775
5797
 
5776
5798
  // audio state could be undefined if you have not sent audio before
5777
5799
  this.audio =
5778
- this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
5800
+ this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection, true);
5779
5801
  });
5780
5802
  }
5781
5803
 
@@ -5831,7 +5853,7 @@ export default class Meeting extends StatelessWebexPlugin {
5831
5853
 
5832
5854
  // video state could be undefined if you have not sent video before
5833
5855
  this.video =
5834
- this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
5856
+ this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection, true);
5835
5857
  });
5836
5858
  }
5837
5859
 
@@ -5947,10 +5969,14 @@ export default class Meeting extends StatelessWebexPlugin {
5947
5969
  // TODO wire into default config. There's currently an issue with the stateless plugin or how we register
5948
5970
  // @ts-ignore - config coming from registerPlugin
5949
5971
  this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
5950
- // add a setup a function move the create and setup media in future
5951
- // TODO: delete old audio and video if stale
5952
- this.audio = this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
5953
- this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
5972
+
5973
+ // for multistream, this.audio and this.video are created when publishTracks() is called
5974
+ if (!this.isMultistream) {
5975
+ this.audio =
5976
+ this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection, true);
5977
+ this.video =
5978
+ this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection, true);
5979
+ }
5954
5980
  // Validation is already done in addMedia so no need to check if the lenght is greater then 0
5955
5981
  this.setLocalTracks(localStream);
5956
5982
  if (this.isMultistream && localShare) {
@@ -6802,7 +6828,10 @@ export default class Meeting extends StatelessWebexPlugin {
6802
6828
  error
6803
6829
  );
6804
6830
  } finally {
6805
- this.setLocalShareTrack(null);
6831
+ // todo: once SPARK-399695 is done, we will be able to just call this.setLocalShareTrack(null); here instead of the next 2 lines:
6832
+ this.mediaProperties.shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
6833
+ this.mediaProperties.setLocalShareTrack(null);
6834
+
6806
6835
  this.mediaProperties.mediaDirection.sendShare = false;
6807
6836
  }
6808
6837
  } else {
@@ -7265,18 +7294,29 @@ export default class Meeting extends StatelessWebexPlugin {
7265
7294
  * @returns {Promise}
7266
7295
  */
7267
7296
  async publishTracks(tracks: {
7268
- microphone?: MediaStreamTrack;
7269
- camera?: MediaStreamTrack;
7297
+ microphone?: LocalMicrophoneTrack;
7298
+ camera?: LocalCameraTrack;
7270
7299
  screenShare: {
7271
- audio?: MediaStreamTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
7272
- video?: MediaStreamTrack;
7300
+ audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
7301
+ video?: LocalDisplayTrack;
7273
7302
  };
7274
7303
  }): Promise<void> {
7275
7304
  this.checkMediaConnection();
7276
7305
 
7306
+ if (!this.isMultistream) {
7307
+ throw new Error('publishTracks() only supported with multistream');
7308
+ }
7309
+
7277
7310
  if (tracks.screenShare?.video) {
7311
+ const oldTrack = this.mediaProperties.shareTrack;
7312
+ const localDisplayTrack = tracks.screenShare?.video;
7313
+
7314
+ oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
7315
+
7278
7316
  // we are starting a screen share
7279
- this.setLocalShareTrack(tracks.screenShare.video);
7317
+ this.mediaProperties.setLocalShareTrack(localDisplayTrack);
7318
+
7319
+ localDisplayTrack.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
7280
7320
 
7281
7321
  await this.requestScreenShareFloor();
7282
7322
  this.mediaProperties.mediaDirection.sendShare = true;
@@ -7287,11 +7327,22 @@ export default class Meeting extends StatelessWebexPlugin {
7287
7327
  }
7288
7328
 
7289
7329
  if (tracks.microphone) {
7290
- this.setLocalAudioTrack(tracks.microphone);
7330
+ const oldTrack = this.mediaProperties.audioTrack;
7331
+ const localTrack = tracks.microphone;
7332
+
7333
+ oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
7334
+
7335
+ this.mediaProperties.setLocalAudioTrack(localTrack);
7291
7336
  this.mediaProperties.mediaDirection.sendAudio = true;
7292
7337
 
7293
7338
  // audio mute state could be undefined if you have not sent audio before
7294
- this.audio = this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
7339
+ if (!this.audio) {
7340
+ this.audio = createMuteState(AUDIO, this, this.mediaProperties.mediaDirection, false);
7341
+ } else {
7342
+ this.audio.handleLocalTrackChange(this);
7343
+ }
7344
+
7345
+ localTrack.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
7295
7346
 
7296
7347
  await this.mediaProperties.webrtcMediaConnection.publishTrack(
7297
7348
  this.mediaProperties.audioTrack
@@ -7299,11 +7350,22 @@ export default class Meeting extends StatelessWebexPlugin {
7299
7350
  }
7300
7351
 
7301
7352
  if (tracks.camera) {
7302
- this.setLocalVideoTrack(tracks.camera);
7353
+ const oldTrack = this.mediaProperties.videoTrack;
7354
+ const localTrack = tracks.camera;
7355
+
7356
+ oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
7357
+
7358
+ this.mediaProperties.setLocalVideoTrack(localTrack);
7303
7359
  this.mediaProperties.mediaDirection.sendVideo = true;
7304
7360
 
7305
7361
  // video state could be undefined if you have not sent video before
7306
- this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
7362
+ if (!this.video) {
7363
+ this.video = createMuteState(VIDEO, this, this.mediaProperties.mediaDirection, false);
7364
+ } else {
7365
+ this.video.handleLocalTrackChange(this);
7366
+ }
7367
+
7368
+ localTrack.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
7307
7369
 
7308
7370
  await this.mediaProperties.webrtcMediaConnection.publishTrack(
7309
7371
  this.mediaProperties.videoTrack
@@ -7317,48 +7379,43 @@ export default class Meeting extends StatelessWebexPlugin {
7317
7379
  * @param {Array<MediaStreamTrack>} tracks
7318
7380
  * @returns {Promise}
7319
7381
  */
7320
- async unpublishTracks(tracks: MediaStreamTrack[]): Promise<void> {
7382
+ async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
7321
7383
  this.checkMediaConnection();
7322
7384
 
7385
+ if (!this.isMultistream) {
7386
+ throw new Error('unpublishTracks() is only supported with multistream');
7387
+ }
7388
+
7323
7389
  const unpublishPromises = [];
7324
7390
 
7325
- for (const track of tracks) {
7326
- // @ts-ignore originalTrack is private - this will be fixed in SPARK-399694
7327
- if (track === this.mediaProperties.shareTrack?.originalTrack) {
7328
- const localTrackToUnpublish = this.mediaProperties.shareTrack;
7391
+ for (const track of tracks.filter((t) => !!t)) {
7392
+ if (track === this.mediaProperties.shareTrack) {
7393
+ this.mediaProperties.setLocalShareTrack(null);
7329
7394
 
7330
- this.setLocalShareTrack(null);
7395
+ track.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
7331
7396
 
7332
7397
  this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
7333
7398
  this.mediaProperties.mediaDirection.sendShare = false;
7334
7399
 
7335
- unpublishPromises.push(
7336
- this.mediaProperties.webrtcMediaConnection.unpublishTrack(localTrackToUnpublish)
7337
- );
7400
+ unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
7338
7401
  }
7339
7402
 
7340
- // @ts-ignore originalTrack is private - this will be fixed in SPARK-399694
7341
- if (track === this.mediaProperties.audioTrack?.originalTrack) {
7342
- const localTrackToUnpublish = this.mediaProperties.audioTrack;
7343
-
7344
- this.setLocalAudioTrack(null);
7403
+ if (track === this.mediaProperties.audioTrack) {
7404
+ this.mediaProperties.setLocalAudioTrack(null);
7345
7405
  this.mediaProperties.mediaDirection.sendAudio = false;
7346
7406
 
7347
- unpublishPromises.push(
7348
- this.mediaProperties.webrtcMediaConnection.unpublishTrack(localTrackToUnpublish)
7349
- );
7350
- }
7407
+ track.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
7351
7408
 
7352
- // @ts-ignore originalTrack is private - this will be fixed in SPARK-399694
7353
- if (track === this.mediaProperties.videoTrack?.originalTrack) {
7354
- const localTrackToUnpublish = this.mediaProperties.videoTrack;
7409
+ unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
7410
+ }
7355
7411
 
7356
- this.setLocalVideoTrack(null);
7412
+ if (track === this.mediaProperties.videoTrack) {
7413
+ this.mediaProperties.setLocalVideoTrack(null);
7357
7414
  this.mediaProperties.mediaDirection.sendVideo = false;
7358
7415
 
7359
- unpublishPromises.push(
7360
- this.mediaProperties.webrtcMediaConnection.unpublishTrack(localTrackToUnpublish)
7361
- );
7416
+ track.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
7417
+
7418
+ unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
7362
7419
  }
7363
7420
  }
7364
7421