|
@@ -13,7 +13,6 @@ import clonedeep from 'lodash.clonedeep';
|
13
|
13
|
import JitsiTrackError from '../../JitsiTrackError';
|
14
|
14
|
import * as JitsiTrackErrors from '../../JitsiTrackErrors';
|
15
|
15
|
import CameraFacingMode from '../../service/RTC/CameraFacingMode';
|
16
|
|
-import * as MediaType from '../../service/RTC/MediaType';
|
17
|
16
|
import RTCEvents from '../../service/RTC/RTCEvents';
|
18
|
17
|
import Resolutions from '../../service/RTC/Resolutions';
|
19
|
18
|
import VideoType from '../../service/RTC/VideoType';
|
|
@@ -39,19 +38,6 @@ const eventEmitter = new EventEmitter();
|
39
|
38
|
|
40
|
39
|
const AVAILABLE_DEVICES_POLL_INTERVAL_TIME = 3000; // ms
|
41
|
40
|
|
42
|
|
-/**
|
43
|
|
- * Default resolution to obtain for video tracks if no resolution is specified.
|
44
|
|
- * This default is used for old gum flow only, as new gum flow uses
|
45
|
|
- * {@link DEFAULT_CONSTRAINTS}.
|
46
|
|
- */
|
47
|
|
-const OLD_GUM_DEFAULT_RESOLUTION = 720;
|
48
|
|
-
|
49
|
|
-/**
|
50
|
|
- * Default devices to obtain when no specific devices are specified. This
|
51
|
|
- * default is used for old gum flow only.
|
52
|
|
- */
|
53
|
|
-const OLD_GUM_DEFAULT_DEVICES = [ 'audio', 'video' ];
|
54
|
|
-
|
55
|
41
|
/**
|
56
|
42
|
* Default MediaStreamConstraints to use for calls to getUserMedia.
|
57
|
43
|
*
|
|
@@ -72,11 +58,6 @@ const DEFAULT_CONSTRAINTS = {
|
72
|
58
|
}
|
73
|
59
|
};
|
74
|
60
|
|
75
|
|
-/**
|
76
|
|
- * The default frame rate for Screen Sharing.
|
77
|
|
- */
|
78
|
|
-export const SS_DEFAULT_FRAME_RATE = 5;
|
79
|
|
-
|
80
|
61
|
// Currently audio output device change is supported only in Chrome and
|
81
|
62
|
// default output always has 'default' device ID
|
82
|
63
|
let audioOutputDeviceId = 'default'; // default device
|
|
@@ -95,9 +76,6 @@ let disableNS = false;
|
95
|
76
|
// Disables Automatic Gain Control
|
96
|
77
|
let disableAGC = false;
|
97
|
78
|
|
98
|
|
-// Disables Highpass Filter
|
99
|
|
-let disableHPF = false;
|
100
|
|
-
|
101
|
79
|
// Enables stereo.
|
102
|
80
|
let stereo = null;
|
103
|
81
|
|
|
@@ -115,246 +93,6 @@ function emptyFuncton() {
|
115
|
93
|
// no-op
|
116
|
94
|
}
|
117
|
95
|
|
118
|
|
-/**
|
119
|
|
- *
|
120
|
|
- * @param constraints
|
121
|
|
- * @param isNewStyleConstraintsSupported
|
122
|
|
- * @param resolution
|
123
|
|
- */
|
124
|
|
-function setResolutionConstraints(
|
125
|
|
- constraints,
|
126
|
|
- isNewStyleConstraintsSupported,
|
127
|
|
- resolution) {
|
128
|
|
- if (Resolutions[resolution]) {
|
129
|
|
- if (isNewStyleConstraintsSupported) {
|
130
|
|
- constraints.video.width = {
|
131
|
|
- ideal: Resolutions[resolution].width
|
132
|
|
- };
|
133
|
|
- constraints.video.height = {
|
134
|
|
- ideal: Resolutions[resolution].height
|
135
|
|
- };
|
136
|
|
- }
|
137
|
|
-
|
138
|
|
- constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
139
|
|
- constraints.video.mandatory.minHeight = Resolutions[resolution].height;
|
140
|
|
- }
|
141
|
|
-
|
142
|
|
- if (constraints.video.mandatory.minWidth) {
|
143
|
|
- constraints.video.mandatory.maxWidth
|
144
|
|
- = constraints.video.mandatory.minWidth;
|
145
|
|
- }
|
146
|
|
-
|
147
|
|
- if (constraints.video.mandatory.minHeight) {
|
148
|
|
- constraints.video.mandatory.maxHeight
|
149
|
|
- = constraints.video.mandatory.minHeight;
|
150
|
|
- }
|
151
|
|
-}
|
152
|
|
-
|
153
|
|
-/**
|
154
|
|
- * @param {string[]} um required user media types
|
155
|
|
- *
|
156
|
|
- * @param {Object} [options={}] optional parameters
|
157
|
|
- * @param {string} options.resolution
|
158
|
|
- * @param {number} options.bandwidth
|
159
|
|
- * @param {number} options.fps
|
160
|
|
- * @param {string} options.desktopStream
|
161
|
|
- * @param {string} options.cameraDeviceId
|
162
|
|
- * @param {string} options.micDeviceId
|
163
|
|
- * @param {CameraFacingMode} options.facingMode
|
164
|
|
- * @param {bool} firefox_fake_device
|
165
|
|
- * @param {Object} options.frameRate - used only for dekstop sharing.
|
166
|
|
- * @param {Object} options.frameRate.min - Minimum fps
|
167
|
|
- * @param {Object} options.frameRate.max - Maximum fps
|
168
|
|
- * @param {bool} options.screenShareAudio - Used by electron clients to
|
169
|
|
- * enable system audio screen sharing.
|
170
|
|
- */
|
171
|
|
-function getConstraints(um, options = {}) {
|
172
|
|
- const constraints = {
|
173
|
|
- audio: false,
|
174
|
|
- video: false
|
175
|
|
- };
|
176
|
|
-
|
177
|
|
- // Don't mix new and old style settings for Chromium as this leads
|
178
|
|
- // to TypeError in new Chromium versions. @see
|
179
|
|
- // https://bugs.chromium.org/p/chromium/issues/detail?id=614716
|
180
|
|
- // This is a temporary solution, in future we will fully split old and
|
181
|
|
- // new style constraints when new versions of Chromium and Firefox will
|
182
|
|
- // have stable support of new constraints format. For more information
|
183
|
|
- // @see https://github.com/jitsi/lib-jitsi-meet/pull/136
|
184
|
|
- const isNewStyleConstraintsSupported
|
185
|
|
- = browser.isFirefox()
|
186
|
|
- || browser.isWebKitBased()
|
187
|
|
- || browser.isReactNative();
|
188
|
|
-
|
189
|
|
- if (um.indexOf('video') >= 0) {
|
190
|
|
- // same behaviour as true
|
191
|
|
- constraints.video = { mandatory: {},
|
192
|
|
- optional: [] };
|
193
|
|
-
|
194
|
|
- if (options.cameraDeviceId) {
|
195
|
|
- if (isNewStyleConstraintsSupported) {
|
196
|
|
- // New style of setting device id.
|
197
|
|
- constraints.video.deviceId = options.cameraDeviceId;
|
198
|
|
- }
|
199
|
|
-
|
200
|
|
- // Old style.
|
201
|
|
- constraints.video.mandatory.sourceId = options.cameraDeviceId;
|
202
|
|
- } else {
|
203
|
|
- // Prefer the front i.e. user-facing camera (to the back i.e.
|
204
|
|
- // environment-facing camera, for example).
|
205
|
|
- // TODO: Maybe use "exact" syntax if options.facingMode is defined,
|
206
|
|
- // but this probably needs to be decided when updating other
|
207
|
|
- // constraints, as we currently don't use "exact" syntax anywhere.
|
208
|
|
- const facingMode = options.facingMode || CameraFacingMode.USER;
|
209
|
|
-
|
210
|
|
- if (isNewStyleConstraintsSupported) {
|
211
|
|
- constraints.video.facingMode = facingMode;
|
212
|
|
- }
|
213
|
|
- constraints.video.optional.push({
|
214
|
|
- facingMode
|
215
|
|
- });
|
216
|
|
- }
|
217
|
|
-
|
218
|
|
- if (options.minFps || options.maxFps || options.fps) {
|
219
|
|
- // for some cameras it might be necessary to request 30fps
|
220
|
|
- // so they choose 30fps mjpg over 10fps yuy2
|
221
|
|
- if (options.minFps || options.fps) {
|
222
|
|
- // Fall back to options.fps for backwards compatibility
|
223
|
|
- options.minFps = options.minFps || options.fps;
|
224
|
|
- constraints.video.mandatory.minFrameRate = options.minFps;
|
225
|
|
- }
|
226
|
|
- if (options.maxFps) {
|
227
|
|
- constraints.video.mandatory.maxFrameRate = options.maxFps;
|
228
|
|
- }
|
229
|
|
- }
|
230
|
|
-
|
231
|
|
- setResolutionConstraints(
|
232
|
|
- constraints, isNewStyleConstraintsSupported, options.resolution);
|
233
|
|
- }
|
234
|
|
- if (um.indexOf('audio') >= 0) {
|
235
|
|
- if (browser.isReactNative()) {
|
236
|
|
- // The react-native-webrtc project that we're currently using
|
237
|
|
- // expects the audio constraint to be a boolean.
|
238
|
|
- constraints.audio = true;
|
239
|
|
- } else if (browser.isFirefox()) {
|
240
|
|
- if (options.micDeviceId) {
|
241
|
|
- constraints.audio = {
|
242
|
|
- mandatory: {},
|
243
|
|
- deviceId: options.micDeviceId, // new style
|
244
|
|
- optional: [ {
|
245
|
|
- sourceId: options.micDeviceId // old style
|
246
|
|
- } ] };
|
247
|
|
- } else {
|
248
|
|
- constraints.audio = true;
|
249
|
|
- }
|
250
|
|
- } else {
|
251
|
|
- // same behaviour as true
|
252
|
|
- constraints.audio = { mandatory: {},
|
253
|
|
- optional: [] };
|
254
|
|
- if (options.micDeviceId) {
|
255
|
|
- if (isNewStyleConstraintsSupported) {
|
256
|
|
- // New style of setting device id.
|
257
|
|
- constraints.audio.deviceId = options.micDeviceId;
|
258
|
|
- }
|
259
|
|
-
|
260
|
|
- // Old style.
|
261
|
|
- constraints.audio.optional.push({
|
262
|
|
- sourceId: options.micDeviceId
|
263
|
|
- });
|
264
|
|
- }
|
265
|
|
-
|
266
|
|
- // if it is good enough for hangouts...
|
267
|
|
- constraints.audio.optional.push(
|
268
|
|
- { echoCancellation: !disableAEC && !disableAP },
|
269
|
|
- { googEchoCancellation: !disableAEC && !disableAP },
|
270
|
|
- { googAutoGainControl: !disableAGC && !disableAP },
|
271
|
|
- { googNoiseSuppression: !disableNS && !disableAP },
|
272
|
|
- { googHighpassFilter: !disableHPF && !disableAP },
|
273
|
|
- { googNoiseSuppression2: !disableNS && !disableAP },
|
274
|
|
- { googEchoCancellation2: !disableAEC && !disableAP },
|
275
|
|
- { googAutoGainControl2: !disableAGC && !disableAP }
|
276
|
|
- );
|
277
|
|
- }
|
278
|
|
- }
|
279
|
|
- if (um.indexOf('screen') >= 0) {
|
280
|
|
- if (browser.isChrome()) {
|
281
|
|
- constraints.video = {
|
282
|
|
- mandatory: getSSConstraints({
|
283
|
|
- ...options,
|
284
|
|
- source: 'screen'
|
285
|
|
- }),
|
286
|
|
- optional: []
|
287
|
|
- };
|
288
|
|
-
|
289
|
|
- } else if (browser.isFirefox()) {
|
290
|
|
- constraints.video = {
|
291
|
|
- mozMediaSource: 'window',
|
292
|
|
- mediaSource: 'window',
|
293
|
|
- frameRate: options.frameRate || {
|
294
|
|
- min: SS_DEFAULT_FRAME_RATE,
|
295
|
|
- max: SS_DEFAULT_FRAME_RATE
|
296
|
|
- }
|
297
|
|
- };
|
298
|
|
-
|
299
|
|
- } else {
|
300
|
|
- const errmsg
|
301
|
|
- = '\'screen\' WebRTC media source is supported only in Chrome'
|
302
|
|
- + ' and Firefox';
|
303
|
|
-
|
304
|
|
- GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
|
305
|
|
- logger.error(errmsg);
|
306
|
|
- }
|
307
|
|
- }
|
308
|
|
- if (um.indexOf('desktop') >= 0) {
|
309
|
|
- constraints.video = {
|
310
|
|
- mandatory: getSSConstraints({
|
311
|
|
- ...options,
|
312
|
|
- source: 'desktop'
|
313
|
|
- }),
|
314
|
|
- optional: []
|
315
|
|
- };
|
316
|
|
-
|
317
|
|
- // Audio screen sharing for electron only works for screen type devices.
|
318
|
|
- // i.e. when the user shares the whole desktop.
|
319
|
|
- if (browser.isElectron() && options.screenShareAudio
|
320
|
|
- && (options.desktopStream.indexOf('screen') >= 0)) {
|
321
|
|
-
|
322
|
|
- // Provide constraints as described by the electron desktop capturer
|
323
|
|
- // documentation here:
|
324
|
|
- // https://www.electronjs.org/docs/api/desktop-capturer
|
325
|
|
- // Note. The documentation specifies that chromeMediaSourceId should not be present
|
326
|
|
- // which, in the case a users has multiple monitors, leads to them being shared all
|
327
|
|
- // at once. However we tested with chromeMediaSourceId present and it seems to be
|
328
|
|
- // working properly and also takes care of the previously mentioned issue.
|
329
|
|
- constraints.audio = { mandatory: {
|
330
|
|
- chromeMediaSource: constraints.video.mandatory.chromeMediaSource
|
331
|
|
- } };
|
332
|
|
- }
|
333
|
|
- }
|
334
|
|
-
|
335
|
|
- if (options.bandwidth) {
|
336
|
|
- if (!constraints.video) {
|
337
|
|
- // same behaviour as true
|
338
|
|
- constraints.video = { mandatory: {},
|
339
|
|
- optional: [] };
|
340
|
|
- }
|
341
|
|
- constraints.video.optional.push({ bandwidth: options.bandwidth });
|
342
|
|
- }
|
343
|
|
-
|
344
|
|
- // we turn audio for both audio and video tracks, the fake audio & video
|
345
|
|
- // seems to work only when enabled in one getUserMedia call, we cannot get
|
346
|
|
- // fake audio separate by fake video this later can be a problem with some
|
347
|
|
- // of the tests
|
348
|
|
- if (browser.isFirefox() && options.firefox_fake_device) {
|
349
|
|
- // seems to be fixed now, removing this experimental fix, as having
|
350
|
|
- // multiple audio tracks brake the tests
|
351
|
|
- // constraints.audio = true;
|
352
|
|
- constraints.fake = true;
|
353
|
|
- }
|
354
|
|
-
|
355
|
|
- return constraints;
|
356
|
|
-}
|
357
|
|
-
|
358
|
96
|
/**
|
359
|
97
|
* Creates a constraints object to be passed into a call to getUserMedia.
|
360
|
98
|
*
|
|
@@ -377,12 +115,22 @@ function getConstraints(um, options = {}) {
|
377
|
115
|
* @private
|
378
|
116
|
* @returns {Object}
|
379
|
117
|
*/
|
380
|
|
-function newGetConstraints(um = [], options = {}) {
|
|
118
|
+function getConstraints(um = [], options = {}) {
|
381
|
119
|
// Create a deep copy of the constraints to avoid any modification of
|
382
|
120
|
// the passed in constraints object.
|
383
|
121
|
const constraints = clonedeep(options.constraints || DEFAULT_CONSTRAINTS);
|
384
|
122
|
|
385
|
123
|
if (um.indexOf('video') >= 0) {
|
|
124
|
+ // The "resolution" option is a shortcut and takes precendence.
|
|
125
|
+ if (Resolutions[options.resolution]) {
|
|
126
|
+ const r = Resolutions[options.resolution];
|
|
127
|
+
|
|
128
|
+ constraints.video = {
|
|
129
|
+ height: { ideal: r.height },
|
|
130
|
+ width: { ideal: r.width }
|
|
131
|
+ };
|
|
132
|
+ }
|
|
133
|
+
|
386
|
134
|
if (!constraints.video) {
|
387
|
135
|
constraints.video = {};
|
388
|
136
|
}
|
|
@@ -393,12 +141,12 @@ function newGetConstraints(um = [], options = {}) {
|
393
|
141
|
// TODO: remove this hack when the bug fix is available on Mojave, Sierra and High Sierra.
|
394
|
142
|
if (browser.isWebKitBased()) {
|
395
|
143
|
if (constraints.video.height && constraints.video.height.ideal) {
|
396
|
|
- constraints.video.height = { ideal: clonedeep(constraints.video.height.ideal) };
|
|
144
|
+ constraints.video.height = { ideal: constraints.video.height.ideal };
|
397
|
145
|
} else {
|
398
|
146
|
logger.warn('Ideal camera height missing, camera may not start properly');
|
399
|
147
|
}
|
400
|
148
|
if (constraints.video.width && constraints.video.width.ideal) {
|
401
|
|
- constraints.video.width = { ideal: clonedeep(constraints.video.width.ideal) };
|
|
149
|
+ constraints.video.width = { ideal: constraints.video.width.ideal };
|
402
|
150
|
} else {
|
403
|
151
|
logger.warn('Ideal camera width missing, camera may not start properly');
|
404
|
152
|
}
|
|
@@ -433,60 +181,6 @@ function newGetConstraints(um = [], options = {}) {
|
433
|
181
|
constraints.audio = false;
|
434
|
182
|
}
|
435
|
183
|
|
436
|
|
- if (um.indexOf('desktop') >= 0) {
|
437
|
|
- if (!constraints.video || typeof constraints.video === 'boolean') {
|
438
|
|
- constraints.video = {};
|
439
|
|
- }
|
440
|
|
-
|
441
|
|
- constraints.video = {
|
442
|
|
- mandatory: getSSConstraints({
|
443
|
|
- ...options,
|
444
|
|
- source: 'desktop'
|
445
|
|
- })
|
446
|
|
- };
|
447
|
|
- }
|
448
|
|
-
|
449
|
|
- return constraints;
|
450
|
|
-}
|
451
|
|
-
|
452
|
|
-/**
|
453
|
|
- * Generates GUM constraints for screen sharing.
|
454
|
|
- *
|
455
|
|
- * @param {Object} options - The options passed to
|
456
|
|
- * <tt>obtainAudioAndVideoPermissions</tt>.
|
457
|
|
- * @returns {Object} - GUM constraints.
|
458
|
|
- *
|
459
|
|
- * TODO: Currently only the new GUM flow and Chrome is using the method. We
|
460
|
|
- * should make it work for all use cases.
|
461
|
|
- */
|
462
|
|
-function getSSConstraints(options = {}) {
|
463
|
|
- const {
|
464
|
|
- desktopStream,
|
465
|
|
- frameRate = {
|
466
|
|
- min: SS_DEFAULT_FRAME_RATE,
|
467
|
|
- max: SS_DEFAULT_FRAME_RATE
|
468
|
|
- }
|
469
|
|
- } = options;
|
470
|
|
- const { max, min } = frameRate;
|
471
|
|
-
|
472
|
|
- const constraints = {
|
473
|
|
- chromeMediaSource: options.source,
|
474
|
|
- maxWidth: window.screen.width,
|
475
|
|
- maxHeight: window.screen.height
|
476
|
|
- };
|
477
|
|
-
|
478
|
|
- if (typeof min === 'number') {
|
479
|
|
- constraints.minFrameRate = min;
|
480
|
|
- }
|
481
|
|
-
|
482
|
|
- if (typeof max === 'number') {
|
483
|
|
- constraints.maxFrameRate = max;
|
484
|
|
- }
|
485
|
|
-
|
486
|
|
- if (typeof desktopStream !== 'undefined') {
|
487
|
|
- constraints.chromeMediaSourceId = desktopStream;
|
488
|
|
- }
|
489
|
|
-
|
490
|
184
|
return constraints;
|
491
|
185
|
}
|
492
|
186
|
|
|
@@ -615,92 +309,6 @@ function onMediaDevicesListChanged(devicesReceived) {
|
615
|
309
|
eventEmitter.emit(RTCEvents.DEVICE_LIST_CHANGED, availableDevices);
|
616
|
310
|
}
|
617
|
311
|
|
618
|
|
-/**
|
619
|
|
- * Handles the newly created Media Streams.
|
620
|
|
- * @param streams the new Media Streams
|
621
|
|
- * @param resolution the resolution of the video streams
|
622
|
|
- * @returns {*[]} object that describes the new streams
|
623
|
|
- */
|
624
|
|
-function handleLocalStream(streams, resolution) {
|
625
|
|
- let audioStream, desktopStream, videoStream;
|
626
|
|
- const res = [];
|
627
|
|
-
|
628
|
|
- // XXX The function obtainAudioAndVideoPermissions has examined the type of
|
629
|
|
- // the browser, its capabilities, etc. and has taken the decision whether to
|
630
|
|
- // invoke getUserMedia per device (e.g. Firefox) or once for both audio and
|
631
|
|
- // video (e.g. Chrome). In order to not duplicate the logic here, examine
|
632
|
|
- // the specified streams and figure out what we've received based on
|
633
|
|
- // obtainAudioAndVideoPermissions' decision.
|
634
|
|
- if (streams) {
|
635
|
|
- // As mentioned above, certian types of browser (e.g. Chrome) support
|
636
|
|
- // (with a result which meets our requirements expressed bellow) calling
|
637
|
|
- // getUserMedia once for both audio and video.
|
638
|
|
- const audioVideo = streams.audioVideo;
|
639
|
|
-
|
640
|
|
- if (audioVideo) {
|
641
|
|
- const audioTracks = audioVideo.getAudioTracks();
|
642
|
|
-
|
643
|
|
- if (audioTracks.length) {
|
644
|
|
- audioStream = new MediaStream();
|
645
|
|
- for (let i = 0; i < audioTracks.length; i++) {
|
646
|
|
- audioStream.addTrack(audioTracks[i]);
|
647
|
|
- }
|
648
|
|
- }
|
649
|
|
-
|
650
|
|
- const videoTracks = audioVideo.getVideoTracks();
|
651
|
|
-
|
652
|
|
- if (videoTracks.length) {
|
653
|
|
- videoStream = new MediaStream();
|
654
|
|
- for (let j = 0; j < videoTracks.length; j++) {
|
655
|
|
- videoStream.addTrack(videoTracks[j]);
|
656
|
|
- }
|
657
|
|
- }
|
658
|
|
-
|
659
|
|
- audioVideo.release && audioVideo.release(false);
|
660
|
|
- } else {
|
661
|
|
- // On other types of browser (e.g. Firefox) we choose (namely,
|
662
|
|
- // obtainAudioAndVideoPermissions) to call getUserMedia per device
|
663
|
|
- // (type).
|
664
|
|
- audioStream = streams.audio;
|
665
|
|
- videoStream = streams.video;
|
666
|
|
- }
|
667
|
|
-
|
668
|
|
- desktopStream = streams.desktop;
|
669
|
|
- }
|
670
|
|
-
|
671
|
|
- if (desktopStream) {
|
672
|
|
- const { stream, sourceId, sourceType } = desktopStream;
|
673
|
|
-
|
674
|
|
- res.push({
|
675
|
|
- stream,
|
676
|
|
- sourceId,
|
677
|
|
- sourceType,
|
678
|
|
- track: stream.getVideoTracks()[0],
|
679
|
|
- mediaType: MediaType.VIDEO,
|
680
|
|
- videoType: VideoType.DESKTOP
|
681
|
|
- });
|
682
|
|
- }
|
683
|
|
- if (audioStream) {
|
684
|
|
- res.push({
|
685
|
|
- stream: audioStream,
|
686
|
|
- track: audioStream.getAudioTracks()[0],
|
687
|
|
- mediaType: MediaType.AUDIO,
|
688
|
|
- videoType: null
|
689
|
|
- });
|
690
|
|
- }
|
691
|
|
- if (videoStream) {
|
692
|
|
- res.push({
|
693
|
|
- stream: videoStream,
|
694
|
|
- track: videoStream.getVideoTracks()[0],
|
695
|
|
- mediaType: MediaType.VIDEO,
|
696
|
|
- videoType: VideoType.CAMERA,
|
697
|
|
- resolution
|
698
|
|
- });
|
699
|
|
- }
|
700
|
|
-
|
701
|
|
- return res;
|
702
|
|
-}
|
703
|
|
-
|
704
|
312
|
/**
|
705
|
313
|
*
|
706
|
314
|
*/
|
|
@@ -738,10 +346,6 @@ class RTCUtils extends Listenable {
|
738
|
346
|
disableAGC = options.disableAGC;
|
739
|
347
|
logger.info(`Disable AGC: ${disableAGC}`);
|
740
|
348
|
}
|
741
|
|
- if (typeof options.disableHPF === 'boolean') {
|
742
|
|
- disableHPF = options.disableHPF;
|
743
|
|
- logger.info(`Disable HPF: ${disableHPF}`);
|
744
|
|
- }
|
745
|
349
|
if (typeof options.audioQuality?.stereo === 'boolean') {
|
746
|
350
|
stereo = options.audioQuality.stereo;
|
747
|
351
|
logger.info(`Stereo: ${stereo}`);
|
|
@@ -750,19 +354,7 @@ class RTCUtils extends Listenable {
|
750
|
354
|
window.clearInterval(availableDevicesPollTimer);
|
751
|
355
|
availableDevicesPollTimer = undefined;
|
752
|
356
|
|
753
|
|
- if (browser.usesNewGumFlow()) {
|
754
|
|
- this.RTCPeerConnectionType = RTCPeerConnection;
|
755
|
|
-
|
756
|
|
- this.attachMediaStream
|
757
|
|
- = wrapAttachMediaStream((element, stream) => {
|
758
|
|
- if (element) {
|
759
|
|
- element.srcObject = stream;
|
760
|
|
- }
|
761
|
|
- });
|
762
|
|
-
|
763
|
|
- this.getStreamID = ({ id }) => id;
|
764
|
|
- this.getTrackID = ({ id }) => id;
|
765
|
|
- } else if (browser.isReactNative()) {
|
|
357
|
+ if (browser.isReactNative()) {
|
766
|
358
|
this.RTCPeerConnectionType = RTCPeerConnection;
|
767
|
359
|
|
768
|
360
|
this.attachMediaStream = undefined; // Unused on React Native.
|
|
@@ -779,10 +371,17 @@ class RTCUtils extends Listenable {
|
779
|
371
|
};
|
780
|
372
|
this.getTrackID = ({ id }) => id;
|
781
|
373
|
} else {
|
782
|
|
- const message = 'Endpoint does not appear to be WebRTC-capable';
|
|
374
|
+ this.RTCPeerConnectionType = RTCPeerConnection;
|
783
|
375
|
|
784
|
|
- logger.error(message);
|
785
|
|
- throw new Error(message);
|
|
376
|
+ this.attachMediaStream
|
|
377
|
+ = wrapAttachMediaStream((element, stream) => {
|
|
378
|
+ if (element) {
|
|
379
|
+ element.srcObject = stream;
|
|
380
|
+ }
|
|
381
|
+ });
|
|
382
|
+
|
|
383
|
+ this.getStreamID = ({ id }) => id;
|
|
384
|
+ this.getTrackID = ({ id }) => id;
|
786
|
385
|
}
|
787
|
386
|
|
788
|
387
|
this.pcConstraints = browser.isChromiumBased() || browser.isReactNative()
|
|
@@ -792,9 +391,7 @@ class RTCUtils extends Listenable {
|
792
|
391
|
] }
|
793
|
392
|
: {};
|
794
|
393
|
|
795
|
|
- screenObtainer.init(
|
796
|
|
- options,
|
797
|
|
- this.getUserMediaWithConstraints.bind(this));
|
|
394
|
+ screenObtainer.init(options);
|
798
|
395
|
|
799
|
396
|
if (this.isDeviceListAvailable()) {
|
800
|
397
|
this.enumerateDevices(ds => {
|
|
@@ -839,38 +436,6 @@ class RTCUtils extends Listenable {
|
839
|
436
|
});
|
840
|
437
|
}
|
841
|
438
|
|
842
|
|
- /* eslint-disable max-params */
|
843
|
|
-
|
844
|
|
- /**
|
845
|
|
- * @param {string[]} um required user media types
|
846
|
|
- * @param {Object} [options] optional parameters
|
847
|
|
- * @param {string} options.resolution
|
848
|
|
- * @param {number} options.bandwidth
|
849
|
|
- * @param {number} options.fps
|
850
|
|
- * @param {string} options.desktopStream
|
851
|
|
- * @param {string} options.cameraDeviceId
|
852
|
|
- * @param {string} options.micDeviceId
|
853
|
|
- * @param {Object} options.frameRate - used only for dekstop sharing.
|
854
|
|
- * @param {Object} options.frameRate.min - Minimum fps
|
855
|
|
- * @param {Object} options.frameRate.max - Maximum fps
|
856
|
|
- * @param {bool} options.screenShareAudio - Used by electron clients to
|
857
|
|
- * enable system audio screen sharing.
|
858
|
|
- * @param {number} options.timeout - The timeout in ms for GUM.
|
859
|
|
- * @returns {Promise} Returns a media stream on success or a JitsiTrackError
|
860
|
|
- * on failure.
|
861
|
|
- **/
|
862
|
|
- getUserMediaWithConstraints(um, options = {}) {
|
863
|
|
- const {
|
864
|
|
- timeout,
|
865
|
|
- ...otherOptions
|
866
|
|
- } = options;
|
867
|
|
- const constraints = getConstraints(um, otherOptions);
|
868
|
|
-
|
869
|
|
- logger.info('Get media constraints', JSON.stringify(constraints));
|
870
|
|
-
|
871
|
|
- return this._getUserMedia(um, constraints, timeout);
|
872
|
|
- }
|
873
|
|
-
|
874
|
439
|
/**
|
875
|
440
|
* Acquires a media stream via getUserMedia that
|
876
|
441
|
* matches the given constraints
|
|
@@ -930,23 +495,17 @@ class RTCUtils extends Listenable {
|
930
|
495
|
* logic compared to use screenObtainer versus normal device capture logic
|
931
|
496
|
* in RTCUtils#_getUserMedia.
|
932
|
497
|
*
|
933
|
|
- * @param {Object} options
|
934
|
|
- * @param {string[]} options.desktopSharingSources
|
935
|
|
- * @param {Object} options.desktopSharingFrameRate
|
936
|
|
- * @param {Object} options.desktopSharingFrameRate.min - Minimum fps
|
937
|
|
- * @param {Object} options.desktopSharingFrameRate.max - Maximum fps
|
938
|
498
|
* @returns {Promise} A promise which will be resolved with an object which
|
939
|
499
|
* contains the acquired display stream. If desktop sharing is not supported
|
940
|
500
|
* then a rejected promise will be returned.
|
941
|
501
|
*/
|
942
|
|
- _newGetDesktopMedia(options) {
|
|
502
|
+ _getDesktopMedia() {
|
943
|
503
|
if (!screenObtainer.isSupported()) {
|
944
|
504
|
return Promise.reject(new Error('Desktop sharing is not supported!'));
|
945
|
505
|
}
|
946
|
506
|
|
947
|
507
|
return new Promise((resolve, reject) => {
|
948
|
508
|
screenObtainer.obtainStream(
|
949
|
|
- this._parseDesktopSharingOptions(options),
|
950
|
509
|
stream => {
|
951
|
510
|
resolve(stream);
|
952
|
511
|
},
|
|
@@ -956,108 +515,6 @@ class RTCUtils extends Listenable {
|
956
|
515
|
});
|
957
|
516
|
}
|
958
|
517
|
|
959
|
|
- /* eslint-enable max-params */
|
960
|
|
-
|
961
|
|
- /**
|
962
|
|
- * Creates the local MediaStreams.
|
963
|
|
- * @param {Object} [options] optional parameters
|
964
|
|
- * @param {Array} options.devices the devices that will be requested
|
965
|
|
- * @param {string} options.resolution resolution constraints
|
966
|
|
- * @param {string} options.cameraDeviceId
|
967
|
|
- * @param {string} options.micDeviceId
|
968
|
|
- * @param {Object} options.desktopSharingFrameRate
|
969
|
|
- * @param {Object} options.desktopSharingFrameRate.min - Minimum fps
|
970
|
|
- * @param {Object} options.desktopSharingFrameRate.max - Maximum fps
|
971
|
|
- * @returns {*} Promise object that will receive the new JitsiTracks
|
972
|
|
- */
|
973
|
|
- obtainAudioAndVideoPermissions(options = {}) {
|
974
|
|
- options.devices = options.devices || [ ...OLD_GUM_DEFAULT_DEVICES ];
|
975
|
|
- options.resolution = options.resolution || OLD_GUM_DEFAULT_RESOLUTION;
|
976
|
|
-
|
977
|
|
- const requestingDesktop = options.devices.includes('desktop');
|
978
|
|
-
|
979
|
|
- if (requestingDesktop && !screenObtainer.isSupported()) {
|
980
|
|
- return Promise.reject(
|
981
|
|
- new Error('Desktop sharing is not supported!'));
|
982
|
|
- }
|
983
|
|
-
|
984
|
|
- return this._getAudioAndVideoStreams(options).then(streams =>
|
985
|
|
- handleLocalStream(streams, options.resolution));
|
986
|
|
- }
|
987
|
|
-
|
988
|
|
- /**
|
989
|
|
- * Performs one call to getUserMedia for audio and/or video and another call
|
990
|
|
- * for desktop.
|
991
|
|
- *
|
992
|
|
- * @param {Object} options - An object describing how the gUM request should
|
993
|
|
- * be executed. See {@link obtainAudioAndVideoPermissions} for full options.
|
994
|
|
- * @returns {*} Promise object that will receive the new JitsiTracks on
|
995
|
|
- * success or a JitsiTrackError on failure.
|
996
|
|
- */
|
997
|
|
- _getAudioAndVideoStreams(options) {
|
998
|
|
- const requestingDesktop = options.devices.includes('desktop');
|
999
|
|
-
|
1000
|
|
- options.devices = options.devices.filter(device =>
|
1001
|
|
- device !== 'desktop');
|
1002
|
|
-
|
1003
|
|
- const gumPromise = options.devices.length
|
1004
|
|
- ? this.getUserMediaWithConstraints(options.devices, options)
|
1005
|
|
- : Promise.resolve(null);
|
1006
|
|
-
|
1007
|
|
- return gumPromise
|
1008
|
|
- .then(avStream => {
|
1009
|
|
- // If any requested devices are missing, call gum again in
|
1010
|
|
- // an attempt to obtain the actual error. For example, the
|
1011
|
|
- // requested video device is missing or permission was
|
1012
|
|
- // denied.
|
1013
|
|
- const missingTracks
|
1014
|
|
- = this._getMissingTracks(options.devices, avStream);
|
1015
|
|
-
|
1016
|
|
- if (missingTracks.length) {
|
1017
|
|
- this.stopMediaStream(avStream);
|
1018
|
|
-
|
1019
|
|
- return this.getUserMediaWithConstraints(
|
1020
|
|
- missingTracks, options)
|
1021
|
|
-
|
1022
|
|
- // GUM has already failed earlier and this success
|
1023
|
|
- // handling should not be reached.
|
1024
|
|
- .then(() => Promise.reject(new JitsiTrackError(
|
1025
|
|
- { name: 'UnknownError' },
|
1026
|
|
- getConstraints(options.devices, options),
|
1027
|
|
- missingTracks)));
|
1028
|
|
- }
|
1029
|
|
-
|
1030
|
|
- return avStream;
|
1031
|
|
- })
|
1032
|
|
- .then(audioVideo => {
|
1033
|
|
- if (!requestingDesktop) {
|
1034
|
|
- return { audioVideo };
|
1035
|
|
- }
|
1036
|
|
-
|
1037
|
|
- if (options.desktopSharingSourceDevice) {
|
1038
|
|
- this.stopMediaStream(audioVideo);
|
1039
|
|
-
|
1040
|
|
- throw new Error('Using a camera as screenshare source is'
|
1041
|
|
- + 'not supported on this browser.');
|
1042
|
|
- }
|
1043
|
|
-
|
1044
|
|
- return new Promise((resolve, reject) => {
|
1045
|
|
- screenObtainer.obtainStream(
|
1046
|
|
- this._parseDesktopSharingOptions(options),
|
1047
|
|
- desktop => resolve({
|
1048
|
|
- audioVideo,
|
1049
|
|
- desktop
|
1050
|
|
- }),
|
1051
|
|
- error => {
|
1052
|
|
- if (audioVideo) {
|
1053
|
|
- this.stopMediaStream(audioVideo);
|
1054
|
|
- }
|
1055
|
|
- reject(error);
|
1056
|
|
- });
|
1057
|
|
- });
|
1058
|
|
- });
|
1059
|
|
- }
|
1060
|
|
-
|
1061
|
518
|
/**
|
1062
|
519
|
* Private utility for determining if the passed in MediaStream contains
|
1063
|
520
|
* tracks of the type(s) specified in the requested devices.
|
|
@@ -1091,22 +548,6 @@ class RTCUtils extends Listenable {
|
1091
|
548
|
return missingDevices;
|
1092
|
549
|
}
|
1093
|
550
|
|
1094
|
|
- /**
|
1095
|
|
- * Returns an object formatted for specifying desktop sharing parameters.
|
1096
|
|
- *
|
1097
|
|
- * @param {Object} options - Takes in the same options object as
|
1098
|
|
- * {@link obtainAudioAndVideoPermissions}.
|
1099
|
|
- * @returns {Object}
|
1100
|
|
- */
|
1101
|
|
- _parseDesktopSharingOptions(options) {
|
1102
|
|
- return {
|
1103
|
|
- desktopSharingSources: options.desktopSharingSources,
|
1104
|
|
- gumOptions: {
|
1105
|
|
- frameRate: options.desktopSharingFrameRate
|
1106
|
|
- }
|
1107
|
|
- };
|
1108
|
|
- }
|
1109
|
|
-
|
1110
|
551
|
/**
|
1111
|
552
|
* Gets streams from specified device types. This function intentionally
|
1112
|
553
|
* ignores errors for upstream to catch and handle instead.
|
|
@@ -1125,9 +566,7 @@ class RTCUtils extends Listenable {
|
1125
|
566
|
* track. If an error occurs, it will be deferred to the caller for
|
1126
|
567
|
* handling.
|
1127
|
568
|
*/
|
1128
|
|
- newObtainAudioAndVideoPermissions(options) {
|
1129
|
|
- logger.info('Using the new gUM flow');
|
1130
|
|
-
|
|
569
|
+ obtainAudioAndVideoPermissions(options) {
|
1131
|
570
|
const {
|
1132
|
571
|
timeout,
|
1133
|
572
|
...otherOptions
|
|
@@ -1155,9 +594,7 @@ class RTCUtils extends Listenable {
|
1155
|
594
|
}
|
1156
|
595
|
|
1157
|
596
|
const {
|
1158
|
|
- desktopSharingSourceDevice,
|
1159
|
|
- desktopSharingSources,
|
1160
|
|
- desktopSharingFrameRate
|
|
597
|
+ desktopSharingSourceDevice
|
1161
|
598
|
} = otherOptions;
|
1162
|
599
|
|
1163
|
600
|
// Attempt to use a video input device as a screenshare source if
|
|
@@ -1178,15 +615,11 @@ class RTCUtils extends Listenable {
|
1178
|
615
|
}
|
1179
|
616
|
|
1180
|
617
|
const requestedDevices = [ 'video' ];
|
1181
|
|
-
|
1182
|
|
- // Leverage the helper used by {@link _newGetDesktopMedia} to
|
1183
|
|
- // get constraints for the desktop stream.
|
1184
|
|
- const { gumOptions } = this._parseDesktopSharingOptions(otherOptions);
|
1185
|
|
-
|
1186
|
618
|
const constraints = {
|
1187
|
619
|
video: {
|
1188
|
|
- ...gumOptions,
|
1189
|
620
|
deviceId: matchingDevice.deviceId
|
|
621
|
+
|
|
622
|
+ // frameRate is omited here on purpose since this is a device that we'll pretend is a screen.
|
1190
|
623
|
}
|
1191
|
624
|
};
|
1192
|
625
|
|
|
@@ -1199,10 +632,7 @@ class RTCUtils extends Listenable {
|
1199
|
632
|
});
|
1200
|
633
|
}
|
1201
|
634
|
|
1202
|
|
- return this._newGetDesktopMedia({
|
1203
|
|
- desktopSharingSources,
|
1204
|
|
- desktopSharingFrameRate
|
1205
|
|
- });
|
|
635
|
+ return this._getDesktopMedia();
|
1206
|
636
|
}.bind(this);
|
1207
|
637
|
|
1208
|
638
|
/**
|
|
@@ -1264,8 +694,7 @@ class RTCUtils extends Listenable {
|
1264
|
694
|
return Promise.resolve();
|
1265
|
695
|
}
|
1266
|
696
|
|
1267
|
|
- const constraints = newGetConstraints(
|
1268
|
|
- requestedCaptureDevices, otherOptions);
|
|
697
|
+ const constraints = getConstraints(requestedCaptureDevices, otherOptions);
|
1269
|
698
|
|
1270
|
699
|
logger.info('Got media constraints: ', JSON.stringify(constraints));
|
1271
|
700
|
|