@webex/plugin-meetings 3.0.0-beta.146 → 3.0.0-beta.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation/index.js +0 -2
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/webex-errors.js +3 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/config.js +1 -7
- package/dist/config.js.map +1 -1
- package/dist/constants.js +7 -15
- package/dist/constants.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/media/index.js +5 -56
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +15 -93
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +1112 -1873
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +88 -184
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/util.js +1 -23
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +1 -2
- package/dist/meetings/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +153 -134
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +8 -7
- package/dist/roap/index.js.map +1 -1
- package/dist/types/common/errors/webex-errors.d.ts +1 -1
- package/dist/types/config.d.ts +0 -6
- package/dist/types/constants.d.ts +1 -18
- package/dist/types/index.d.ts +1 -1
- package/dist/types/media/properties.d.ts +16 -38
- package/dist/types/meeting/index.d.ts +97 -353
- package/dist/types/meeting/muteState.d.ts +36 -38
- package/dist/types/meeting/util.d.ts +2 -4
- package/package.json +19 -19
- package/src/annotation/index.ts +0 -2
- package/src/common/errors/webex-errors.ts +6 -2
- package/src/config.ts +0 -6
- package/src/constants.ts +1 -14
- package/src/index.ts +1 -0
- package/src/media/index.ts +10 -53
- package/src/media/properties.ts +32 -92
- package/src/meeting/index.ts +544 -1567
- package/src/meeting/muteState.ts +87 -178
- package/src/meeting/util.ts +3 -24
- package/src/meetings/index.ts +0 -1
- package/src/reconnection-manager/index.ts +4 -9
- package/src/roap/index.ts +13 -14
- package/test/integration/spec/converged-space-meetings.js +59 -3
- package/test/integration/spec/journey.js +330 -256
- package/test/integration/spec/space-meeting.js +75 -3
- package/test/unit/spec/annotation/index.ts +4 -4
- package/test/unit/spec/meeting/index.js +811 -1367
- package/test/unit/spec/meeting/muteState.js +238 -394
- package/test/unit/spec/meeting/utils.js +2 -9
- package/test/unit/spec/multistream/receiveSlot.ts +1 -1
- package/test/unit/spec/roap/index.ts +2 -2
- package/test/utils/integrationTestUtils.js +5 -23
package/src/meeting/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import uuid from 'uuid';
|
|
2
|
-
import {cloneDeep, isEqual, pick,
|
|
2
|
+
import {cloneDeep, isEqual, pick, defer, isEmpty} from 'lodash';
|
|
3
3
|
// @ts-ignore - Fix this
|
|
4
4
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
5
5
|
import {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '@webex/internal-media-core';
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
+
getDevices,
|
|
15
16
|
LocalTrack,
|
|
16
17
|
LocalCameraTrack,
|
|
17
18
|
LocalDisplayTrack,
|
|
@@ -31,18 +32,13 @@ import NetworkQualityMonitor from '../networkQualityMonitor';
|
|
|
31
32
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
32
33
|
import Trigger from '../common/events/trigger-proxy';
|
|
33
34
|
import Roap from '../roap/index';
|
|
34
|
-
import Media from '../media';
|
|
35
|
+
import Media, {type BundlePolicy} from '../media';
|
|
35
36
|
import MediaProperties from '../media/properties';
|
|
36
37
|
import MeetingStateMachine from './state';
|
|
37
38
|
import {createMuteState} from './muteState';
|
|
38
39
|
import LocusInfo from '../locus-info';
|
|
39
40
|
import Metrics from '../metrics';
|
|
40
|
-
import {
|
|
41
|
-
trigger,
|
|
42
|
-
mediaType as MetricsMediaType,
|
|
43
|
-
error as MetricsError,
|
|
44
|
-
eventType,
|
|
45
|
-
} from '../metrics/config';
|
|
41
|
+
import {trigger, error as MetricsError, eventType} from '../metrics/config';
|
|
46
42
|
import ReconnectionManager from '../reconnection-manager';
|
|
47
43
|
import MeetingRequest from './request';
|
|
48
44
|
import Members from '../members/index';
|
|
@@ -88,14 +84,12 @@ import {
|
|
|
88
84
|
RECORDING_STATE,
|
|
89
85
|
SHARE_STATUS,
|
|
90
86
|
SHARE_STOPPED_REASON,
|
|
91
|
-
VIDEO_RESOLUTIONS,
|
|
92
87
|
VIDEO,
|
|
93
88
|
HTTP_VERBS,
|
|
94
89
|
SELF_ROLES,
|
|
95
90
|
} from '../constants';
|
|
96
91
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
97
92
|
import ParameterError from '../common/errors/parameter';
|
|
98
|
-
import MediaError from '../common/errors/media';
|
|
99
93
|
import {
|
|
100
94
|
MeetingInfoV2PasswordError,
|
|
101
95
|
MeetingInfoV2CaptchaError,
|
|
@@ -105,6 +99,7 @@ import BrowserDetection from '../common/browser-detection';
|
|
|
105
99
|
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
106
100
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
107
101
|
import {
|
|
102
|
+
Configuration as RemoteMediaManagerConfiguration,
|
|
108
103
|
RemoteMediaManager,
|
|
109
104
|
Event as RemoteMediaManagerEvent,
|
|
110
105
|
} from '../multistream/remoteMediaManager';
|
|
@@ -143,12 +138,29 @@ const logRequest = (request: any, {logText = ''}) => {
|
|
|
143
138
|
});
|
|
144
139
|
};
|
|
145
140
|
|
|
141
|
+
export type LocalTracks = {
|
|
142
|
+
microphone?: LocalMicrophoneTrack;
|
|
143
|
+
camera?: LocalCameraTrack;
|
|
144
|
+
screenShare?: {
|
|
145
|
+
audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
|
|
146
|
+
video?: LocalDisplayTrack;
|
|
147
|
+
};
|
|
148
|
+
annotationInfo?: AnnotationInfo;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type AddMediaOptions = {
|
|
152
|
+
localTracks?: LocalTracks;
|
|
153
|
+
audioEnabled?: boolean; // if not specified, default value true is used
|
|
154
|
+
videoEnabled?: boolean; // if not specified, default value true is used
|
|
155
|
+
receiveShare?: boolean; // if not specified, default value true is used
|
|
156
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
|
157
|
+
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
|
158
|
+
};
|
|
159
|
+
|
|
146
160
|
export const MEDIA_UPDATE_TYPE = {
|
|
147
|
-
|
|
148
|
-
AUDIO: 'AUDIO',
|
|
149
|
-
VIDEO: 'VIDEO',
|
|
150
|
-
SHARE: 'SHARE',
|
|
161
|
+
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
151
162
|
LAMBDA: 'LAMBDA',
|
|
163
|
+
UPDATE_MEDIA: 'UPDATE_MEDIA',
|
|
152
164
|
};
|
|
153
165
|
|
|
154
166
|
/**
|
|
@@ -163,16 +175,6 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
163
175
|
* @property {boolean} isSharing
|
|
164
176
|
*/
|
|
165
177
|
|
|
166
|
-
/**
|
|
167
|
-
* AudioVideo
|
|
168
|
-
* @typedef {Object} AudioVideo
|
|
169
|
-
* @property {Object} audio
|
|
170
|
-
* @property {String} audio.deviceId
|
|
171
|
-
* @property {Object} video
|
|
172
|
-
* @property {String} video.deviceId
|
|
173
|
-
* @property {String} video.localVideoQuality // [240p, 360p, 480p, 720p, 1080p]
|
|
174
|
-
*/
|
|
175
|
-
|
|
176
178
|
/**
|
|
177
179
|
* SharePreferences
|
|
178
180
|
* @typedef {Object} SharePreferences
|
|
@@ -187,21 +189,12 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
187
189
|
* @property {String} [pin]
|
|
188
190
|
* @property {Boolean} [moderator]
|
|
189
191
|
* @property {String|Object} [meetingQuality]
|
|
190
|
-
* @property {String} [meetingQuality.local]
|
|
191
192
|
* @property {String} [meetingQuality.remote]
|
|
192
193
|
* @property {Boolean} [rejoin]
|
|
193
194
|
* @property {Boolean} [enableMultistream]
|
|
194
195
|
* @property {String} [correlationId]
|
|
195
196
|
*/
|
|
196
197
|
|
|
197
|
-
/**
|
|
198
|
-
* SendOptions
|
|
199
|
-
* @typedef {Object} SendOptions
|
|
200
|
-
* @property {Boolean} sendAudio
|
|
201
|
-
* @property {Boolean} sendVideo
|
|
202
|
-
* @property {Boolean} sendShare
|
|
203
|
-
*/
|
|
204
|
-
|
|
205
198
|
/**
|
|
206
199
|
* Recording
|
|
207
200
|
* @typedef {Object} Recording
|
|
@@ -535,6 +528,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
535
528
|
state: any;
|
|
536
529
|
localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
537
530
|
localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
531
|
+
underlyingLocalTrackChangeHandler: () => void;
|
|
538
532
|
roles: any[];
|
|
539
533
|
environment: string;
|
|
540
534
|
namespace = MEETINGS;
|
|
@@ -783,7 +777,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
783
777
|
*/
|
|
784
778
|
this.reconnectionManager = new ReconnectionManager(this);
|
|
785
779
|
/**
|
|
786
|
-
* created
|
|
780
|
+
* created with media connection
|
|
787
781
|
* @instance
|
|
788
782
|
* @type {MuteState}
|
|
789
783
|
* @private
|
|
@@ -791,7 +785,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
791
785
|
*/
|
|
792
786
|
this.audio = null;
|
|
793
787
|
/**
|
|
794
|
-
* created
|
|
788
|
+
* created with media connection
|
|
795
789
|
* @instance
|
|
796
790
|
* @type {MuteState}
|
|
797
791
|
* @private
|
|
@@ -1202,6 +1196,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1202
1196
|
this.localVideoTrackMuteStateHandler = (event) => {
|
|
1203
1197
|
this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1204
1198
|
};
|
|
1199
|
+
|
|
1200
|
+
// The handling of underlying track changes should be done inside
|
|
1201
|
+
// @webex/internal-media-core, but for now we have to do it here, because
|
|
1202
|
+
// RoapMediaConnection has to use raw MediaStreamTracks in its API until
|
|
1203
|
+
// the Calling SDK also moves to using webrtc-core tracks
|
|
1204
|
+
this.underlyingLocalTrackChangeHandler = () => {
|
|
1205
|
+
if (!this.isMultistream) {
|
|
1206
|
+
this.updateTranscodedMediaConnection();
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1205
1209
|
}
|
|
1206
1210
|
|
|
1207
1211
|
/**
|
|
@@ -1912,11 +1916,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1912
1916
|
this.pstnUpdate(payload);
|
|
1913
1917
|
|
|
1914
1918
|
// If user moved to a JOINED state and there is a pending floor grant trigger it
|
|
1915
|
-
|
|
1916
|
-
this.requestScreenShareFloor().then(() => {
|
|
1917
|
-
this.floorGrantPending = false;
|
|
1918
|
-
});
|
|
1919
|
-
}
|
|
1919
|
+
this.requestScreenShareFloorIfPending();
|
|
1920
1920
|
});
|
|
1921
1921
|
}
|
|
1922
1922
|
|
|
@@ -2200,6 +2200,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2200
2200
|
});
|
|
2201
2201
|
}
|
|
2202
2202
|
|
|
2203
|
+
/**
|
|
2204
|
+
* Trigger annotation info update event
|
|
2205
|
+
@returns {undefined}
|
|
2206
|
+
@param {object} contentShare
|
|
2207
|
+
@param {object} previousContentShare
|
|
2208
|
+
*/
|
|
2209
|
+
private triggerAnnotationInfoEvent(contentShare, previousContentShare) {
|
|
2210
|
+
if (
|
|
2211
|
+
contentShare?.annotation &&
|
|
2212
|
+
!isEqual(contentShare?.annotation, previousContentShare?.annotation)
|
|
2213
|
+
) {
|
|
2214
|
+
Trigger.trigger(
|
|
2215
|
+
this,
|
|
2216
|
+
{
|
|
2217
|
+
file: 'meeting/index',
|
|
2218
|
+
function: 'triggerAnnotationInfoEvent',
|
|
2219
|
+
},
|
|
2220
|
+
EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
|
|
2221
|
+
contentShare.annotation
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2203
2226
|
/**
|
|
2204
2227
|
* Set up the locus info media shares listener
|
|
2205
2228
|
* update content and whiteboard sharing id value for members, and updates the member
|
|
@@ -2215,17 +2238,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2215
2238
|
const previousContentShare = payload.previous?.content;
|
|
2216
2239
|
const previousWhiteboardShare = payload.previous?.whiteboard;
|
|
2217
2240
|
|
|
2218
|
-
|
|
2219
|
-
Trigger.trigger(
|
|
2220
|
-
this,
|
|
2221
|
-
{
|
|
2222
|
-
file: 'meetings/index',
|
|
2223
|
-
function: 'remoteShare',
|
|
2224
|
-
},
|
|
2225
|
-
EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
|
|
2226
|
-
contentShare.annotation
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2241
|
+
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2229
2242
|
|
|
2230
2243
|
if (
|
|
2231
2244
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
@@ -2254,29 +2267,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2254
2267
|
this.selfId === contentShare.beneficiaryId &&
|
|
2255
2268
|
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
2256
2269
|
) {
|
|
2257
|
-
//
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
// todo: remove this block of code and instead make sure we have LocalTrackEvents.Ended listener always registered (SPARK-399695)
|
|
2261
|
-
if (localShareTrack?.readyState === 'ended') {
|
|
2262
|
-
try {
|
|
2263
|
-
if (this.isMultistream) {
|
|
2264
|
-
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2265
|
-
} else {
|
|
2266
|
-
await this.stopShare({
|
|
2267
|
-
skipSignalingCheck: true,
|
|
2268
|
-
});
|
|
2269
|
-
}
|
|
2270
|
-
} catch (error) {
|
|
2271
|
-
LoggerProxy.logger.log(
|
|
2272
|
-
'Meeting:index#setUpLocusMediaSharesListener --> Error stopping share: ',
|
|
2273
|
-
error
|
|
2274
|
-
);
|
|
2275
|
-
}
|
|
2276
|
-
} else {
|
|
2277
|
-
// CONTENT - sharing content local
|
|
2278
|
-
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
2279
|
-
}
|
|
2270
|
+
// CONTENT - sharing content local
|
|
2271
|
+
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
2280
2272
|
}
|
|
2281
2273
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
2282
2274
|
// There is no concept of local/remote share for whiteboard
|
|
@@ -2372,14 +2364,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2372
2364
|
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2373
2365
|
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2374
2366
|
) {
|
|
2375
|
-
|
|
2376
|
-
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2377
|
-
} else {
|
|
2378
|
-
await this.updateShare({
|
|
2379
|
-
sendShare: false,
|
|
2380
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2367
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2383
2368
|
}
|
|
2384
2369
|
} finally {
|
|
2385
2370
|
sendStartedSharingRemote();
|
|
@@ -2997,13 +2982,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2997
2982
|
});
|
|
2998
2983
|
}
|
|
2999
2984
|
});
|
|
3000
|
-
this.locusInfo.on(EVENTS.DESTROY_MEETING, (payload) => {
|
|
2985
|
+
this.locusInfo.on(EVENTS.DESTROY_MEETING, async (payload) => {
|
|
3001
2986
|
// if self state is NOT left
|
|
3002
2987
|
|
|
3003
2988
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
3004
2989
|
if (this.wirelessShare) {
|
|
3005
2990
|
if (this.mediaProperties.shareTrack) {
|
|
3006
|
-
this.setLocalShareTrack(
|
|
2991
|
+
await this.setLocalShareTrack(undefined);
|
|
3007
2992
|
}
|
|
3008
2993
|
}
|
|
3009
2994
|
// when multiple WEB deviceType join with same user
|
|
@@ -3017,18 +3002,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3017
3002
|
if (payload.shouldLeave) {
|
|
3018
3003
|
// TODO: We should do cleaning of meeting object if the shouldLeave: false because there might be meeting object which we are not cleaning
|
|
3019
3004
|
|
|
3020
|
-
|
|
3021
|
-
.
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3005
|
+
try {
|
|
3006
|
+
await this.leave({reason: payload.reason});
|
|
3007
|
+
|
|
3008
|
+
LoggerProxy.logger.warn(
|
|
3009
|
+
'Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. The meeting has been left, but has not been destroyed, you should see a later event for leave.'
|
|
3010
|
+
);
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
// @ts-ignore
|
|
3013
|
+
LoggerProxy.logger.error(
|
|
3014
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3032
3017
|
} else {
|
|
3033
3018
|
LoggerProxy.logger.info(
|
|
3034
3019
|
'Meeting:index#setUpLocusInfoMeetingListener --> MEETING_REMOVED_REASON',
|
|
@@ -3179,66 +3164,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3179
3164
|
return this.members;
|
|
3180
3165
|
}
|
|
3181
3166
|
|
|
3182
|
-
/**
|
|
3183
|
-
* Truthy when a meeting has an audio connection established
|
|
3184
|
-
* @returns {Boolean} true if meeting audio is connected otherwise false
|
|
3185
|
-
* @public
|
|
3186
|
-
* @memberof Meeting
|
|
3187
|
-
*/
|
|
3188
|
-
public isAudioConnected() {
|
|
3189
|
-
return !!this.audio;
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
/**
|
|
3193
|
-
* Convenience function to tell whether a meeting is muted
|
|
3194
|
-
* @returns {Boolean} if meeting audio muted or not
|
|
3195
|
-
* @public
|
|
3196
|
-
* @memberof Meeting
|
|
3197
|
-
*/
|
|
3198
|
-
public isAudioMuted() {
|
|
3199
|
-
return this.audio && this.audio.isMuted();
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
/**
|
|
3203
|
-
* Convenience function to tell if the end user last changed the audio state
|
|
3204
|
-
* @returns {Boolean} if audio was manipulated by the end user
|
|
3205
|
-
* @public
|
|
3206
|
-
* @memberof Meeting
|
|
3207
|
-
*/
|
|
3208
|
-
public isAudioSelf() {
|
|
3209
|
-
return this.audio && this.audio.isSelf();
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
/**
|
|
3213
|
-
* Truthy when a meeting has a video connection established
|
|
3214
|
-
* @returns {Boolean} true if meeting video connected otherwise false
|
|
3215
|
-
* @public
|
|
3216
|
-
* @memberof Meeting
|
|
3217
|
-
*/
|
|
3218
|
-
public isVideoConnected() {
|
|
3219
|
-
return !!this.video;
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
/**
|
|
3223
|
-
* Convenience function to tell whether video is muted
|
|
3224
|
-
* @returns {Boolean} if meeting video is muted or not
|
|
3225
|
-
* @public
|
|
3226
|
-
* @memberof Meeting
|
|
3227
|
-
*/
|
|
3228
|
-
public isVideoMuted() {
|
|
3229
|
-
return this.video && this.video.isMuted();
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
/**
|
|
3233
|
-
* Convenience function to tell whether the end user changed the video state
|
|
3234
|
-
* @returns {Boolean} if meeting video is muted or not
|
|
3235
|
-
* @public
|
|
3236
|
-
* @memberof Meeting
|
|
3237
|
-
*/
|
|
3238
|
-
public isVideoSelf() {
|
|
3239
|
-
return this.video && this.video.isSelf();
|
|
3240
|
-
}
|
|
3241
|
-
|
|
3242
3167
|
/**
|
|
3243
3168
|
* Sets the meeting info on the class instance
|
|
3244
3169
|
* @param {Object} meetingInfo
|
|
@@ -3369,21 +3294,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3369
3294
|
Trigger.trigger(this, options, EVENTS.REQUEST_UPLOAD_LOGS, this);
|
|
3370
3295
|
}
|
|
3371
3296
|
|
|
3372
|
-
/**
|
|
3373
|
-
* Removes remote audio and video stream on the class instance and triggers an event
|
|
3374
|
-
* to developers
|
|
3375
|
-
* @returns {undefined}
|
|
3376
|
-
* @public
|
|
3377
|
-
* @memberof Meeting
|
|
3378
|
-
* @deprecated after v1.89.3
|
|
3379
|
-
*/
|
|
3380
|
-
public unsetRemoteStream() {
|
|
3381
|
-
LoggerProxy.logger.warn(
|
|
3382
|
-
'Meeting:index#unsetRemoteStream --> [DEPRECATION WARNING]: unsetRemoteStream has been deprecated after v1.89.3'
|
|
3383
|
-
);
|
|
3384
|
-
this.mediaProperties.unsetRemoteMedia();
|
|
3385
|
-
}
|
|
3386
|
-
|
|
3387
3297
|
/**
|
|
3388
3298
|
* Removes remote audio, video and share tracks from class instance's mediaProperties
|
|
3389
3299
|
* @returns {undefined}
|
|
@@ -3468,274 +3378,124 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3468
3378
|
}
|
|
3469
3379
|
|
|
3470
3380
|
/**
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3473
|
-
*
|
|
3474
|
-
* @
|
|
3475
|
-
|
|
3476
|
-
private sendLocalMediaReadyEvent() {
|
|
3477
|
-
Trigger.trigger(
|
|
3478
|
-
this,
|
|
3479
|
-
{
|
|
3480
|
-
file: 'meeting/index',
|
|
3481
|
-
function: 'sendLocalMediaReadyEvent',
|
|
3482
|
-
},
|
|
3483
|
-
EVENT_TRIGGERS.MEDIA_READY,
|
|
3484
|
-
{
|
|
3485
|
-
type: EVENT_TYPES.LOCAL,
|
|
3486
|
-
stream: MediaUtil.createMediaStream([
|
|
3487
|
-
this.mediaProperties.audioTrack?.underlyingTrack,
|
|
3488
|
-
this.mediaProperties.videoTrack?.underlyingTrack,
|
|
3489
|
-
]),
|
|
3490
|
-
}
|
|
3491
|
-
);
|
|
3492
|
-
}
|
|
3493
|
-
|
|
3494
|
-
/**
|
|
3495
|
-
* Sets the local audio track on the class and emits an event to the developer
|
|
3496
|
-
* @param {MediaStreamTrack} rawAudioTrack
|
|
3497
|
-
* @param {Boolean} emitEvent if true, a media ready event is emitted to the developer
|
|
3498
|
-
* @returns {undefined}
|
|
3499
|
-
* @private
|
|
3500
|
-
* @memberof Meeting
|
|
3381
|
+
* Stores the reference to a new microphone track, sets up the required event listeners
|
|
3382
|
+
* on it, cleans up previous track, etc.
|
|
3383
|
+
*
|
|
3384
|
+
* @param {LocalMicrophoneTrack | null} localTrack local microphone track
|
|
3385
|
+
* @returns {Promise<void>}
|
|
3501
3386
|
*/
|
|
3502
|
-
private setLocalAudioTrack(
|
|
3503
|
-
|
|
3504
|
-
throw new Error('this method is only supposed to be used for transcoded meetings');
|
|
3505
|
-
}
|
|
3387
|
+
private async setLocalAudioTrack(localTrack?: LocalMicrophoneTrack) {
|
|
3388
|
+
const oldTrack = this.mediaProperties.audioTrack;
|
|
3506
3389
|
|
|
3507
|
-
|
|
3508
|
-
|
|
3390
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3391
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3509
3392
|
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
);
|
|
3393
|
+
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3394
|
+
this.mediaProperties.setLocalAudioTrack(localTrack);
|
|
3513
3395
|
|
|
3514
|
-
|
|
3515
|
-
echoCancellation: settings.echoCancellation,
|
|
3516
|
-
noiseSuppression: settings.noiseSuppression,
|
|
3517
|
-
});
|
|
3396
|
+
this.audio.handleLocalTrackChange(this);
|
|
3518
3397
|
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
JSON.stringify(this.mediaProperties.mediaSettings.audio)
|
|
3522
|
-
);
|
|
3523
|
-
this.mediaProperties.setLocalAudioTrack(localMicrophoneTrack);
|
|
3524
|
-
if (this.audio) this.audio.applyClientStateLocally(this);
|
|
3525
|
-
} else {
|
|
3526
|
-
this.mediaProperties.setLocalAudioTrack(null);
|
|
3527
|
-
}
|
|
3398
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3399
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3528
3400
|
|
|
3529
|
-
if (
|
|
3530
|
-
|
|
3401
|
+
if (!this.isMultistream || !localTrack) {
|
|
3402
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3403
|
+
await this.unpublishTrack(oldTrack);
|
|
3531
3404
|
}
|
|
3405
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
3532
3406
|
}
|
|
3533
3407
|
|
|
3534
3408
|
/**
|
|
3535
|
-
*
|
|
3536
|
-
*
|
|
3537
|
-
*
|
|
3538
|
-
* @
|
|
3539
|
-
* @
|
|
3540
|
-
* @memberof Meeting
|
|
3409
|
+
* Stores the reference to a new camera track, sets up the required event listeners
|
|
3410
|
+
* on it, cleans up previous track, etc.
|
|
3411
|
+
*
|
|
3412
|
+
* @param {LocalCameraTrack | null} localTrack local camera track
|
|
3413
|
+
* @returns {Promise<void>}
|
|
3541
3414
|
*/
|
|
3542
|
-
private setLocalVideoTrack(
|
|
3543
|
-
|
|
3544
|
-
throw new Error('this method is only supposed to be used for transcoded meetings');
|
|
3545
|
-
}
|
|
3546
|
-
|
|
3547
|
-
if (rawVideoTrack) {
|
|
3548
|
-
const {aspectRatio, frameRate, height, width, deviceId} = rawVideoTrack.getSettings();
|
|
3415
|
+
private async setLocalVideoTrack(localTrack?: LocalCameraTrack) {
|
|
3416
|
+
const oldTrack = this.mediaProperties.videoTrack;
|
|
3549
3417
|
|
|
3550
|
-
|
|
3418
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3419
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3551
3420
|
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
if (Number(localQualityLevel.slice(0, -1)) > height) {
|
|
3555
|
-
LoggerProxy.logger
|
|
3556
|
-
.warn(`Meeting:index#setLocalVideoTrack --> Local video quality of ${localQualityLevel} not supported,
|
|
3557
|
-
downscaling to highest possible resolution of ${height}p`);
|
|
3558
|
-
|
|
3559
|
-
this.mediaProperties.setLocalQualityLevel(`${height}p`);
|
|
3560
|
-
}
|
|
3561
|
-
|
|
3562
|
-
this.mediaProperties.setLocalVideoTrack(localCameraTrack);
|
|
3563
|
-
if (this.video) this.video.applyClientStateLocally(this);
|
|
3564
|
-
|
|
3565
|
-
this.mediaProperties.setMediaSettings('video', {
|
|
3566
|
-
aspectRatio,
|
|
3567
|
-
frameRate,
|
|
3568
|
-
height,
|
|
3569
|
-
width,
|
|
3570
|
-
});
|
|
3571
|
-
// store and save the selected video input device
|
|
3572
|
-
if (deviceId) {
|
|
3573
|
-
this.mediaProperties.setVideoDeviceId(deviceId);
|
|
3574
|
-
}
|
|
3575
|
-
LoggerProxy.logger.log(
|
|
3576
|
-
'Meeting:index#setLocalVideoTrack --> Video settings.',
|
|
3577
|
-
JSON.stringify(this.mediaProperties.mediaSettings.video)
|
|
3578
|
-
);
|
|
3579
|
-
} else {
|
|
3580
|
-
this.mediaProperties.setLocalVideoTrack(null);
|
|
3581
|
-
}
|
|
3582
|
-
|
|
3583
|
-
if (emitEvent) {
|
|
3584
|
-
this.sendLocalMediaReadyEvent();
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
|
|
3588
|
-
/**
|
|
3589
|
-
* Sets the local media stream on the class and emits an event to the developer
|
|
3590
|
-
* @param {Stream} localStream the local media stream
|
|
3591
|
-
* @returns {undefined}
|
|
3592
|
-
* @public
|
|
3593
|
-
* @memberof Meeting
|
|
3594
|
-
*/
|
|
3595
|
-
public setLocalTracks(localStream: any) {
|
|
3596
|
-
if (localStream) {
|
|
3597
|
-
if (this.isMultistream) {
|
|
3598
|
-
throw new Error(
|
|
3599
|
-
'addMedia() and updateMedia() APIs are not supported with multistream, use publishTracks/unpublishTracks instead'
|
|
3600
|
-
);
|
|
3601
|
-
}
|
|
3421
|
+
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3422
|
+
this.mediaProperties.setLocalVideoTrack(localTrack);
|
|
3602
3423
|
|
|
3603
|
-
|
|
3424
|
+
this.video.handleLocalTrackChange(this);
|
|
3604
3425
|
|
|
3605
|
-
|
|
3606
|
-
|
|
3426
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3427
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3607
3428
|
|
|
3608
|
-
|
|
3429
|
+
if (!this.isMultistream || !localTrack) {
|
|
3430
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3431
|
+
await this.unpublishTrack(oldTrack);
|
|
3609
3432
|
}
|
|
3433
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
3610
3434
|
}
|
|
3611
3435
|
|
|
3612
3436
|
/**
|
|
3613
|
-
*
|
|
3614
|
-
*
|
|
3615
|
-
*
|
|
3616
|
-
*
|
|
3617
|
-
* @
|
|
3437
|
+
* Stores the reference to a new screen share track, sets up the required event listeners
|
|
3438
|
+
* on it, cleans up previous track, etc.
|
|
3439
|
+
* It also sends the floor grant/release request.
|
|
3440
|
+
*
|
|
3441
|
+
* @param {LocalDisplayTrack | undefined} localDisplayTrack local camera track
|
|
3442
|
+
* @returns {Promise<void>}
|
|
3618
3443
|
*/
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
const settings = rawLocalShareTrack.getSettings();
|
|
3444
|
+
private async setLocalShareTrack(localDisplayTrack?: LocalDisplayTrack) {
|
|
3445
|
+
const oldTrack = this.mediaProperties.shareTrack;
|
|
3622
3446
|
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
);
|
|
3447
|
+
oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3448
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3626
3449
|
|
|
3627
|
-
|
|
3450
|
+
this.mediaProperties.setLocalShareTrack(localDisplayTrack);
|
|
3628
3451
|
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
// @ts-ignore
|
|
3635
|
-
displaySurface: settings.displaySurface,
|
|
3636
|
-
// @ts-ignore
|
|
3637
|
-
cursor: settings.cursor,
|
|
3638
|
-
});
|
|
3639
|
-
LoggerProxy.logger.log(
|
|
3640
|
-
'Meeting:index#setLocalShareTrack --> Screen settings.',
|
|
3641
|
-
JSON.stringify(this.mediaProperties.mediaSettings.screen)
|
|
3642
|
-
);
|
|
3452
|
+
localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3453
|
+
localDisplayTrack?.on(
|
|
3454
|
+
LocalTrackEvents.UnderlyingTrackChange,
|
|
3455
|
+
this.underlyingLocalTrackChangeHandler
|
|
3456
|
+
);
|
|
3643
3457
|
|
|
3644
|
-
|
|
3458
|
+
this.mediaProperties.mediaDirection.sendShare = !!localDisplayTrack;
|
|
3645
3459
|
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
file: 'meeting/index',
|
|
3650
|
-
function: 'setLocalShareTrack',
|
|
3651
|
-
},
|
|
3652
|
-
EVENT_TRIGGERS.MEDIA_READY,
|
|
3653
|
-
{
|
|
3654
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
3655
|
-
track: rawLocalShareTrack,
|
|
3656
|
-
}
|
|
3657
|
-
);
|
|
3658
|
-
} else if (this.mediaProperties.shareTrack) {
|
|
3659
|
-
this.mediaProperties.shareTrack.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3660
|
-
this.mediaProperties.shareTrack.stop(); // todo: this line should be removed once SPARK-399695 is done
|
|
3661
|
-
this.mediaProperties.setLocalShareTrack(null);
|
|
3460
|
+
if (!this.isMultistream || !localDisplayTrack) {
|
|
3461
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3462
|
+
await this.unpublishTrack(oldTrack);
|
|
3662
3463
|
}
|
|
3464
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
3663
3465
|
}
|
|
3664
3466
|
|
|
3665
3467
|
/**
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
* @
|
|
3670
|
-
* @
|
|
3468
|
+
* Removes references to local tracks. This function should be called
|
|
3469
|
+
* on cleanup when we leave the meeting etc.
|
|
3470
|
+
*
|
|
3471
|
+
* @internal
|
|
3472
|
+
* @returns {void}
|
|
3671
3473
|
*/
|
|
3672
|
-
public
|
|
3673
|
-
const {audioTrack, videoTrack} = this.mediaProperties;
|
|
3474
|
+
public cleanupLocalTracks() {
|
|
3475
|
+
const {audioTrack, videoTrack, shareTrack} = this.mediaProperties;
|
|
3674
3476
|
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
.then(() => {
|
|
3678
|
-
if (audioTrack || videoTrack) {
|
|
3679
|
-
Trigger.trigger(
|
|
3680
|
-
this,
|
|
3681
|
-
{
|
|
3682
|
-
file: 'meeting/index',
|
|
3683
|
-
function: 'closeLocalStream',
|
|
3684
|
-
},
|
|
3685
|
-
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3686
|
-
{
|
|
3687
|
-
type: EVENT_TYPES.LOCAL,
|
|
3688
|
-
}
|
|
3689
|
-
);
|
|
3690
|
-
}
|
|
3691
|
-
});
|
|
3692
|
-
}
|
|
3477
|
+
audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3478
|
+
audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3693
3479
|
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
* @returns {undefined}
|
|
3697
|
-
* @event media:stopped
|
|
3698
|
-
* @public
|
|
3699
|
-
* @memberof Meeting
|
|
3700
|
-
*/
|
|
3701
|
-
public closeLocalShare() {
|
|
3702
|
-
const track = this.mediaProperties.shareTrack;
|
|
3480
|
+
videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3481
|
+
videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3703
3482
|
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
Trigger.trigger(
|
|
3707
|
-
this,
|
|
3708
|
-
{
|
|
3709
|
-
file: 'meeting/index',
|
|
3710
|
-
function: 'closeLocalShare',
|
|
3711
|
-
},
|
|
3712
|
-
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3713
|
-
{
|
|
3714
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
3715
|
-
}
|
|
3716
|
-
);
|
|
3717
|
-
}
|
|
3718
|
-
});
|
|
3719
|
-
}
|
|
3483
|
+
shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3484
|
+
shareTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3720
3485
|
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
* @public
|
|
3725
|
-
* @memberof Meeting
|
|
3726
|
-
*/
|
|
3727
|
-
public unsetLocalVideoTrack() {
|
|
3728
|
-
this.mediaProperties.unsetLocalVideoTrack();
|
|
3729
|
-
}
|
|
3486
|
+
this.mediaProperties.setLocalAudioTrack(undefined);
|
|
3487
|
+
this.mediaProperties.setLocalVideoTrack(undefined);
|
|
3488
|
+
this.mediaProperties.setLocalShareTrack(undefined);
|
|
3730
3489
|
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3490
|
+
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3491
|
+
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3492
|
+
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3493
|
+
|
|
3494
|
+
// WCME doesn't unpublish tracks when multistream connection is closed, so we do it here
|
|
3495
|
+
// (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
|
|
3496
|
+
audioTrack?.setPublished(false);
|
|
3497
|
+
videoTrack?.setPublished(false);
|
|
3498
|
+
shareTrack?.setPublished(false);
|
|
3739
3499
|
}
|
|
3740
3500
|
|
|
3741
3501
|
/**
|
|
@@ -3802,6 +3562,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3802
3562
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3803
3563
|
}
|
|
3804
3564
|
|
|
3565
|
+
this.audio = null;
|
|
3566
|
+
this.video = null;
|
|
3567
|
+
|
|
3805
3568
|
return Promise.resolve();
|
|
3806
3569
|
}
|
|
3807
3570
|
|
|
@@ -3833,250 +3596,38 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3833
3596
|
}
|
|
3834
3597
|
|
|
3835
3598
|
/**
|
|
3836
|
-
*
|
|
3837
|
-
* @
|
|
3599
|
+
* Shorthand function to join AND set up media
|
|
3600
|
+
* @param {Object} options - options to join with media
|
|
3601
|
+
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
3602
|
+
* @param {MediaDirection} [options.mediaOptions] - see #addMedia()
|
|
3603
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
3838
3604
|
* @public
|
|
3839
3605
|
* @memberof Meeting
|
|
3606
|
+
* @example
|
|
3607
|
+
* joinWithMedia({
|
|
3608
|
+
* joinOptions: {resourceId: 'resourceId' },
|
|
3609
|
+
* mediaOptions: {
|
|
3610
|
+
* localTracks: { microphone: microphoneTrack, camera: cameraTrack }
|
|
3611
|
+
* }
|
|
3612
|
+
* })
|
|
3840
3613
|
*/
|
|
3841
|
-
public
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3849
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3850
|
-
}
|
|
3614
|
+
public joinWithMedia(
|
|
3615
|
+
options: {
|
|
3616
|
+
joinOptions?: any;
|
|
3617
|
+
mediaOptions?: AddMediaOptions;
|
|
3618
|
+
} = {}
|
|
3619
|
+
) {
|
|
3620
|
+
const {mediaOptions, joinOptions} = options;
|
|
3851
3621
|
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
.
|
|
3861
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.audioTrack);
|
|
3862
|
-
Metrics.postEvent({
|
|
3863
|
-
event: eventType.MUTED,
|
|
3864
|
-
meeting: this,
|
|
3865
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: MetricsMediaType.AUDIO},
|
|
3866
|
-
});
|
|
3867
|
-
})
|
|
3868
|
-
.catch((error) => {
|
|
3869
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MUTE_AUDIO_FAILURE, {
|
|
3870
|
-
correlation_id: this.correlationId,
|
|
3871
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3872
|
-
reason: error.message,
|
|
3873
|
-
stack: error.stack,
|
|
3874
|
-
});
|
|
3875
|
-
|
|
3876
|
-
throw error;
|
|
3877
|
-
}),
|
|
3878
|
-
{
|
|
3879
|
-
logText: `Meeting:index#muteAudio --> correlationId=${this.correlationId} muting audio`,
|
|
3880
|
-
}
|
|
3881
|
-
);
|
|
3882
|
-
}
|
|
3883
|
-
|
|
3884
|
-
/**
|
|
3885
|
-
* Unmute meeting audio
|
|
3886
|
-
* @returns {Promise} resolves data from muting audio {mute, self} or rejects if there is no audio set
|
|
3887
|
-
* @public
|
|
3888
|
-
* @memberof Meeting
|
|
3889
|
-
*/
|
|
3890
|
-
public unmuteAudio() {
|
|
3891
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3892
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3893
|
-
}
|
|
3894
|
-
|
|
3895
|
-
// @ts-ignore
|
|
3896
|
-
if (!this.mediaId) {
|
|
3897
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3898
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3899
|
-
}
|
|
3900
|
-
|
|
3901
|
-
if (!this.audio) {
|
|
3902
|
-
return Promise.reject(new ParameterError('no audio control associated to the meeting'));
|
|
3903
|
-
}
|
|
3904
|
-
|
|
3905
|
-
// First, send the control to unmute the participant on the server
|
|
3906
|
-
return logRequest(
|
|
3907
|
-
this.audio
|
|
3908
|
-
.handleClientRequest(this, false)
|
|
3909
|
-
.then(() => {
|
|
3910
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.audioTrack);
|
|
3911
|
-
Metrics.postEvent({
|
|
3912
|
-
event: eventType.UNMUTED,
|
|
3913
|
-
meeting: this,
|
|
3914
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: MetricsMediaType.AUDIO},
|
|
3915
|
-
});
|
|
3916
|
-
})
|
|
3917
|
-
.catch((error) => {
|
|
3918
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UNMUTE_AUDIO_FAILURE, {
|
|
3919
|
-
correlation_id: this.correlationId,
|
|
3920
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3921
|
-
reason: error.message,
|
|
3922
|
-
stack: error.stack,
|
|
3923
|
-
});
|
|
3924
|
-
|
|
3925
|
-
throw error;
|
|
3926
|
-
}),
|
|
3927
|
-
{
|
|
3928
|
-
logText: `Meeting:index#unmuteAudio --> correlationId=${this.correlationId} unmuting audio`,
|
|
3929
|
-
}
|
|
3930
|
-
);
|
|
3931
|
-
}
|
|
3932
|
-
|
|
3933
|
-
/**
|
|
3934
|
-
* Mute the video for a meeting
|
|
3935
|
-
* @returns {Promise} resolves data from muting video {mute, self} or rejects if there is no video set
|
|
3936
|
-
* @public
|
|
3937
|
-
* @memberof Meeting
|
|
3938
|
-
*/
|
|
3939
|
-
public muteVideo() {
|
|
3940
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3941
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3942
|
-
}
|
|
3943
|
-
|
|
3944
|
-
// @ts-ignore
|
|
3945
|
-
if (!this.mediaId) {
|
|
3946
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3947
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3948
|
-
}
|
|
3949
|
-
|
|
3950
|
-
if (!this.video) {
|
|
3951
|
-
return Promise.reject(new ParameterError('no video control associated to the meeting'));
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
|
-
return logRequest(
|
|
3955
|
-
this.video
|
|
3956
|
-
.handleClientRequest(this, true)
|
|
3957
|
-
.then(() => {
|
|
3958
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.videoTrack);
|
|
3959
|
-
Metrics.postEvent({
|
|
3960
|
-
event: eventType.MUTED,
|
|
3961
|
-
meeting: this,
|
|
3962
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: MetricsMediaType.VIDEO},
|
|
3963
|
-
});
|
|
3964
|
-
})
|
|
3965
|
-
.catch((error) => {
|
|
3966
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MUTE_VIDEO_FAILURE, {
|
|
3967
|
-
correlation_id: this.correlationId,
|
|
3968
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3969
|
-
reason: error.message,
|
|
3970
|
-
stack: error.stack,
|
|
3971
|
-
});
|
|
3972
|
-
|
|
3973
|
-
throw error;
|
|
3974
|
-
}),
|
|
3975
|
-
{
|
|
3976
|
-
logText: `Meeting:index#muteVideo --> correlationId=${this.correlationId} muting video`,
|
|
3977
|
-
}
|
|
3978
|
-
);
|
|
3979
|
-
}
|
|
3980
|
-
|
|
3981
|
-
/**
|
|
3982
|
-
* Unmute meeting video
|
|
3983
|
-
* @returns {Promise} resolves data from muting video {mute, self} or rejects if there is no video set
|
|
3984
|
-
* @public
|
|
3985
|
-
* @memberof Meeting
|
|
3986
|
-
*/
|
|
3987
|
-
public unmuteVideo() {
|
|
3988
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3989
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3990
|
-
}
|
|
3991
|
-
|
|
3992
|
-
// @ts-ignore
|
|
3993
|
-
if (!this.mediaId) {
|
|
3994
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3995
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3996
|
-
}
|
|
3997
|
-
|
|
3998
|
-
if (!this.video) {
|
|
3999
|
-
return Promise.reject(new ParameterError('no audio control associated to the meeting'));
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
|
-
return logRequest(
|
|
4003
|
-
this.video
|
|
4004
|
-
.handleClientRequest(this, false)
|
|
4005
|
-
.then(() => {
|
|
4006
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.videoTrack);
|
|
4007
|
-
Metrics.postEvent({
|
|
4008
|
-
event: eventType.UNMUTED,
|
|
4009
|
-
meeting: this,
|
|
4010
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: MetricsMediaType.VIDEO},
|
|
4011
|
-
});
|
|
4012
|
-
})
|
|
4013
|
-
.catch((error) => {
|
|
4014
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UNMUTE_VIDEO_FAILURE, {
|
|
4015
|
-
correlation_id: this.correlationId,
|
|
4016
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
4017
|
-
reason: error.message,
|
|
4018
|
-
stack: error.stack,
|
|
4019
|
-
});
|
|
4020
|
-
|
|
4021
|
-
throw error;
|
|
4022
|
-
}),
|
|
4023
|
-
{
|
|
4024
|
-
logText: `Meeting:index#unmuteVideo --> correlationId=${this.correlationId} unmuting video`,
|
|
4025
|
-
}
|
|
4026
|
-
);
|
|
4027
|
-
}
|
|
4028
|
-
|
|
4029
|
-
/**
|
|
4030
|
-
* Shorthand function to join AND set up media
|
|
4031
|
-
* @param {Object} options - options to join with media
|
|
4032
|
-
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
4033
|
-
* @param {MediaDirection} options.mediaSettings - see #addMedia()
|
|
4034
|
-
* @param {AudioVideo} [options.audioVideoOptions] - see #getMediaStreams()
|
|
4035
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia(), local: see getMediaStreams()}
|
|
4036
|
-
* @public
|
|
4037
|
-
* @memberof Meeting
|
|
4038
|
-
* @example
|
|
4039
|
-
* joinWithMedia({
|
|
4040
|
-
* joinOptions: {resourceId: 'resourceId' },
|
|
4041
|
-
* mediaSettings: {
|
|
4042
|
-
* sendAudio: true,
|
|
4043
|
-
* sendVideo: true,
|
|
4044
|
-
* sendShare: false,
|
|
4045
|
-
* receiveVideo:true,
|
|
4046
|
-
* receiveAudio: true,
|
|
4047
|
-
* receiveShare: true
|
|
4048
|
-
* }
|
|
4049
|
-
* audioVideoOptions: {
|
|
4050
|
-
* audio: 'audioDeviceId',
|
|
4051
|
-
* video: 'videoDeviceId'
|
|
4052
|
-
* }})
|
|
4053
|
-
*/
|
|
4054
|
-
public joinWithMedia(
|
|
4055
|
-
options: {
|
|
4056
|
-
joinOptions?: any;
|
|
4057
|
-
mediaSettings: any;
|
|
4058
|
-
audioVideoOptions?: any;
|
|
4059
|
-
} = {} as any
|
|
4060
|
-
) {
|
|
4061
|
-
// TODO: add validations for parameters
|
|
4062
|
-
const {mediaSettings, joinOptions, audioVideoOptions} = options;
|
|
4063
|
-
|
|
4064
|
-
return this.join(joinOptions)
|
|
4065
|
-
.then((joinResponse) =>
|
|
4066
|
-
this.getMediaStreams(mediaSettings, audioVideoOptions).then(([localStream, localShare]) =>
|
|
4067
|
-
this.addMedia({
|
|
4068
|
-
mediaSettings,
|
|
4069
|
-
localShare,
|
|
4070
|
-
localStream,
|
|
4071
|
-
}).then((mediaResponse) => ({
|
|
4072
|
-
join: joinResponse,
|
|
4073
|
-
media: mediaResponse,
|
|
4074
|
-
local: [localStream, localShare],
|
|
4075
|
-
}))
|
|
4076
|
-
)
|
|
4077
|
-
)
|
|
4078
|
-
.catch((error) => {
|
|
4079
|
-
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
3622
|
+
return this.join(joinOptions)
|
|
3623
|
+
.then((joinResponse) =>
|
|
3624
|
+
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
3625
|
+
join: joinResponse,
|
|
3626
|
+
media: mediaResponse,
|
|
3627
|
+
}))
|
|
3628
|
+
)
|
|
3629
|
+
.catch((error) => {
|
|
3630
|
+
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
4080
3631
|
|
|
4081
3632
|
Metrics.sendBehavioralMetric(
|
|
4082
3633
|
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
@@ -4508,18 +4059,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4508
4059
|
return Promise.reject(error);
|
|
4509
4060
|
}
|
|
4510
4061
|
|
|
4511
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality);
|
|
4512
4062
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality);
|
|
4513
4063
|
}
|
|
4514
4064
|
|
|
4515
4065
|
if (typeof options.meetingQuality === 'object') {
|
|
4516
|
-
if (
|
|
4517
|
-
|
|
4518
|
-
!QUALITY_LEVELS[options.meetingQuality.remote]
|
|
4519
|
-
) {
|
|
4520
|
-
const errorMessage = `Meeting:index#join --> ${
|
|
4521
|
-
options.meetingQuality.local || options.meetingQuality.remote
|
|
4522
|
-
} not defined`;
|
|
4066
|
+
if (!QUALITY_LEVELS[options.meetingQuality.remote]) {
|
|
4067
|
+
const errorMessage = `Meeting:index#join --> ${options.meetingQuality.remote} not defined`;
|
|
4523
4068
|
|
|
4524
4069
|
LoggerProxy.logger.error(errorMessage);
|
|
4525
4070
|
|
|
@@ -4531,9 +4076,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4531
4076
|
return Promise.reject(new Error(errorMessage));
|
|
4532
4077
|
}
|
|
4533
4078
|
|
|
4534
|
-
if (options.meetingQuality.local) {
|
|
4535
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality.local);
|
|
4536
|
-
}
|
|
4537
4079
|
if (options.meetingQuality.remote) {
|
|
4538
4080
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality.remote);
|
|
4539
4081
|
}
|
|
@@ -4840,14 +4382,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4840
4382
|
},
|
|
4841
4383
|
};
|
|
4842
4384
|
|
|
4843
|
-
|
|
4844
|
-
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4845
|
-
|
|
4846
|
-
// close the existing local tracks
|
|
4847
|
-
await this.closeLocalStream();
|
|
4848
|
-
await this.closeLocalShare();
|
|
4385
|
+
this.cleanupLocalTracks();
|
|
4849
4386
|
|
|
4850
|
-
this.mediaProperties.
|
|
4387
|
+
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4388
|
+
this.mediaProperties.unsetRemoteMedia();
|
|
4851
4389
|
|
|
4852
4390
|
// when a move to is intiated by the client , Locus delets the existing media node from the server as soon the DX answers the meeting
|
|
4853
4391
|
// once the DX answers we establish connection back the media server with only receiveShare enabled
|
|
@@ -4930,165 +4468,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4930
4468
|
});
|
|
4931
4469
|
}
|
|
4932
4470
|
|
|
4933
|
-
/**
|
|
4934
|
-
* Get local media streams based on options passed
|
|
4935
|
-
*
|
|
4936
|
-
* NOTE: this method can only be used with transcoded meetings, not with multistream meetings
|
|
4937
|
-
*
|
|
4938
|
-
* @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
|
|
4939
|
-
* @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
4940
|
-
* @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
4941
|
-
* @returns {Promise} see #Media.getUserMedia
|
|
4942
|
-
* @public
|
|
4943
|
-
* @todo should be static, or moved so can be called outside of a meeting
|
|
4944
|
-
* @memberof Meeting
|
|
4945
|
-
*/
|
|
4946
|
-
getMediaStreams = (
|
|
4947
|
-
mediaDirection: any,
|
|
4948
|
-
// This return an OBJECT {video: {height, widght}}
|
|
4949
|
-
// eslint-disable-next-line default-param-last
|
|
4950
|
-
audioVideo: any = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
|
|
4951
|
-
sharePreferences?: any
|
|
4952
|
-
) => {
|
|
4953
|
-
if (
|
|
4954
|
-
mediaDirection &&
|
|
4955
|
-
(mediaDirection.sendAudio || mediaDirection.sendVideo || mediaDirection.sendShare)
|
|
4956
|
-
) {
|
|
4957
|
-
if (
|
|
4958
|
-
mediaDirection &&
|
|
4959
|
-
mediaDirection.sendAudio &&
|
|
4960
|
-
mediaDirection.sendVideo &&
|
|
4961
|
-
mediaDirection.sendShare &&
|
|
4962
|
-
isBrowser('safari')
|
|
4963
|
-
) {
|
|
4964
|
-
LoggerProxy.logger.warn(
|
|
4965
|
-
'Meeting:index#getMediaStreams --> Setting `sendShare` to FALSE, due to complications with Safari'
|
|
4966
|
-
);
|
|
4967
|
-
|
|
4968
|
-
mediaDirection.sendShare = false;
|
|
4969
|
-
|
|
4970
|
-
LoggerProxy.logger.warn(
|
|
4971
|
-
'Meeting:index#getMediaStreams --> Enabling `sendShare` along with `sendAudio` & `sendVideo`, on Safari, causes a failure while setting up a screen share at the same time as the camera+mic stream'
|
|
4972
|
-
);
|
|
4973
|
-
LoggerProxy.logger.warn(
|
|
4974
|
-
'Meeting:index#getMediaStreams --> Please use `meeting.shareScreen()` to manually start the screen share after successfully joining the meeting'
|
|
4975
|
-
);
|
|
4976
|
-
}
|
|
4977
|
-
|
|
4978
|
-
if (audioVideo && isString(audioVideo)) {
|
|
4979
|
-
if (Object.keys(VIDEO_RESOLUTIONS).includes(audioVideo)) {
|
|
4980
|
-
this.mediaProperties.setLocalQualityLevel(audioVideo);
|
|
4981
|
-
audioVideo = {video: VIDEO_RESOLUTIONS[audioVideo].video};
|
|
4982
|
-
} else {
|
|
4983
|
-
throw new ParameterError(
|
|
4984
|
-
`${audioVideo} not supported. Either pass level from pre-defined resolutions or pass complete audioVideo object`
|
|
4985
|
-
);
|
|
4986
|
-
}
|
|
4987
|
-
}
|
|
4988
|
-
|
|
4989
|
-
if (!audioVideo.video) {
|
|
4990
|
-
audioVideo = {
|
|
4991
|
-
...audioVideo,
|
|
4992
|
-
video: {
|
|
4993
|
-
...audioVideo.video,
|
|
4994
|
-
...VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel].video,
|
|
4995
|
-
},
|
|
4996
|
-
};
|
|
4997
|
-
}
|
|
4998
|
-
// extract deviceId if exists otherwise default to null.
|
|
4999
|
-
const {deviceId: preferredVideoDevice} = (audioVideo && audioVideo.video) || {deviceId: null};
|
|
5000
|
-
const lastVideoDeviceId = this.mediaProperties.getVideoDeviceId();
|
|
5001
|
-
|
|
5002
|
-
if (preferredVideoDevice) {
|
|
5003
|
-
// Store new preferred video input device
|
|
5004
|
-
this.mediaProperties.setVideoDeviceId(preferredVideoDevice);
|
|
5005
|
-
} else if (lastVideoDeviceId) {
|
|
5006
|
-
// no new video preference specified so use last stored value,
|
|
5007
|
-
// works with empty object {} or media constraint.
|
|
5008
|
-
// eslint-disable-next-line no-param-reassign
|
|
5009
|
-
audioVideo = {
|
|
5010
|
-
...audioVideo,
|
|
5011
|
-
video: {
|
|
5012
|
-
...audioVideo.video,
|
|
5013
|
-
deviceId: lastVideoDeviceId,
|
|
5014
|
-
},
|
|
5015
|
-
};
|
|
5016
|
-
}
|
|
5017
|
-
|
|
5018
|
-
return Media.getSupportedDevice({
|
|
5019
|
-
sendAudio: mediaDirection.sendAudio,
|
|
5020
|
-
sendVideo: mediaDirection.sendVideo,
|
|
5021
|
-
})
|
|
5022
|
-
.catch((error) =>
|
|
5023
|
-
Promise.reject(
|
|
5024
|
-
new MediaError(
|
|
5025
|
-
'Given constraints do not match permission set for either camera or microphone',
|
|
5026
|
-
error
|
|
5027
|
-
)
|
|
5028
|
-
)
|
|
5029
|
-
)
|
|
5030
|
-
.then((devicePermissions) =>
|
|
5031
|
-
Media.getUserMedia(
|
|
5032
|
-
{
|
|
5033
|
-
...mediaDirection,
|
|
5034
|
-
sendAudio: devicePermissions.sendAudio,
|
|
5035
|
-
sendVideo: devicePermissions.sendVideo,
|
|
5036
|
-
isSharing: this.shareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE,
|
|
5037
|
-
},
|
|
5038
|
-
audioVideo,
|
|
5039
|
-
sharePreferences,
|
|
5040
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5041
|
-
this.config
|
|
5042
|
-
).catch((error) => {
|
|
5043
|
-
// Whenever there is a failure when trying to access a user's device
|
|
5044
|
-
// report it as an Behavioral metric
|
|
5045
|
-
// This gives visibility into common errors and can help
|
|
5046
|
-
// with further troubleshooting
|
|
5047
|
-
const metricName = BEHAVIORAL_METRICS.GET_USER_MEDIA_FAILURE;
|
|
5048
|
-
const data = {
|
|
5049
|
-
correlation_id: this.correlationId,
|
|
5050
|
-
locus_id: this.locusUrl?.split('/').pop(),
|
|
5051
|
-
reason: error.message,
|
|
5052
|
-
stack: error.stack,
|
|
5053
|
-
};
|
|
5054
|
-
const metadata = {
|
|
5055
|
-
type: error.name,
|
|
5056
|
-
};
|
|
5057
|
-
|
|
5058
|
-
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
5059
|
-
throw new MediaError('Unable to retrieve media streams', error);
|
|
5060
|
-
})
|
|
5061
|
-
);
|
|
5062
|
-
}
|
|
5063
|
-
|
|
5064
|
-
return Promise.reject(
|
|
5065
|
-
new MediaError('At least one of the mediaDirection value should be true')
|
|
5066
|
-
);
|
|
5067
|
-
};
|
|
5068
|
-
|
|
5069
|
-
/**
|
|
5070
|
-
* Checks if the machine has at least one audio or video device
|
|
5071
|
-
* @param {Object} options
|
|
5072
|
-
* @param {Boolean} options.sendAudio
|
|
5073
|
-
* @param {Boolean} options.sendVideo
|
|
5074
|
-
* @returns {Object}
|
|
5075
|
-
* @memberof Meetings
|
|
5076
|
-
*/
|
|
5077
|
-
getSupportedDevices = ({
|
|
5078
|
-
sendAudio = true,
|
|
5079
|
-
sendVideo = true,
|
|
5080
|
-
}: {
|
|
5081
|
-
sendAudio: boolean;
|
|
5082
|
-
sendVideo: boolean;
|
|
5083
|
-
}) => Media.getSupportedDevice({sendAudio, sendVideo});
|
|
5084
|
-
|
|
5085
|
-
/**
|
|
5086
|
-
* Get the devices from the Media module
|
|
5087
|
-
* @returns {Promise} resolves to an array of DeviceInfo
|
|
5088
|
-
* @memberof Meetings
|
|
5089
|
-
*/
|
|
5090
|
-
getDevices = () => Media.getDevices();
|
|
5091
|
-
|
|
5092
4471
|
/**
|
|
5093
4472
|
* Handles ROAP_FAILURE event from the webrtc media connection
|
|
5094
4473
|
*
|
|
@@ -5531,13 +4910,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5531
4910
|
}
|
|
5532
4911
|
|
|
5533
4912
|
/**
|
|
5534
|
-
* Creates a webrtc media connection
|
|
4913
|
+
* Creates a webrtc media connection and publishes tracks to it
|
|
5535
4914
|
*
|
|
5536
4915
|
* @param {Object} turnServerInfo TURN server information
|
|
5537
4916
|
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
5538
4917
|
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
5539
4918
|
*/
|
|
5540
|
-
createMediaConnection(turnServerInfo, bundlePolicy) {
|
|
4919
|
+
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
4920
|
+
// create the actual media connection
|
|
5541
4921
|
const mc = Media.createMediaConnection(this.isMultistream, this.getMediaConnectionDebugId(), {
|
|
5542
4922
|
mediaProperties: this.mediaProperties,
|
|
5543
4923
|
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
@@ -5552,6 +4932,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5552
4932
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
5553
4933
|
this.setupMediaConnectionListeners();
|
|
5554
4934
|
|
|
4935
|
+
// publish the tracks
|
|
4936
|
+
if (this.mediaProperties.audioTrack) {
|
|
4937
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
4938
|
+
}
|
|
4939
|
+
if (this.mediaProperties.videoTrack) {
|
|
4940
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
4941
|
+
}
|
|
4942
|
+
if (this.mediaProperties.shareTrack) {
|
|
4943
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
4944
|
+
}
|
|
4945
|
+
|
|
5555
4946
|
return mc;
|
|
5556
4947
|
}
|
|
5557
4948
|
|
|
@@ -5579,24 +4970,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5579
4970
|
}
|
|
5580
4971
|
|
|
5581
4972
|
/**
|
|
5582
|
-
*
|
|
5583
|
-
*
|
|
5584
|
-
* @param {
|
|
5585
|
-
* @param {MediaDirection} options.mediaSettings pass media options
|
|
5586
|
-
* @param {MediaStream} options.localStream
|
|
5587
|
-
* @param {MediaStream} options.localShare
|
|
5588
|
-
* @param {BundlePolicy} options.bundlePolicy bundle policy for multistream meetings
|
|
5589
|
-
* @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
|
|
4973
|
+
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
4974
|
+
*
|
|
4975
|
+
* @param {AddMediaOptions} options
|
|
5590
4976
|
* @returns {Promise}
|
|
5591
4977
|
* @public
|
|
5592
4978
|
* @memberof Meeting
|
|
5593
4979
|
*/
|
|
5594
|
-
addMedia(options:
|
|
4980
|
+
addMedia(options: AddMediaOptions = {}) {
|
|
5595
4981
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
5596
4982
|
|
|
5597
4983
|
let turnDiscoverySkippedReason;
|
|
5598
4984
|
let turnServerUsed = false;
|
|
5599
4985
|
|
|
4986
|
+
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
4987
|
+
|
|
5600
4988
|
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
5601
4989
|
return Promise.reject(new MeetingNotActiveError());
|
|
5602
4990
|
}
|
|
@@ -5610,10 +4998,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5610
4998
|
return Promise.reject(new UserInLobbyError());
|
|
5611
4999
|
}
|
|
5612
5000
|
|
|
5613
|
-
const {
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5001
|
+
const {
|
|
5002
|
+
localTracks,
|
|
5003
|
+
audioEnabled = true,
|
|
5004
|
+
videoEnabled = true,
|
|
5005
|
+
receiveShare = true,
|
|
5006
|
+
remoteMediaManagerConfig,
|
|
5007
|
+
bundlePolicy,
|
|
5008
|
+
} = options;
|
|
5617
5009
|
|
|
5618
5010
|
Metrics.postEvent({
|
|
5619
5011
|
event: eventType.MEDIA_CAPABILITIES,
|
|
@@ -5638,34 +5030,61 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5638
5030
|
},
|
|
5639
5031
|
});
|
|
5640
5032
|
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5033
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any tracks are published
|
|
5034
|
+
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5035
|
+
this.mediaProperties.setMediaDirection({
|
|
5036
|
+
sendAudio: audioEnabled,
|
|
5037
|
+
sendVideo: videoEnabled,
|
|
5038
|
+
sendShare: false,
|
|
5039
|
+
receiveAudio: audioEnabled,
|
|
5040
|
+
receiveVideo: videoEnabled,
|
|
5041
|
+
receiveShare,
|
|
5042
|
+
});
|
|
5043
|
+
|
|
5044
|
+
this.locusMediaRequest = new LocusMediaRequest(
|
|
5045
|
+
{
|
|
5046
|
+
correlationId: this.correlationId,
|
|
5047
|
+
device: {
|
|
5048
|
+
url: this.deviceUrl,
|
|
5049
|
+
// @ts-ignore
|
|
5050
|
+
deviceType: this.config.deviceType,
|
|
5051
|
+
},
|
|
5052
|
+
preferTranscoding: !this.isMultistream,
|
|
5053
|
+
},
|
|
5054
|
+
{
|
|
5055
|
+
// @ts-ignore
|
|
5056
|
+
parent: this.webex,
|
|
5057
|
+
}
|
|
5058
|
+
);
|
|
5059
|
+
|
|
5060
|
+
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5061
|
+
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5062
|
+
|
|
5063
|
+
this.annotationInfo = localTracks?.annotationInfo;
|
|
5064
|
+
|
|
5065
|
+
const promises = [];
|
|
5066
|
+
|
|
5067
|
+
// setup all the references to local tracks in this.mediaProperties before creating media connection
|
|
5068
|
+
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5069
|
+
if (localTracks?.microphone) {
|
|
5070
|
+
promises.push(this.setLocalAudioTrack(localTracks.microphone));
|
|
5071
|
+
}
|
|
5072
|
+
if (localTracks?.camera) {
|
|
5073
|
+
promises.push(this.setLocalVideoTrack(localTracks.camera));
|
|
5074
|
+
}
|
|
5075
|
+
if (localTracks?.screenShare?.video) {
|
|
5076
|
+
promises.push(this.setLocalShareTrack(localTracks.screenShare.video));
|
|
5077
|
+
}
|
|
5078
|
+
|
|
5079
|
+
return Promise.all(promises)
|
|
5659
5080
|
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
5660
|
-
.then((turnDiscoveryObject) => {
|
|
5081
|
+
.then(async (turnDiscoveryObject) => {
|
|
5661
5082
|
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
5662
5083
|
turnServerUsed = !turnDiscoverySkippedReason;
|
|
5663
5084
|
|
|
5664
5085
|
const {turnServerInfo} = turnDiscoveryObject;
|
|
5665
5086
|
|
|
5666
|
-
this.
|
|
5667
|
-
|
|
5668
|
-
const mc = this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
5087
|
+
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
5669
5088
|
|
|
5670
5089
|
if (this.isMultistream) {
|
|
5671
5090
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -5690,16 +5109,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5690
5109
|
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
5691
5110
|
);
|
|
5692
5111
|
|
|
5693
|
-
|
|
5112
|
+
await this.remoteMediaManager.start();
|
|
5694
5113
|
}
|
|
5695
5114
|
|
|
5696
|
-
|
|
5115
|
+
await mc.initiateOffer();
|
|
5697
5116
|
})
|
|
5698
5117
|
.then(() => {
|
|
5699
5118
|
this.setMercuryListener();
|
|
5700
5119
|
})
|
|
5701
5120
|
.then(() =>
|
|
5702
|
-
|
|
5121
|
+
getDevices().then((devices) => {
|
|
5703
5122
|
MeetingUtil.handleDeviceLogging(devices);
|
|
5704
5123
|
})
|
|
5705
5124
|
)
|
|
@@ -5739,7 +5158,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5739
5158
|
|
|
5740
5159
|
// eslint-disable-next-line func-names
|
|
5741
5160
|
// eslint-disable-next-line prefer-arrow-callback
|
|
5742
|
-
if (this.type === _CALL_) {
|
|
5161
|
+
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
5743
5162
|
resolve();
|
|
5744
5163
|
}
|
|
5745
5164
|
const joiningTimer = setInterval(() => {
|
|
@@ -5764,17 +5183,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5764
5183
|
})
|
|
5765
5184
|
)
|
|
5766
5185
|
.then(() => {
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
// When the self state changes to JOINED then request the floor
|
|
5774
|
-
this.floorGrantPending = true;
|
|
5186
|
+
if (localTracks?.screenShare?.video) {
|
|
5187
|
+
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
5188
|
+
lambda: async () => {
|
|
5189
|
+
return this.requestScreenShareFloor();
|
|
5190
|
+
},
|
|
5191
|
+
});
|
|
5775
5192
|
}
|
|
5776
|
-
|
|
5777
|
-
return {};
|
|
5778
5193
|
})
|
|
5779
5194
|
.then(() => this.mediaProperties.getCurrentConnectionType())
|
|
5780
5195
|
.then((connectionType) => {
|
|
@@ -5869,7 +5284,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5869
5284
|
* @private
|
|
5870
5285
|
* @memberof Meeting
|
|
5871
5286
|
*/
|
|
5872
|
-
private enqueueMediaUpdate(mediaUpdateType: string, options: any) {
|
|
5287
|
+
private enqueueMediaUpdate(mediaUpdateType: string, options: any): Promise<void> {
|
|
5873
5288
|
if (mediaUpdateType === MEDIA_UPDATE_TYPE.LAMBDA && typeof options?.lambda !== 'function') {
|
|
5874
5289
|
return Promise.reject(
|
|
5875
5290
|
new Error('lambda must be specified when enqueuing MEDIA_UPDATE_TYPE.LAMBDA')
|
|
@@ -5932,21 +5347,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5932
5347
|
LoggerProxy.logger.log(
|
|
5933
5348
|
`Meeting:index#processNextQueuedMediaUpdate --> performing delayed media update type=${mediaUpdateType}`
|
|
5934
5349
|
);
|
|
5350
|
+
let mediaUpdate = Promise.resolve();
|
|
5351
|
+
|
|
5935
5352
|
switch (mediaUpdateType) {
|
|
5936
|
-
case MEDIA_UPDATE_TYPE.
|
|
5937
|
-
this.
|
|
5938
|
-
break;
|
|
5939
|
-
case MEDIA_UPDATE_TYPE.AUDIO:
|
|
5940
|
-
this.updateAudio(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5941
|
-
break;
|
|
5942
|
-
case MEDIA_UPDATE_TYPE.VIDEO:
|
|
5943
|
-
this.updateVideo(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5944
|
-
break;
|
|
5945
|
-
case MEDIA_UPDATE_TYPE.SHARE:
|
|
5946
|
-
this.updateShare(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5353
|
+
case MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION:
|
|
5354
|
+
mediaUpdate = this.updateTranscodedMediaConnection();
|
|
5947
5355
|
break;
|
|
5948
5356
|
case MEDIA_UPDATE_TYPE.LAMBDA:
|
|
5949
|
-
options.lambda()
|
|
5357
|
+
mediaUpdate = options.lambda();
|
|
5358
|
+
break;
|
|
5359
|
+
case MEDIA_UPDATE_TYPE.UPDATE_MEDIA:
|
|
5360
|
+
mediaUpdate = this.updateMedia(options);
|
|
5950
5361
|
break;
|
|
5951
5362
|
default:
|
|
5952
5363
|
LoggerProxy.logger.error(
|
|
@@ -5954,384 +5365,84 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5954
5365
|
);
|
|
5955
5366
|
break;
|
|
5956
5367
|
}
|
|
5368
|
+
|
|
5369
|
+
mediaUpdate
|
|
5370
|
+
.then(pendingPromiseResolve, pendingPromiseReject)
|
|
5371
|
+
.then(() => this.processNextQueuedMediaUpdate());
|
|
5957
5372
|
}
|
|
5958
5373
|
};
|
|
5959
5374
|
|
|
5960
5375
|
/**
|
|
5961
|
-
*
|
|
5962
|
-
*
|
|
5376
|
+
* Updates the media connection - it allows to enable/disable all audio/video/share in the meeting.
|
|
5377
|
+
* This does not affect the published tracks, so for example if a microphone track is published and
|
|
5378
|
+
* updateMedia({audioEnabled: false}) is called, the audio will not be sent or received anymore,
|
|
5379
|
+
* but the track's "published" state is not changed and when updateMedia({audioEnabled: true}) is called,
|
|
5380
|
+
* the sending of the audio from the same track will resume.
|
|
5381
|
+
*
|
|
5963
5382
|
* @param {Object} options
|
|
5964
|
-
* @param {
|
|
5965
|
-
* @param {
|
|
5966
|
-
* @param {
|
|
5383
|
+
* @param {boolean} options.audioEnabled [optional] enables/disables receiving and sending of main audio in the meeting
|
|
5384
|
+
* @param {boolean} options.videoEnabled [optional] enables/disables receiving and sending of main video in the meeting
|
|
5385
|
+
* @param {boolean} options.shareEnabled [optional] enables/disables receiving and sending of screen share in the meeting
|
|
5967
5386
|
* @returns {Promise}
|
|
5968
5387
|
* @public
|
|
5969
5388
|
* @memberof Meeting
|
|
5970
5389
|
*/
|
|
5971
|
-
public updateMedia(
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
) {
|
|
5978
|
-
const LOG_HEADER = 'Meeting:index#updateMedia -->';
|
|
5390
|
+
public async updateMedia(options: {
|
|
5391
|
+
audioEnabled?: boolean;
|
|
5392
|
+
videoEnabled?: boolean;
|
|
5393
|
+
receiveShare?: boolean;
|
|
5394
|
+
}) {
|
|
5395
|
+
this.checkMediaConnection();
|
|
5979
5396
|
|
|
5980
|
-
|
|
5981
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.ALL, options);
|
|
5982
|
-
}
|
|
5397
|
+
const {audioEnabled, videoEnabled, receiveShare} = options;
|
|
5983
5398
|
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
return this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5988
|
-
}
|
|
5989
|
-
|
|
5990
|
-
const {localStream, localShare, mediaSettings} = options;
|
|
5991
|
-
|
|
5992
|
-
const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
|
|
5993
|
-
|
|
5994
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
5995
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
5996
|
-
}
|
|
5997
|
-
|
|
5998
|
-
return MeetingUtil.validateOptions(options)
|
|
5999
|
-
.then(() => this.preMedia(localStream, localShare, mediaSettings))
|
|
6000
|
-
.then(() =>
|
|
6001
|
-
this.mediaProperties.webrtcMediaConnection
|
|
6002
|
-
.update({
|
|
6003
|
-
localTracks: {
|
|
6004
|
-
audio: this.mediaProperties.mediaDirection.sendAudio
|
|
6005
|
-
? this.mediaProperties.audioTrack.underlyingTrack
|
|
6006
|
-
: null,
|
|
6007
|
-
video: this.mediaProperties.mediaDirection.sendVideo
|
|
6008
|
-
? this.mediaProperties.videoTrack.underlyingTrack
|
|
6009
|
-
: null,
|
|
6010
|
-
screenShareVideo: this.mediaProperties.mediaDirection.sendShare
|
|
6011
|
-
? this.mediaProperties.shareTrack.underlyingTrack
|
|
6012
|
-
: null,
|
|
6013
|
-
},
|
|
6014
|
-
direction: {
|
|
6015
|
-
audio: Media.getDirection(
|
|
6016
|
-
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6017
|
-
this.mediaProperties.mediaDirection.sendAudio
|
|
6018
|
-
),
|
|
6019
|
-
video: Media.getDirection(
|
|
6020
|
-
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6021
|
-
this.mediaProperties.mediaDirection.sendVideo
|
|
6022
|
-
),
|
|
6023
|
-
screenShareVideo: Media.getDirection(
|
|
6024
|
-
this.mediaProperties.mediaDirection.receiveShare,
|
|
6025
|
-
this.mediaProperties.mediaDirection.sendShare
|
|
6026
|
-
),
|
|
6027
|
-
},
|
|
6028
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6029
|
-
})
|
|
6030
|
-
.then(() => {
|
|
6031
|
-
LoggerProxy.logger.info(`${LOG_HEADER} webrtcMediaConnection.update done`);
|
|
6032
|
-
})
|
|
6033
|
-
.catch((error) => {
|
|
6034
|
-
LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
|
|
6035
|
-
|
|
6036
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
6037
|
-
correlation_id: this.correlationId,
|
|
6038
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
6039
|
-
reason: error.message,
|
|
6040
|
-
stack: error.stack,
|
|
6041
|
-
});
|
|
6042
|
-
|
|
6043
|
-
throw error;
|
|
6044
|
-
})
|
|
6045
|
-
// todo: the following code used to be called always after sending the roap message with the new SDP
|
|
6046
|
-
// now it's called independently from the roap message (so might be before it), check if that's OK
|
|
6047
|
-
// if not, ensure it's called after (now it's called after roap message is sent out, but we're not
|
|
6048
|
-
// waiting for sendRoapMediaRequest() to be resolved)
|
|
6049
|
-
.then(() =>
|
|
6050
|
-
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
6051
|
-
lambda: () => {
|
|
6052
|
-
return Promise.resolve()
|
|
6053
|
-
.then(() =>
|
|
6054
|
-
this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus)
|
|
6055
|
-
)
|
|
6056
|
-
.then((startShare) => {
|
|
6057
|
-
// This is a special case if we do an /floor grant followed by /media
|
|
6058
|
-
// we actually get a OFFER from the server and a GLAR condition happens
|
|
6059
|
-
if (startShare) {
|
|
6060
|
-
// We are assuming that the clients are connected when doing an update
|
|
6061
|
-
return this.requestScreenShareFloor();
|
|
6062
|
-
}
|
|
6063
|
-
|
|
6064
|
-
return Promise.resolve();
|
|
6065
|
-
});
|
|
6066
|
-
},
|
|
6067
|
-
})
|
|
6068
|
-
)
|
|
6069
|
-
);
|
|
6070
|
-
}
|
|
5399
|
+
LoggerProxy.logger.log(
|
|
5400
|
+
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
5401
|
+
);
|
|
6071
5402
|
|
|
6072
|
-
/**
|
|
6073
|
-
* Update the main audio track with new parameters
|
|
6074
|
-
*
|
|
6075
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
6076
|
-
*
|
|
6077
|
-
* @param {Object} options
|
|
6078
|
-
* @param {boolean} options.sendAudio
|
|
6079
|
-
* @param {boolean} options.receiveAudio
|
|
6080
|
-
* @param {MediaStream} options.stream Stream that contains the audio track to update
|
|
6081
|
-
* @returns {Promise}
|
|
6082
|
-
* @public
|
|
6083
|
-
* @memberof Meeting
|
|
6084
|
-
*/
|
|
6085
|
-
public async updateAudio(options: {
|
|
6086
|
-
sendAudio: boolean;
|
|
6087
|
-
receiveAudio: boolean;
|
|
6088
|
-
stream: MediaStream;
|
|
6089
|
-
}) {
|
|
6090
|
-
if (this.isMultistream) {
|
|
6091
|
-
throw new Error(
|
|
6092
|
-
'updateAudio() is not supported with multistream, use publishTracks/unpublishTracks instead'
|
|
6093
|
-
);
|
|
6094
|
-
}
|
|
6095
5403
|
if (!this.canUpdateMedia()) {
|
|
6096
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.
|
|
6097
|
-
}
|
|
6098
|
-
const {sendAudio, receiveAudio, stream} = options;
|
|
6099
|
-
const track = MeetingUtil.getTrack(stream).audioTrack;
|
|
6100
|
-
|
|
6101
|
-
if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
|
|
6102
|
-
return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
|
|
5404
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
6103
5405
|
}
|
|
6104
5406
|
|
|
6105
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
6106
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
6107
|
-
}
|
|
6108
|
-
|
|
6109
|
-
return MeetingUtil.validateOptions({sendAudio, localStream: stream})
|
|
6110
|
-
.then(() =>
|
|
6111
|
-
this.mediaProperties.webrtcMediaConnection.update({
|
|
6112
|
-
localTracks: {audio: track},
|
|
6113
|
-
direction: {
|
|
6114
|
-
audio: Media.getDirection(receiveAudio, sendAudio),
|
|
6115
|
-
video: Media.getDirection(
|
|
6116
|
-
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6117
|
-
this.mediaProperties.mediaDirection.sendVideo
|
|
6118
|
-
),
|
|
6119
|
-
screenShareVideo: Media.getDirection(
|
|
6120
|
-
this.mediaProperties.mediaDirection.receiveShare,
|
|
6121
|
-
this.mediaProperties.mediaDirection.sendShare
|
|
6122
|
-
),
|
|
6123
|
-
},
|
|
6124
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6125
|
-
})
|
|
6126
|
-
)
|
|
6127
|
-
.then(() => {
|
|
6128
|
-
this.setLocalAudioTrack(track);
|
|
6129
|
-
// todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
|
|
6130
|
-
this.mediaProperties.mediaDirection.sendAudio = sendAudio;
|
|
6131
|
-
this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
|
|
6132
|
-
|
|
6133
|
-
// audio state could be undefined if you have not sent audio before
|
|
6134
|
-
this.audio =
|
|
6135
|
-
this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection, true);
|
|
6136
|
-
});
|
|
6137
|
-
}
|
|
6138
|
-
|
|
6139
|
-
/**
|
|
6140
|
-
* Update the main video track with new parameters
|
|
6141
|
-
*
|
|
6142
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
6143
|
-
*
|
|
6144
|
-
* @param {Object} options
|
|
6145
|
-
* @param {boolean} options.sendVideo
|
|
6146
|
-
* @param {boolean} options.receiveVideo
|
|
6147
|
-
* @param {MediaStream} options.stream Stream that contains the video track to update
|
|
6148
|
-
* @returns {Promise}
|
|
6149
|
-
* @public
|
|
6150
|
-
* @memberof Meeting
|
|
6151
|
-
*/
|
|
6152
|
-
public updateVideo(options: {sendVideo: boolean; receiveVideo: boolean; stream: MediaStream}) {
|
|
6153
5407
|
if (this.isMultistream) {
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
|
|
6160
|
-
}
|
|
6161
|
-
const {sendVideo, receiveVideo, stream} = options;
|
|
6162
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
5408
|
+
if (videoEnabled !== undefined) {
|
|
5409
|
+
throw new Error(
|
|
5410
|
+
'enabling/disabling video in a meeting is not supported for multistream, it can only be done upfront when calling addMedia()'
|
|
5411
|
+
);
|
|
5412
|
+
}
|
|
6163
5413
|
|
|
6164
|
-
|
|
6165
|
-
|
|
5414
|
+
if (receiveShare !== undefined) {
|
|
5415
|
+
throw new Error(
|
|
5416
|
+
'toggling receiveShare in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
6166
5419
|
}
|
|
6167
5420
|
|
|
6168
|
-
if (
|
|
6169
|
-
|
|
5421
|
+
if (audioEnabled !== undefined) {
|
|
5422
|
+
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5423
|
+
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5424
|
+
this.audio.enable(this, audioEnabled);
|
|
6170
5425
|
}
|
|
6171
5426
|
|
|
6172
|
-
|
|
6173
|
-
.
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
direction: {
|
|
6177
|
-
audio: Media.getDirection(
|
|
6178
|
-
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6179
|
-
this.mediaProperties.mediaDirection.sendAudio
|
|
6180
|
-
),
|
|
6181
|
-
video: Media.getDirection(receiveVideo, sendVideo),
|
|
6182
|
-
screenShareVideo: Media.getDirection(
|
|
6183
|
-
this.mediaProperties.mediaDirection.receiveShare,
|
|
6184
|
-
this.mediaProperties.mediaDirection.sendShare
|
|
6185
|
-
),
|
|
6186
|
-
},
|
|
6187
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6188
|
-
})
|
|
6189
|
-
)
|
|
6190
|
-
.then(() => {
|
|
6191
|
-
this.setLocalVideoTrack(track);
|
|
6192
|
-
this.mediaProperties.mediaDirection.sendVideo = sendVideo;
|
|
6193
|
-
this.mediaProperties.mediaDirection.receiveVideo = receiveVideo;
|
|
6194
|
-
|
|
6195
|
-
// video state could be undefined if you have not sent video before
|
|
6196
|
-
this.video =
|
|
6197
|
-
this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection, true);
|
|
6198
|
-
});
|
|
6199
|
-
}
|
|
6200
|
-
|
|
6201
|
-
/**
|
|
6202
|
-
* Internal function when stopping a share stream, cleanup
|
|
6203
|
-
* @param {boolean} sendShare
|
|
6204
|
-
* @param {boolean} previousShareStatus
|
|
6205
|
-
* @returns {Promise}
|
|
6206
|
-
* @private
|
|
6207
|
-
* @memberof Meeting
|
|
6208
|
-
*/
|
|
6209
|
-
private checkForStopShare(sendShare: boolean, previousShareStatus: boolean) {
|
|
6210
|
-
if (sendShare && !previousShareStatus) {
|
|
6211
|
-
// When user starts sharing
|
|
6212
|
-
return Promise.resolve(true);
|
|
5427
|
+
if (videoEnabled !== undefined) {
|
|
5428
|
+
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5429
|
+
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5430
|
+
this.video.enable(this, videoEnabled);
|
|
6213
5431
|
}
|
|
6214
5432
|
|
|
6215
|
-
if (
|
|
6216
|
-
|
|
6217
|
-
return this.releaseScreenShareFloor().then(() => Promise.resolve(false));
|
|
5433
|
+
if (receiveShare !== undefined) {
|
|
5434
|
+
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
6218
5435
|
}
|
|
6219
5436
|
|
|
6220
|
-
return Promise.resolve();
|
|
6221
|
-
}
|
|
6222
|
-
|
|
6223
|
-
/**
|
|
6224
|
-
* Update the share streams, can be used to start sharing
|
|
6225
|
-
*
|
|
6226
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
6227
|
-
*
|
|
6228
|
-
* @param {Object} options
|
|
6229
|
-
* @param {boolean} options.sendShare
|
|
6230
|
-
* @param {boolean} options.receiveShare
|
|
6231
|
-
* @returns {Promise}
|
|
6232
|
-
* @public
|
|
6233
|
-
* @memberof Meeting
|
|
6234
|
-
*/
|
|
6235
|
-
public updateShare(options: {
|
|
6236
|
-
sendShare?: boolean;
|
|
6237
|
-
receiveShare?: boolean;
|
|
6238
|
-
stream?: any;
|
|
6239
|
-
skipSignalingCheck?: boolean;
|
|
6240
|
-
}) {
|
|
6241
5437
|
if (this.isMultistream) {
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
}
|
|
6246
|
-
|
|
6247
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
|
|
6248
|
-
}
|
|
6249
|
-
const {sendShare, receiveShare, stream} = options;
|
|
6250
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
6251
|
-
|
|
6252
|
-
if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
|
|
6253
|
-
return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
|
|
6254
|
-
}
|
|
6255
|
-
|
|
6256
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
6257
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
5438
|
+
if (audioEnabled !== undefined) {
|
|
5439
|
+
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5440
|
+
}
|
|
5441
|
+
} else {
|
|
5442
|
+
await this.updateTranscodedMediaConnection();
|
|
6258
5443
|
}
|
|
6259
5444
|
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
this.setLocalShareTrack(track);
|
|
6263
|
-
|
|
6264
|
-
return MeetingUtil.validateOptions({sendShare, localShare: stream})
|
|
6265
|
-
.then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
|
|
6266
|
-
.then((startShare) =>
|
|
6267
|
-
this.mediaProperties.webrtcMediaConnection
|
|
6268
|
-
.update({
|
|
6269
|
-
localTracks: {screenShareVideo: track},
|
|
6270
|
-
direction: {
|
|
6271
|
-
audio: Media.getDirection(
|
|
6272
|
-
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6273
|
-
this.mediaProperties.mediaDirection.sendAudio
|
|
6274
|
-
),
|
|
6275
|
-
video: Media.getDirection(
|
|
6276
|
-
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6277
|
-
this.mediaProperties.mediaDirection.sendVideo
|
|
6278
|
-
),
|
|
6279
|
-
screenShareVideo: Media.getDirection(receiveShare, sendShare),
|
|
6280
|
-
},
|
|
6281
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6282
|
-
})
|
|
6283
|
-
.then(() =>
|
|
6284
|
-
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
6285
|
-
lambda: async () => {
|
|
6286
|
-
if (startShare) {
|
|
6287
|
-
return this.requestScreenShareFloor();
|
|
6288
|
-
}
|
|
6289
|
-
|
|
6290
|
-
return undefined;
|
|
6291
|
-
},
|
|
6292
|
-
})
|
|
6293
|
-
)
|
|
6294
|
-
)
|
|
6295
|
-
.then(() => {
|
|
6296
|
-
this.mediaProperties.mediaDirection.sendShare = sendShare;
|
|
6297
|
-
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
6298
|
-
})
|
|
6299
|
-
.catch((error) => {
|
|
6300
|
-
this.unsetLocalShareTrack();
|
|
6301
|
-
throw error;
|
|
6302
|
-
});
|
|
6303
|
-
}
|
|
6304
|
-
|
|
6305
|
-
/**
|
|
6306
|
-
* Do all the attach media pre set up before executing the actual attach
|
|
6307
|
-
* @param {MediaStream} localStream
|
|
6308
|
-
* @param {MediaStream} localShare
|
|
6309
|
-
* @param {MediaDirection} mediaSettings
|
|
6310
|
-
* @returns {undefined}
|
|
6311
|
-
* @private
|
|
6312
|
-
* @memberof Meeting
|
|
6313
|
-
*/
|
|
6314
|
-
private preMedia(localStream: MediaStream, localShare: MediaStream, mediaSettings: any) {
|
|
6315
|
-
// eslint-disable-next-line no-warning-comments
|
|
6316
|
-
// TODO wire into default config. There's currently an issue with the stateless plugin or how we register
|
|
6317
|
-
// @ts-ignore - config coming from registerPlugin
|
|
6318
|
-
this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
|
|
6319
|
-
|
|
6320
|
-
// for multistream, this.audio and this.video are created when publishTracks() is called
|
|
6321
|
-
if (!this.isMultistream) {
|
|
6322
|
-
this.audio =
|
|
6323
|
-
this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection, true);
|
|
6324
|
-
this.video =
|
|
6325
|
-
this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection, true);
|
|
6326
|
-
}
|
|
6327
|
-
// Validation is already done in addMedia so no need to check if the lenght is greater then 0
|
|
6328
|
-
this.setLocalTracks(localStream);
|
|
6329
|
-
if (this.isMultistream && localShare) {
|
|
6330
|
-
throw new Error(
|
|
6331
|
-
'calling addMedia() with localShare stream is not supported when using multistream'
|
|
6332
|
-
);
|
|
6333
|
-
}
|
|
6334
|
-
this.setLocalShareTrack(MeetingUtil.getTrack(localShare).videoTrack);
|
|
5445
|
+
return undefined;
|
|
6335
5446
|
}
|
|
6336
5447
|
|
|
6337
5448
|
/**
|
|
@@ -6573,56 +5684,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6573
5684
|
* @memberof Meeting
|
|
6574
5685
|
*/
|
|
6575
5686
|
private requestScreenShareFloor() {
|
|
6576
|
-
|
|
5687
|
+
if (!this.mediaProperties.shareTrack || !this.mediaProperties.mediaDirection.sendShare) {
|
|
5688
|
+
LoggerProxy.logger.log(
|
|
5689
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareTrack=${
|
|
5690
|
+
this.mediaProperties.shareTrack ? 'yes' : 'no'
|
|
5691
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
5692
|
+
);
|
|
6577
5693
|
|
|
6578
|
-
|
|
6579
|
-
|
|
5694
|
+
return Promise.resolve({});
|
|
5695
|
+
}
|
|
5696
|
+
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
5697
|
+
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
6580
5698
|
|
|
6581
|
-
|
|
6582
|
-
.
|
|
6583
|
-
disposition: FLOOR_ACTION.GRANTED,
|
|
6584
|
-
personUrl: this.locusInfo.self.url,
|
|
6585
|
-
deviceUrl: this.deviceUrl,
|
|
6586
|
-
uri: content.url,
|
|
6587
|
-
resourceUrl: this.resourceUrl,
|
|
6588
|
-
annotationInfo: this.annotationInfo,
|
|
6589
|
-
})
|
|
6590
|
-
.then(() => {
|
|
6591
|
-
this.isSharing = true;
|
|
5699
|
+
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
5700
|
+
Metrics.postEvent({event: eventType.SHARE_INITIATED, meeting: this});
|
|
6592
5701
|
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
5702
|
+
return this.meetingRequest
|
|
5703
|
+
.changeMeetingFloor({
|
|
5704
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
5705
|
+
personUrl: this.locusInfo.self.url,
|
|
5706
|
+
deviceUrl: this.deviceUrl,
|
|
5707
|
+
uri: content.url,
|
|
5708
|
+
resourceUrl: this.resourceUrl,
|
|
5709
|
+
annotationInfo: this.annotationInfo,
|
|
5710
|
+
})
|
|
5711
|
+
.then(() => {
|
|
5712
|
+
this.isSharing = true;
|
|
6597
5713
|
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
5714
|
+
return Promise.resolve();
|
|
5715
|
+
})
|
|
5716
|
+
.catch((error) => {
|
|
5717
|
+
LoggerProxy.logger.error('Meeting:index#share --> Error ', error);
|
|
5718
|
+
|
|
5719
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_FAILURE, {
|
|
5720
|
+
correlation_id: this.correlationId,
|
|
5721
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5722
|
+
reason: error.message,
|
|
5723
|
+
stack: error.stack,
|
|
5724
|
+
});
|
|
5725
|
+
|
|
5726
|
+
return Promise.reject(error);
|
|
6603
5727
|
});
|
|
5728
|
+
}
|
|
6604
5729
|
|
|
6605
|
-
|
|
6606
|
-
});
|
|
5730
|
+
return Promise.reject(new ParameterError('Cannot share without content.'));
|
|
6607
5731
|
}
|
|
5732
|
+
this.floorGrantPending = true;
|
|
6608
5733
|
|
|
6609
|
-
return Promise.
|
|
5734
|
+
return Promise.resolve({});
|
|
6610
5735
|
}
|
|
6611
5736
|
|
|
6612
5737
|
/**
|
|
6613
|
-
*
|
|
6614
|
-
*
|
|
6615
|
-
*
|
|
6616
|
-
* @
|
|
5738
|
+
* Requests screen share floor if such request is pending.
|
|
5739
|
+
* It should be called whenever meeting state changes to JOINED
|
|
5740
|
+
*
|
|
5741
|
+
* @returns {void}
|
|
6617
5742
|
*/
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
...options,
|
|
6625
|
-
});
|
|
5743
|
+
private requestScreenShareFloorIfPending() {
|
|
5744
|
+
if (this.floorGrantPending && this.state === MEETING_STATE.STATES.JOINED) {
|
|
5745
|
+
this.requestScreenShareFloor().then(() => {
|
|
5746
|
+
this.floorGrantPending = false;
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
6626
5749
|
}
|
|
6627
5750
|
|
|
6628
5751
|
/**
|
|
@@ -6634,10 +5757,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6634
5757
|
private releaseScreenShareFloor() {
|
|
6635
5758
|
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
6636
5759
|
|
|
6637
|
-
if (content
|
|
5760
|
+
if (content) {
|
|
6638
5761
|
Metrics.postEvent({event: eventType.SHARE_STOPPED, meeting: this});
|
|
6639
5762
|
|
|
6640
|
-
if (content.floor
|
|
5763
|
+
if (content.floor?.beneficiary.id !== this.selfId) {
|
|
6641
5764
|
// remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
|
|
6642
5765
|
this.isSharing = false;
|
|
6643
5766
|
|
|
@@ -6669,7 +5792,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6669
5792
|
});
|
|
6670
5793
|
}
|
|
6671
5794
|
|
|
6672
|
-
|
|
5795
|
+
// according to Locus there is no content, so we don't need to release the floor (it's probably already been released)
|
|
5796
|
+
this.isSharing = false;
|
|
5797
|
+
|
|
5798
|
+
return Promise.resolve();
|
|
6673
5799
|
}
|
|
6674
5800
|
|
|
6675
5801
|
/**
|
|
@@ -6933,62 +6059,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6933
6059
|
});
|
|
6934
6060
|
}
|
|
6935
6061
|
|
|
6936
|
-
/**
|
|
6937
|
-
* Sets the quality of the local video stream
|
|
6938
|
-
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
6939
|
-
* @returns {Promise<MediaStream>} localStream
|
|
6940
|
-
*/
|
|
6941
|
-
setLocalVideoQuality(level: string) {
|
|
6942
|
-
LoggerProxy.logger.log(`Meeting:index#setLocalVideoQuality --> Setting quality to ${level}`);
|
|
6943
|
-
|
|
6944
|
-
if (!VIDEO_RESOLUTIONS[level]) {
|
|
6945
|
-
return this.rejectWithErrorLog(`Meeting:index#setLocalVideoQuality --> ${level} not defined`);
|
|
6946
|
-
}
|
|
6947
|
-
|
|
6948
|
-
if (!this.mediaProperties.mediaDirection.sendVideo) {
|
|
6949
|
-
return this.rejectWithErrorLog(
|
|
6950
|
-
'Meeting:index#setLocalVideoQuality --> unable to change video quality, sendVideo is disabled'
|
|
6951
|
-
);
|
|
6952
|
-
}
|
|
6953
|
-
|
|
6954
|
-
// If level is already the same, don't do anything
|
|
6955
|
-
if (level === this.mediaProperties.localQualityLevel) {
|
|
6956
|
-
LoggerProxy.logger.warn(
|
|
6957
|
-
`Meeting:index#setLocalQualityLevel --> Quality already set to ${level}`
|
|
6958
|
-
);
|
|
6959
|
-
|
|
6960
|
-
return Promise.resolve();
|
|
6961
|
-
}
|
|
6962
|
-
|
|
6963
|
-
// Set the quality level in properties
|
|
6964
|
-
this.mediaProperties.setLocalQualityLevel(level);
|
|
6965
|
-
|
|
6966
|
-
const mediaDirection = {
|
|
6967
|
-
sendAudio: this.mediaProperties.mediaDirection.sendAudio,
|
|
6968
|
-
sendVideo: this.mediaProperties.mediaDirection.sendVideo,
|
|
6969
|
-
sendShare: this.mediaProperties.mediaDirection.sendShare,
|
|
6970
|
-
};
|
|
6971
|
-
|
|
6972
|
-
// When changing local video quality level
|
|
6973
|
-
// Need to stop current track first as chrome doesn't support resolution upscaling(for eg. changing 480p to 720p)
|
|
6974
|
-
// Without feeding it a new track
|
|
6975
|
-
// open bug link: https://bugs.chromium.org/p/chromium/issues/detail?id=943469
|
|
6976
|
-
if (isBrowser('chrome') && this.mediaProperties.videoTrack)
|
|
6977
|
-
Media.stopTracks(this.mediaProperties.videoTrack);
|
|
6978
|
-
|
|
6979
|
-
return this.getMediaStreams(mediaDirection, VIDEO_RESOLUTIONS[level]).then(
|
|
6980
|
-
async ([localStream]) => {
|
|
6981
|
-
await this.updateVideo({
|
|
6982
|
-
sendVideo: true,
|
|
6983
|
-
receiveVideo: true,
|
|
6984
|
-
stream: localStream,
|
|
6985
|
-
});
|
|
6986
|
-
|
|
6987
|
-
return localStream;
|
|
6988
|
-
}
|
|
6989
|
-
);
|
|
6990
|
-
}
|
|
6991
|
-
|
|
6992
6062
|
/**
|
|
6993
6063
|
* Sets the quality level of the remote incoming media
|
|
6994
6064
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
@@ -7024,129 +6094,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7024
6094
|
// Set the quality level in properties
|
|
7025
6095
|
this.mediaProperties.setRemoteQualityLevel(level);
|
|
7026
6096
|
|
|
7027
|
-
return this.
|
|
7028
|
-
}
|
|
7029
|
-
|
|
7030
|
-
/**
|
|
7031
|
-
* This is deprecated, please use setLocalVideoQuality for setting local and setRemoteQualityLevel for remote
|
|
7032
|
-
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
7033
|
-
* @returns {Promise}
|
|
7034
|
-
* @deprecated After FHD support
|
|
7035
|
-
*/
|
|
7036
|
-
setMeetingQuality(level: string) {
|
|
7037
|
-
LoggerProxy.logger.log(`Meeting:index#setMeetingQuality --> Setting quality to ${level}`);
|
|
7038
|
-
|
|
7039
|
-
if (!QUALITY_LEVELS[level]) {
|
|
7040
|
-
return this.rejectWithErrorLog(`Meeting:index#setMeetingQuality --> ${level} not defined`);
|
|
7041
|
-
}
|
|
7042
|
-
|
|
7043
|
-
const previousLevel = {
|
|
7044
|
-
local: this.mediaProperties.localQualityLevel,
|
|
7045
|
-
remote: this.mediaProperties.remoteQualityLevel,
|
|
7046
|
-
};
|
|
7047
|
-
|
|
7048
|
-
// If level is already the same, don't do anything
|
|
7049
|
-
if (
|
|
7050
|
-
level === this.mediaProperties.localQualityLevel &&
|
|
7051
|
-
level === this.mediaProperties.remoteQualityLevel
|
|
7052
|
-
) {
|
|
7053
|
-
LoggerProxy.logger.warn(
|
|
7054
|
-
`Meeting:index#setMeetingQuality --> Quality already set to ${level}`
|
|
7055
|
-
);
|
|
7056
|
-
|
|
7057
|
-
return Promise.resolve();
|
|
7058
|
-
}
|
|
7059
|
-
|
|
7060
|
-
// Determine the direction of our current media
|
|
7061
|
-
const {receiveAudio, receiveVideo, sendVideo} = this.mediaProperties.mediaDirection;
|
|
7062
|
-
|
|
7063
|
-
return (sendVideo ? this.setLocalVideoQuality(level) : Promise.resolve())
|
|
7064
|
-
.then(() =>
|
|
7065
|
-
receiveAudio || receiveVideo ? this.setRemoteQualityLevel(level) : Promise.resolve()
|
|
7066
|
-
)
|
|
7067
|
-
.catch((error) => {
|
|
7068
|
-
// From troubleshooting it seems that the stream itself doesn't change the max-fs if the peer connection isn't stable
|
|
7069
|
-
this.mediaProperties.setLocalQualityLevel(previousLevel.local);
|
|
7070
|
-
this.mediaProperties.setRemoteQualityLevel(previousLevel.remote);
|
|
7071
|
-
|
|
7072
|
-
LoggerProxy.logger.error(`Meeting:index#setMeetingQuality --> ${error.message}`);
|
|
7073
|
-
|
|
7074
|
-
Metrics.sendBehavioralMetric(
|
|
7075
|
-
BEHAVIORAL_METRICS.SET_MEETING_QUALITY_FAILURE,
|
|
7076
|
-
{
|
|
7077
|
-
correlation_id: this.correlationId,
|
|
7078
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
7079
|
-
reason: error.message,
|
|
7080
|
-
stack: error.stack,
|
|
7081
|
-
},
|
|
7082
|
-
{
|
|
7083
|
-
type: error.name,
|
|
7084
|
-
}
|
|
7085
|
-
);
|
|
7086
|
-
|
|
7087
|
-
return Promise.reject(error);
|
|
7088
|
-
});
|
|
7089
|
-
}
|
|
7090
|
-
|
|
7091
|
-
/**
|
|
7092
|
-
*
|
|
7093
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
|
|
7094
|
-
*
|
|
7095
|
-
* @param {Object} options parameter
|
|
7096
|
-
* @param {Boolean} options.sendAudio send audio from the display share
|
|
7097
|
-
* @param {Boolean} options.sendShare send video from the display share
|
|
7098
|
-
* @param {Object} options.sharePreferences
|
|
7099
|
-
* @param {MediaTrackConstraints} options.sharePreferences.shareConstraints constraints to apply to video
|
|
7100
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints}
|
|
7101
|
-
* @param {Boolean} options.sharePreferences.highFrameRate if shareConstraints isn't provided, set default values based off of this boolean
|
|
7102
|
-
* @returns {Promise}
|
|
7103
|
-
*/
|
|
7104
|
-
shareScreen(
|
|
7105
|
-
options: {
|
|
7106
|
-
sendAudio: boolean;
|
|
7107
|
-
sendShare: boolean;
|
|
7108
|
-
sharePreferences: {shareConstraints: MediaTrackConstraints};
|
|
7109
|
-
} = {} as any
|
|
7110
|
-
) {
|
|
7111
|
-
LoggerProxy.logger.log('Meeting:index#shareScreen --> Getting local share');
|
|
7112
|
-
|
|
7113
|
-
const shareConstraints = {
|
|
7114
|
-
sendShare: true,
|
|
7115
|
-
sendAudio: false,
|
|
7116
|
-
...options,
|
|
7117
|
-
};
|
|
7118
|
-
|
|
7119
|
-
// @ts-ignore - config coming from registerPlugin
|
|
7120
|
-
return Media.getDisplayMedia(shareConstraints, this.config)
|
|
7121
|
-
.then((shareStream) =>
|
|
7122
|
-
this.updateShare({
|
|
7123
|
-
sendShare: true,
|
|
7124
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
7125
|
-
stream: shareStream,
|
|
7126
|
-
})
|
|
7127
|
-
)
|
|
7128
|
-
.catch((error) => {
|
|
7129
|
-
// Whenever there is a failure when trying to access a user's display
|
|
7130
|
-
// report it as an Behavioral metric
|
|
7131
|
-
// This gives visibility into common errors and can help
|
|
7132
|
-
// with further troubleshooting
|
|
7133
|
-
|
|
7134
|
-
// This metrics will get erros for getDisplayMedia and share errors for now
|
|
7135
|
-
// TODO: The getDisplayMedia errors need to be moved inside `media.getDisplayMedia`
|
|
7136
|
-
const metricName = BEHAVIORAL_METRICS.GET_DISPLAY_MEDIA_FAILURE;
|
|
7137
|
-
const data = {
|
|
7138
|
-
correlation_id: this.correlationId,
|
|
7139
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
7140
|
-
reason: error.message,
|
|
7141
|
-
stack: error.stack,
|
|
7142
|
-
};
|
|
7143
|
-
const metadata = {
|
|
7144
|
-
type: error.name,
|
|
7145
|
-
};
|
|
7146
|
-
|
|
7147
|
-
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
7148
|
-
throw new MediaError('Unable to retrieve display media stream', error);
|
|
7149
|
-
});
|
|
6097
|
+
return this.updateTranscodedMediaConnection();
|
|
7150
6098
|
}
|
|
7151
6099
|
|
|
7152
6100
|
/**
|
|
@@ -7159,34 +6107,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7159
6107
|
private handleShareTrackEnded = async () => {
|
|
7160
6108
|
if (this.wirelessShare) {
|
|
7161
6109
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
7162
|
-
} else
|
|
6110
|
+
} else {
|
|
7163
6111
|
try {
|
|
7164
|
-
|
|
7165
|
-
await this.releaseScreenShareFloor();
|
|
7166
|
-
}
|
|
6112
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo: screen share audio (SPARK-399690)
|
|
7167
6113
|
} catch (error) {
|
|
7168
6114
|
LoggerProxy.logger.log(
|
|
7169
6115
|
'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
|
|
7170
6116
|
error
|
|
7171
6117
|
);
|
|
7172
|
-
} finally {
|
|
7173
|
-
// todo: once SPARK-399695 is done, we will be able to just call this.setLocalShareTrack(null); here instead of the next 2 lines:
|
|
7174
|
-
this.mediaProperties.shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
7175
|
-
this.mediaProperties.setLocalShareTrack(null);
|
|
7176
|
-
|
|
7177
|
-
this.mediaProperties.mediaDirection.sendShare = false;
|
|
7178
6118
|
}
|
|
7179
|
-
} else {
|
|
7180
|
-
// Skip checking for a stable peerConnection
|
|
7181
|
-
// to allow immediately stopping screenshare
|
|
7182
|
-
this.stopShare({
|
|
7183
|
-
skipSignalingCheck: true,
|
|
7184
|
-
}).catch((error) => {
|
|
7185
|
-
LoggerProxy.logger.log(
|
|
7186
|
-
'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
|
|
7187
|
-
error
|
|
7188
|
-
);
|
|
7189
|
-
});
|
|
7190
6119
|
}
|
|
7191
6120
|
|
|
7192
6121
|
Trigger.trigger(
|
|
@@ -7197,7 +6126,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7197
6126
|
},
|
|
7198
6127
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
7199
6128
|
{
|
|
7200
|
-
|
|
6129
|
+
reason: SHARE_STOPPED_REASON.TRACK_ENDED,
|
|
7201
6130
|
}
|
|
7202
6131
|
);
|
|
7203
6132
|
};
|
|
@@ -7235,8 +6164,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7235
6164
|
* @returns {undefined}
|
|
7236
6165
|
*/
|
|
7237
6166
|
private handleMediaLogging(mediaProperties: {
|
|
7238
|
-
audioTrack
|
|
7239
|
-
videoTrack
|
|
6167
|
+
audioTrack?: LocalMicrophoneTrack;
|
|
6168
|
+
videoTrack?: LocalCameraTrack;
|
|
7240
6169
|
}) {
|
|
7241
6170
|
MeetingUtil.handleVideoLogging(mediaProperties.videoTrack);
|
|
7242
6171
|
MeetingUtil.handleAudioLogging(mediaProperties.audioTrack);
|
|
@@ -7661,7 +6590,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7661
6590
|
if (this.mediaProperties?.webrtcMediaConnection) {
|
|
7662
6591
|
return;
|
|
7663
6592
|
}
|
|
7664
|
-
throw new
|
|
6593
|
+
throw new NoMediaEstablishedYetError();
|
|
7665
6594
|
}
|
|
7666
6595
|
|
|
7667
6596
|
/**
|
|
@@ -7690,90 +6619,150 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7690
6619
|
}
|
|
7691
6620
|
}
|
|
7692
6621
|
|
|
7693
|
-
/**
|
|
7694
|
-
* Publishes specified local tracks in the meeting
|
|
6622
|
+
/** Updates the tracks being sent on the transcoded media connection
|
|
7695
6623
|
*
|
|
7696
|
-
* @
|
|
7697
|
-
* @returns {Promise}
|
|
6624
|
+
* @returns {Promise<void>}
|
|
7698
6625
|
*/
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
camera?: LocalCameraTrack;
|
|
7702
|
-
screenShare: {
|
|
7703
|
-
audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
|
|
7704
|
-
video?: LocalDisplayTrack;
|
|
7705
|
-
};
|
|
7706
|
-
annotationInfo?: AnnotationInfo;
|
|
7707
|
-
}): Promise<void> {
|
|
7708
|
-
this.checkMediaConnection();
|
|
6626
|
+
private updateTranscodedMediaConnection(): Promise<void> {
|
|
6627
|
+
const LOG_HEADER = 'Meeting:index#updateTranscodedMediaConnection -->';
|
|
7709
6628
|
|
|
7710
|
-
|
|
7711
|
-
throw new Error('publishTracks() only supported with multistream');
|
|
7712
|
-
}
|
|
7713
|
-
this.annotationInfo = tracks.annotationInfo;
|
|
7714
|
-
if (tracks.screenShare?.video) {
|
|
7715
|
-
const oldTrack = this.mediaProperties.shareTrack;
|
|
7716
|
-
const localDisplayTrack = tracks.screenShare?.video;
|
|
6629
|
+
LoggerProxy.logger.info(`${LOG_HEADER} starting`);
|
|
7717
6630
|
|
|
7718
|
-
|
|
6631
|
+
if (!this.canUpdateMedia()) {
|
|
6632
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION, {});
|
|
6633
|
+
}
|
|
7719
6634
|
|
|
7720
|
-
|
|
7721
|
-
|
|
6635
|
+
return this.mediaProperties.webrtcMediaConnection
|
|
6636
|
+
.update({
|
|
6637
|
+
localTracks: {
|
|
6638
|
+
audio: this.mediaProperties.audioTrack?.underlyingTrack || null,
|
|
6639
|
+
video: this.mediaProperties.videoTrack?.underlyingTrack || null,
|
|
6640
|
+
screenShareVideo: this.mediaProperties.shareTrack?.underlyingTrack || null,
|
|
6641
|
+
},
|
|
6642
|
+
direction: {
|
|
6643
|
+
audio: Media.getDirection(
|
|
6644
|
+
true,
|
|
6645
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6646
|
+
this.mediaProperties.mediaDirection.sendAudio
|
|
6647
|
+
),
|
|
6648
|
+
video: Media.getDirection(
|
|
6649
|
+
true,
|
|
6650
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6651
|
+
this.mediaProperties.mediaDirection.sendVideo
|
|
6652
|
+
),
|
|
6653
|
+
screenShareVideo: Media.getDirection(
|
|
6654
|
+
false,
|
|
6655
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
6656
|
+
this.mediaProperties.mediaDirection.sendShare
|
|
6657
|
+
),
|
|
6658
|
+
},
|
|
6659
|
+
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6660
|
+
})
|
|
6661
|
+
.then(() => {
|
|
6662
|
+
LoggerProxy.logger.info(`${LOG_HEADER} done`);
|
|
6663
|
+
})
|
|
6664
|
+
.catch((error) => {
|
|
6665
|
+
LoggerProxy.logger.error(`${LOG_HEADER} Error: `, error);
|
|
7722
6666
|
|
|
7723
|
-
|
|
6667
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
6668
|
+
correlation_id: this.correlationId,
|
|
6669
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6670
|
+
reason: error.message,
|
|
6671
|
+
stack: error.stack,
|
|
6672
|
+
});
|
|
7724
6673
|
|
|
7725
|
-
|
|
7726
|
-
|
|
6674
|
+
throw error;
|
|
6675
|
+
});
|
|
6676
|
+
}
|
|
7727
6677
|
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
6678
|
+
/**
|
|
6679
|
+
* Publishes a track.
|
|
6680
|
+
*
|
|
6681
|
+
* @param {LocalTrack} track to publish
|
|
6682
|
+
* @returns {Promise}
|
|
6683
|
+
*/
|
|
6684
|
+
private async publishTrack(track?: LocalTrack) {
|
|
6685
|
+
if (!track) {
|
|
6686
|
+
return;
|
|
7731
6687
|
}
|
|
7732
6688
|
|
|
7733
|
-
if (
|
|
7734
|
-
|
|
7735
|
-
|
|
6689
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6690
|
+
if (this.isMultistream) {
|
|
6691
|
+
await this.mediaProperties.webrtcMediaConnection.publishTrack(track);
|
|
6692
|
+
} else {
|
|
6693
|
+
track.setPublished(true); // for multistream, this call is done by WCME
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
7736
6697
|
|
|
7737
|
-
|
|
6698
|
+
/**
|
|
6699
|
+
* Un-publishes a track.
|
|
6700
|
+
*
|
|
6701
|
+
* @param {LocalTrack} track to unpublish
|
|
6702
|
+
* @returns {Promise}
|
|
6703
|
+
*/
|
|
6704
|
+
private async unpublishTrack(track?: LocalTrack) {
|
|
6705
|
+
if (!track) {
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
7738
6708
|
|
|
7739
|
-
|
|
7740
|
-
this.mediaProperties.
|
|
6709
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
6710
|
+
await this.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
|
|
6711
|
+
} else {
|
|
6712
|
+
track.setPublished(false); // for multistream, this call is done by WCME
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
7741
6715
|
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
6716
|
+
/**
|
|
6717
|
+
* Publishes specified local tracks in the meeting
|
|
6718
|
+
*
|
|
6719
|
+
* @param {Object} tracks
|
|
6720
|
+
* @returns {Promise}
|
|
6721
|
+
*/
|
|
6722
|
+
async publishTracks(tracks: LocalTracks): Promise<void> {
|
|
6723
|
+
this.checkMediaConnection();
|
|
7748
6724
|
|
|
7749
|
-
|
|
6725
|
+
this.annotationInfo = tracks.annotationInfo;
|
|
7750
6726
|
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
6727
|
+
if (
|
|
6728
|
+
!tracks.microphone &&
|
|
6729
|
+
!tracks.camera &&
|
|
6730
|
+
!tracks.screenShare?.audio &&
|
|
6731
|
+
!tracks.screenShare?.video
|
|
6732
|
+
) {
|
|
6733
|
+
// nothing to do
|
|
6734
|
+
return;
|
|
7754
6735
|
}
|
|
7755
6736
|
|
|
7756
|
-
|
|
7757
|
-
const oldTrack = this.mediaProperties.videoTrack;
|
|
7758
|
-
const localTrack = tracks.camera;
|
|
6737
|
+
let floorRequestNeeded = false;
|
|
7759
6738
|
|
|
7760
|
-
|
|
6739
|
+
if (tracks.screenShare?.video) {
|
|
6740
|
+
await this.setLocalShareTrack(tracks.screenShare?.video);
|
|
7761
6741
|
|
|
7762
|
-
|
|
7763
|
-
|
|
6742
|
+
floorRequestNeeded = true;
|
|
6743
|
+
}
|
|
7764
6744
|
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
} else {
|
|
7769
|
-
this.video.handleLocalTrackChange(this);
|
|
7770
|
-
}
|
|
6745
|
+
if (tracks.microphone) {
|
|
6746
|
+
await this.setLocalAudioTrack(tracks.microphone);
|
|
6747
|
+
}
|
|
7771
6748
|
|
|
7772
|
-
|
|
6749
|
+
if (tracks.camera) {
|
|
6750
|
+
await this.setLocalVideoTrack(tracks.camera);
|
|
6751
|
+
}
|
|
7773
6752
|
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
6753
|
+
if (!this.isMultistream) {
|
|
6754
|
+
await this.updateTranscodedMediaConnection();
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
if (floorRequestNeeded) {
|
|
6758
|
+
// we're sending the http request to Locus to request the screen share floor
|
|
6759
|
+
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
6760
|
+
// and also if sharing from the start, we need confluence to have been created
|
|
6761
|
+
await this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
6762
|
+
lambda: async () => {
|
|
6763
|
+
return this.requestScreenShareFloor();
|
|
6764
|
+
},
|
|
6765
|
+
});
|
|
7777
6766
|
}
|
|
7778
6767
|
}
|
|
7779
6768
|
|
|
@@ -7786,43 +6775,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7786
6775
|
async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
|
|
7787
6776
|
this.checkMediaConnection();
|
|
7788
6777
|
|
|
7789
|
-
|
|
7790
|
-
throw new Error('unpublishTracks() is only supported with multistream');
|
|
7791
|
-
}
|
|
7792
|
-
|
|
7793
|
-
const unpublishPromises = [];
|
|
6778
|
+
const promises = [];
|
|
7794
6779
|
|
|
7795
6780
|
for (const track of tracks.filter((t) => !!t)) {
|
|
7796
6781
|
if (track === this.mediaProperties.shareTrack) {
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
this.
|
|
7803
|
-
|
|
7804
|
-
unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
|
|
6782
|
+
try {
|
|
6783
|
+
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
6784
|
+
} catch (e) {
|
|
6785
|
+
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
6786
|
+
}
|
|
6787
|
+
promises.push(this.setLocalShareTrack(undefined));
|
|
7805
6788
|
}
|
|
7806
6789
|
|
|
7807
6790
|
if (track === this.mediaProperties.audioTrack) {
|
|
7808
|
-
this.
|
|
7809
|
-
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
7810
|
-
|
|
7811
|
-
track.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
7812
|
-
|
|
7813
|
-
unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
|
|
6791
|
+
promises.push(this.setLocalAudioTrack(undefined));
|
|
7814
6792
|
}
|
|
7815
6793
|
|
|
7816
6794
|
if (track === this.mediaProperties.videoTrack) {
|
|
7817
|
-
this.
|
|
7818
|
-
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
7819
|
-
|
|
7820
|
-
track.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
7821
|
-
|
|
7822
|
-
unpublishPromises.push(this.mediaProperties.webrtcMediaConnection.unpublishTrack(track));
|
|
6795
|
+
promises.push(this.setLocalVideoTrack(undefined));
|
|
7823
6796
|
}
|
|
7824
6797
|
}
|
|
7825
6798
|
|
|
7826
|
-
|
|
6799
|
+
if (!this.isMultistream) {
|
|
6800
|
+
promises.push(this.updateTranscodedMediaConnection());
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6803
|
+
await Promise.all(promises);
|
|
7827
6804
|
}
|
|
7828
6805
|
}
|