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