|
@@ -10,7 +10,9 @@ import { getLocalParticipant } from '../participants';
|
10
|
10
|
|
11
|
11
|
import {
|
12
|
12
|
TRACK_ADDED,
|
13
|
|
- TRACK_PERMISSION_ERROR,
|
|
13
|
+ TRACK_BEING_CREATED,
|
|
14
|
+ TRACK_CREATE_CANCELED,
|
|
15
|
+ TRACK_CREATE_ERROR,
|
14
|
16
|
TRACK_REMOVED,
|
15
|
17
|
TRACK_UPDATED
|
16
|
18
|
} from './actionTypes';
|
|
@@ -79,7 +81,11 @@ export function createLocalTracksA(options = {}) {
|
79
|
81
|
// to implement them) and the right thing to do is to ask for each
|
80
|
82
|
// device separately.
|
81
|
83
|
for (const device of devices) {
|
82
|
|
- createLocalTracksF(
|
|
84
|
+ if (getState()['features/base/tracks']
|
|
85
|
+ .find(t => t.local && t.mediaType === device)) {
|
|
86
|
+ throw new Error(`Local track for ${device} already exists`);
|
|
87
|
+ }
|
|
88
|
+ const gumProcess = createLocalTracksF(
|
83
|
89
|
{
|
84
|
90
|
cameraDeviceId: options.cameraDeviceId,
|
85
|
91
|
devices: [ device ],
|
|
@@ -89,9 +95,48 @@ export function createLocalTracksA(options = {}) {
|
89
|
95
|
/* firePermissionPromptIsShownEvent */ false,
|
90
|
96
|
store)
|
91
|
97
|
.then(
|
92
|
|
- localTracks => dispatch(_updateLocalTracks(localTracks)),
|
|
98
|
+ localTracks => {
|
|
99
|
+ // Because GUM is called for 1 device (which is actually
|
|
100
|
+ // a media type 'audio','video', 'screen' etc.) we should
|
|
101
|
+ // not get more than one JitsiTrack.
|
|
102
|
+ if (localTracks.length !== 1) {
|
|
103
|
+ throw new Error(
|
|
104
|
+ 'Expected exactly 1 track, but was '
|
|
105
|
+ + `given ${localTracks.length} tracks`
|
|
106
|
+ + `for device: ${device}.`);
|
|
107
|
+ }
|
|
108
|
+
|
|
109
|
+ if (gumProcess.canceled) {
|
|
110
|
+ return _disposeTracks(localTracks)
|
|
111
|
+ .then(
|
|
112
|
+ () =>
|
|
113
|
+ dispatch(
|
|
114
|
+ _trackCreateCanceled(device)));
|
|
115
|
+ }
|
|
116
|
+
|
|
117
|
+ return dispatch(trackAdded(localTracks[0]));
|
|
118
|
+ },
|
|
119
|
+ // eslint-disable-next-line no-confusing-arrow
|
93
|
120
|
reason =>
|
94
|
|
- dispatch(_onCreateLocalTracksRejected(reason, device)));
|
|
121
|
+ dispatch(
|
|
122
|
+ gumProcess.canceled
|
|
123
|
+ ? _trackCreateCanceled(device)
|
|
124
|
+ : _onCreateLocalTracksRejected(reason, device)));
|
|
125
|
+
|
|
126
|
+ gumProcess.cancel = () => {
|
|
127
|
+ gumProcess.canceled = true;
|
|
128
|
+
|
|
129
|
+ return gumProcess;
|
|
130
|
+ };
|
|
131
|
+
|
|
132
|
+ dispatch({
|
|
133
|
+ type: TRACK_BEING_CREATED,
|
|
134
|
+ track: {
|
|
135
|
+ local: true,
|
|
136
|
+ gumProcess,
|
|
137
|
+ mediaType: device
|
|
138
|
+ }
|
|
139
|
+ });
|
95
|
140
|
}
|
96
|
141
|
};
|
97
|
142
|
}
|
|
@@ -103,12 +148,17 @@ export function createLocalTracksA(options = {}) {
|
103
|
148
|
* @returns {Function}
|
104
|
149
|
*/
|
105
|
150
|
export function destroyLocalTracks() {
|
106
|
|
- return (dispatch, getState) =>
|
107
|
|
- dispatch(
|
108
|
|
- _disposeAndRemoveTracks(
|
109
|
|
- getState()['features/base/tracks']
|
110
|
|
- .filter(t => t.local)
|
111
|
|
- .map(t => t.jitsiTrack)));
|
|
151
|
+ return (dispatch, getState) => {
|
|
152
|
+ // First wait until any getUserMedia in progress is settled and then get
|
|
153
|
+ // rid of all local tracks.
|
|
154
|
+ _cancelAllGumInProgress(getState)
|
|
155
|
+ .then(
|
|
156
|
+ () => dispatch(
|
|
157
|
+ _disposeAndRemoveTracks(
|
|
158
|
+ getState()['features/base/tracks']
|
|
159
|
+ .filter(t => t.local)
|
|
160
|
+ .map(t => t.jitsiTrack))));
|
|
161
|
+ };
|
112
|
162
|
}
|
113
|
163
|
|
114
|
164
|
/**
|
|
@@ -316,81 +366,85 @@ function _addTracks(tracks) {
|
316
|
366
|
}
|
317
|
367
|
|
318
|
368
|
/**
|
319
|
|
- * Disposes passed tracks and signals them to be removed.
|
|
369
|
+ * Signals that track create operation for given media track has been canceled.
|
|
370
|
+ * Will clean up local track stub from the Redux state which holds the
|
|
371
|
+ * 'gumProcess' reference.
|
320
|
372
|
*
|
321
|
|
- * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} tracks - List of tracks.
|
322
|
|
- * @protected
|
323
|
|
- * @returns {Function}
|
|
373
|
+ * @param {MEDIA_TYPE} mediaType - The type of the media for which the track was
|
|
374
|
+ * being created.
|
|
375
|
+ * @returns {{
|
|
376
|
+ * type,
|
|
377
|
+ * trackType: MEDIA_TYPE
|
|
378
|
+ * }}
|
|
379
|
+ * @private
|
324
|
380
|
*/
|
325
|
|
-export function _disposeAndRemoveTracks(tracks) {
|
326
|
|
- return dispatch =>
|
327
|
|
- Promise.all(
|
328
|
|
- tracks.map(t =>
|
329
|
|
- t.dispose()
|
330
|
|
- .catch(err => {
|
331
|
|
- // Track might be already disposed so ignore such an
|
332
|
|
- // error. Of course, re-throw any other error(s).
|
333
|
|
- if (err.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
334
|
|
- throw err;
|
335
|
|
- }
|
336
|
|
- })
|
337
|
|
- ))
|
338
|
|
- .then(Promise.all(tracks.map(t => dispatch(trackRemoved(t)))));
|
|
381
|
+function _trackCreateCanceled(mediaType) {
|
|
382
|
+ return {
|
|
383
|
+ type: TRACK_CREATE_CANCELED,
|
|
384
|
+ trackType: mediaType
|
|
385
|
+ };
|
339
|
386
|
}
|
340
|
387
|
|
341
|
388
|
/**
|
342
|
|
- * Finds the first {@code JitsiLocalTrack} in a specific array/list of
|
343
|
|
- * {@code JitsiTrack}s which is of a specific {@code MEDIA_TYPE}.
|
|
389
|
+ * Cancels and waits for any get user media operations currently in progress to
|
|
390
|
+ * complete.
|
344
|
391
|
*
|
345
|
|
- * @param {JitsiTrack[]} tracks - The array/list of {@code JitsiTrack}s to look
|
346
|
|
- * through.
|
347
|
|
- * @param {MEDIA_TYPE} mediaType - The {@code MEDIA_TYPE} of the first
|
348
|
|
- * {@code JitsiLocalTrack} to be returned.
|
|
392
|
+ * @param {Function} getState - The Redux store {@code getState} method used to
|
|
393
|
+ * obtain the state.
|
|
394
|
+ * @returns {Promise} - A Promise resolved once all {@code gumProcess.cancel}
|
|
395
|
+ * Promises are settled. That is when they are either resolved or rejected,
|
|
396
|
+ * because all we care about here is to be sure that get user media callbacks
|
|
397
|
+ * have completed (returned from the native side).
|
349
|
398
|
* @private
|
350
|
|
- * @returns {JitsiLocalTrack} The first {@code JitsiLocalTrack}, if any, in the
|
351
|
|
- * specified {@code tracks} of the specified {@code mediaType}.
|
352
|
399
|
*/
|
353
|
|
-function _getLocalTrack(tracks, mediaType) {
|
354
|
|
- return tracks.find(track =>
|
355
|
|
- track.isLocal()
|
356
|
|
-
|
357
|
|
- // XXX JitsiTrack#getType() returns a MEDIA_TYPE value in the terms
|
358
|
|
- // of lib-jitsi-meet while mediaType is in the terms of jitsi-meet.
|
359
|
|
- && track.getType() === mediaType);
|
|
400
|
+function _cancelAllGumInProgress(getState) {
|
|
401
|
+ // FIXME use logger
|
|
402
|
+ const logError
|
|
403
|
+ = error =>
|
|
404
|
+ console.error('gumProcess.cancel failed', JSON.stringify(error));
|
|
405
|
+
|
|
406
|
+ return Promise.all(
|
|
407
|
+ getState()['features/base/tracks']
|
|
408
|
+ .filter(t => t.local)
|
|
409
|
+ .map(
|
|
410
|
+ t => t.gumProcess
|
|
411
|
+ && t.gumProcess.cancel().catch(logError)));
|
360
|
412
|
}
|
361
|
413
|
|
362
|
414
|
/**
|
363
|
|
- * Determines which local media tracks should be added and which removed.
|
|
415
|
+ * Disposes passed tracks and signals them to be removed.
|
364
|
416
|
*
|
365
|
|
- * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} currentTracks - List of
|
366
|
|
- * current/existing media tracks.
|
367
|
|
- * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} newTracks - List of new media
|
368
|
|
- * tracks.
|
369
|
|
- * @private
|
370
|
|
- * @returns {{
|
371
|
|
- * tracksToAdd: JitsiLocalTrack[],
|
372
|
|
- * tracksToRemove: JitsiLocalTrack[]
|
373
|
|
- * }}
|
|
417
|
+ * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} tracks - List of tracks.
|
|
418
|
+ * @protected
|
|
419
|
+ * @returns {Function}
|
374
|
420
|
*/
|
375
|
|
-function _getLocalTracksToChange(currentTracks, newTracks) {
|
376
|
|
- const tracksToAdd = [];
|
377
|
|
- const tracksToRemove = [];
|
378
|
|
-
|
379
|
|
- for (const mediaType of [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ]) {
|
380
|
|
- const newTrack = _getLocalTrack(newTracks, mediaType);
|
381
|
|
-
|
382
|
|
- if (newTrack) {
|
383
|
|
- const currentTrack = _getLocalTrack(currentTracks, mediaType);
|
384
|
|
-
|
385
|
|
- tracksToAdd.push(newTrack);
|
386
|
|
- currentTrack && tracksToRemove.push(currentTrack);
|
387
|
|
- }
|
388
|
|
- }
|
|
421
|
+export function _disposeAndRemoveTracks(tracks) {
|
|
422
|
+ return dispatch =>
|
|
423
|
+ _disposeTracks(tracks)
|
|
424
|
+ .then(
|
|
425
|
+ () => Promise.all(tracks.map(t => dispatch(trackRemoved(t)))));
|
|
426
|
+}
|
389
|
427
|
|
390
|
|
- return {
|
391
|
|
- tracksToAdd,
|
392
|
|
- tracksToRemove
|
393
|
|
- };
|
|
428
|
+/**
|
|
429
|
+ * Disposes passed tracks.
|
|
430
|
+ *
|
|
431
|
+ * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} tracks - List of tracks.
|
|
432
|
+ * @protected
|
|
433
|
+ * @returns {Promise} - A Promise resolved once {@link JitsiTrack.dispose()} is
|
|
434
|
+ * done for every track from the list.
|
|
435
|
+ */
|
|
436
|
+function _disposeTracks(tracks) {
|
|
437
|
+ return Promise.all(
|
|
438
|
+ tracks.map(t =>
|
|
439
|
+ t.dispose()
|
|
440
|
+ .catch(err => {
|
|
441
|
+ // Track might be already disposed so ignore such an
|
|
442
|
+ // error. Of course, re-throw any other error(s).
|
|
443
|
+ if (err.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
|
|
444
|
+ throw err;
|
|
445
|
+ }
|
|
446
|
+ })
|
|
447
|
+ ));
|
394
|
448
|
}
|
395
|
449
|
|
396
|
450
|
/**
|
|
@@ -430,8 +484,10 @@ function _onCreateLocalTracksRejected({ gum }, device) {
|
430
|
484
|
trackPermissionError = error instanceof DOMException;
|
431
|
485
|
break;
|
432
|
486
|
}
|
433
|
|
- trackPermissionError && dispatch({
|
434
|
|
- type: TRACK_PERMISSION_ERROR,
|
|
487
|
+
|
|
488
|
+ dispatch({
|
|
489
|
+ type: TRACK_CREATE_ERROR,
|
|
490
|
+ permissionDenied: trackPermissionError,
|
435
|
491
|
trackType: device
|
436
|
492
|
});
|
437
|
493
|
}
|
|
@@ -468,24 +524,3 @@ function _shouldMirror(track) {
|
468
|
524
|
// but that may not be the case tomorrow.
|
469
|
525
|
&& track.getCameraFacingMode() === CAMERA_FACING_MODE.USER);
|
470
|
526
|
}
|
471
|
|
-
|
472
|
|
-/**
|
473
|
|
- * Set new local tracks replacing any existing tracks that were previously
|
474
|
|
- * available. Currently only one audio and one video local tracks are allowed.
|
475
|
|
- *
|
476
|
|
- * @param {(JitsiLocalTrack|JitsiRemoteTrack)[]} [newTracks=[]] - List of new
|
477
|
|
- * media tracks.
|
478
|
|
- * @private
|
479
|
|
- * @returns {Function}
|
480
|
|
- */
|
481
|
|
-function _updateLocalTracks(newTracks = []) {
|
482
|
|
- return (dispatch, getState) => {
|
483
|
|
- const tracks
|
484
|
|
- = getState()['features/base/tracks'].map(t => t.jitsiTrack);
|
485
|
|
- const { tracksToAdd, tracksToRemove }
|
486
|
|
- = _getLocalTracksToChange(tracks, newTracks);
|
487
|
|
-
|
488
|
|
- return dispatch(_disposeAndRemoveTracks(tracksToRemove))
|
489
|
|
- .then(() => dispatch(_addTracks(tracksToAdd)));
|
490
|
|
- };
|
491
|
|
-}
|