Bläddra i källkod

Merge pull request #3419 from virtuacoplenny/lenny/queue-replace-track

Queue replaceLocalTrack
master
virtuacoplenny 7 år sedan
förälder
incheckning
2043845d52
Inget konto är kopplat till bidragsgivarens mejladress
4 ändrade filer med 144 tillägg och 40 borttagningar
  1. 70
    26
      conference.js
  2. 0
    14
      modules/UI/videolayout/VideoContainer.js
  3. 63
    0
      modules/util/TaskQueue.js
  4. 11
    0
      modules/util/helpers.js

+ 70
- 26
conference.js Visa fil

@@ -11,6 +11,7 @@ import * as RemoteControlEvents
11 11
     from './service/remotecontrol/RemoteControlEvents';
12 12
 import UIEvents from './service/UI/UIEvents';
13 13
 import UIUtil from './modules/UI/util/UIUtil';
14
+import { createTaskQueue } from './modules/util/helpers';
14 15
 import * as JitsiMeetConferenceEvents from './ConferenceEvents';
15 16
 
16 17
 import {
@@ -274,6 +275,27 @@ function redirectToStaticPage(pathname) {
274 275
     windowLocation.pathname = newPathname;
275 276
 }
276 277
 
278
+/**
279
+ * A queue for the async replaceLocalTrack action so that multiple audio
280
+ * replacements cannot happen simultaneously. This solves the issue where
281
+ * replaceLocalTrack is called multiple times with an oldTrack of null, causing
282
+ * multiple local tracks of the same type to be used.
283
+ *
284
+ * @private
285
+ * @type {Object}
286
+ */
287
+const _replaceLocalAudioTrackQueue = createTaskQueue();
288
+
289
+/**
290
+ * A task queue for replacement local video tracks. This separate queue exists
291
+ * so video replacement is not blocked by audio replacement tasks in the queue
292
+ * {@link _replaceLocalAudioTrackQueue}.
293
+ *
294
+ * @private
295
+ * @type {Object}
296
+ */
297
+const _replaceLocalVideoTrackQueue = createTaskQueue();
298
+
277 299
 /**
278 300
  *
279 301
  */
@@ -856,9 +878,6 @@ export default {
856 878
             return;
857 879
         }
858 880
 
859
-        // FIXME it is possible to queue this task twice, but it's not causing
860
-        // any issues. Specifically this can happen when the previous
861
-        // get user media call is blocked on "ask user for permissions" dialog.
862 881
         if (!this.localVideo && !mute) {
863 882
             const maybeShowErrorDialog = error => {
864 883
                 showUI && APP.UI.showCameraErrorNotification(error);
@@ -1261,16 +1280,23 @@ export default {
1261 1280
      * @returns {Promise}
1262 1281
      */
1263 1282
     useVideoStream(newStream) {
1264
-        return APP.store.dispatch(
1265
-            replaceLocalTrack(this.localVideo, newStream, room))
1266
-            .then(() => {
1267
-                this.localVideo = newStream;
1268
-                this._setSharingScreen(newStream);
1269
-                if (newStream) {
1270
-                    APP.UI.addLocalStream(newStream);
1271
-                }
1272
-                this.setVideoMuteStatus(this.isLocalVideoMuted());
1283
+        return new Promise((resolve, reject) => {
1284
+            _replaceLocalVideoTrackQueue.enqueue(onFinish => {
1285
+                APP.store.dispatch(
1286
+                replaceLocalTrack(this.localVideo, newStream, room))
1287
+                    .then(() => {
1288
+                        this.localVideo = newStream;
1289
+                        this._setSharingScreen(newStream);
1290
+                        if (newStream) {
1291
+                            APP.UI.addLocalStream(newStream);
1292
+                        }
1293
+                        this.setVideoMuteStatus(this.isLocalVideoMuted());
1294
+                    })
1295
+                    .then(resolve)
1296
+                    .catch(reject)
1297
+                    .then(onFinish);
1273 1298
             });
1299
+        });
1274 1300
     },
1275 1301
 
1276 1302
     /**
@@ -1300,15 +1326,22 @@ export default {
1300 1326
      * @returns {Promise}
1301 1327
      */
1302 1328
     useAudioStream(newStream) {
1303
-        return APP.store.dispatch(
1304
-            replaceLocalTrack(this.localAudio, newStream, room))
1305
-            .then(() => {
1306
-                this.localAudio = newStream;
1307
-                if (newStream) {
1308
-                    APP.UI.addLocalStream(newStream);
1309
-                }
1310
-                this.setAudioMuteStatus(this.isLocalAudioMuted());
1329
+        return new Promise((resolve, reject) => {
1330
+            _replaceLocalAudioTrackQueue.enqueue(onFinish => {
1331
+                APP.store.dispatch(
1332
+                replaceLocalTrack(this.localAudio, newStream, room))
1333
+                    .then(() => {
1334
+                        this.localAudio = newStream;
1335
+                        if (newStream) {
1336
+                            APP.UI.addLocalStream(newStream);
1337
+                        }
1338
+                        this.setAudioMuteStatus(this.isLocalAudioMuted());
1339
+                    })
1340
+                    .then(resolve)
1341
+                    .catch(reject)
1342
+                    .then(onFinish);
1311 1343
             });
1344
+        });
1312 1345
     },
1313 1346
 
1314 1347
     /**
@@ -2375,11 +2408,24 @@ export default {
2375 2408
                     createLocalTracksF,
2376 2409
                     newDevices.videoinput,
2377 2410
                     newDevices.audioinput)
2378
-                .then(tracks =>
2379
-                    Promise.all(this._setLocalAudioVideoStreams(tracks)))
2411
+                .then(tracks => {
2412
+                    // If audio or video muted before, or we unplugged current
2413
+                    // device and selected new one, then mute new track.
2414
+                    const muteSyncPromises = tracks.map(track => {
2415
+                        if ((track.isVideoTrack() && videoWasMuted)
2416
+                            || (track.isAudioTrack() && audioWasMuted)) {
2417
+                            return track.mute();
2418
+                        }
2419
+
2420
+                        return Promise.resolve();
2421
+                    });
2422
+
2423
+                    return Promise.all(muteSyncPromises)
2424
+                        .then(() => Promise.all(
2425
+                            this._setLocalAudioVideoStreams(tracks)));
2426
+                })
2380 2427
                 .then(() => {
2381
-                    // If audio was muted before, or we unplugged current device
2382
-                    // and selected new one, then mute new audio track.
2428
+                    // Log and sync known mute state.
2383 2429
                     if (audioWasMuted) {
2384 2430
                         sendAnalytics(createTrackMutedEvent(
2385 2431
                             'audio',
@@ -2388,8 +2434,6 @@ export default {
2388 2434
                         muteLocalAudio(true);
2389 2435
                     }
2390 2436
 
2391
-                    // If video was muted before, or we unplugged current device
2392
-                    // and selected new one, then mute new video track.
2393 2437
                     if (!this.isSharingScreen && videoWasMuted) {
2394 2438
                         sendAnalytics(createTrackMutedEvent(
2395 2439
                             'video',

+ 0
- 14
modules/UI/videolayout/VideoContainer.js Visa fil

@@ -216,8 +216,6 @@ export class VideoContainer extends LargeContainer {
216 216
         this.emitter = emitter;
217 217
         this.resizeContainer = resizeContainer;
218 218
 
219
-        this.isVisible = false;
220
-
221 219
         /**
222 220
          * Whether the background should fit the height of the container
223 221
          * (portrait) or fit the width of the container (landscape).
@@ -603,17 +601,11 @@ export class VideoContainer extends LargeContainer {
603 601
      * TODO: refactor this since Temasys is no longer supported.
604 602
      */
605 603
     show() {
606
-        // its already visible
607
-        if (this.isVisible) {
608
-            return Promise.resolve();
609
-        }
610
-
611 604
         return new Promise(resolve => {
612 605
             this.$wrapperParent.css('visibility', 'visible').fadeTo(
613 606
                 FADE_DURATION_MS,
614 607
                 1,
615 608
                 () => {
616
-                    this.isVisible = true;
617 609
                     resolve();
618 610
                 }
619 611
             );
@@ -628,15 +620,9 @@ export class VideoContainer extends LargeContainer {
628 620
         // hide its avatar
629 621
         this.showAvatar(false);
630 622
 
631
-        // its already hidden
632
-        if (!this.isVisible) {
633
-            return Promise.resolve();
634
-        }
635
-
636 623
         return new Promise(resolve => {
637 624
             this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
638 625
                 this.$wrapperParent.css('visibility', 'hidden');
639
-                this.isVisible = false;
640 626
                 resolve();
641 627
             });
642 628
         });

+ 63
- 0
modules/util/TaskQueue.js Visa fil

@@ -0,0 +1,63 @@
1
+const logger = require('jitsi-meet-logger').getLogger(__filename);
2
+
3
+/**
4
+ * Manages a queue of functions where the current function in progress will
5
+ * automatically execute the next queued function.
6
+ */
7
+export class TaskQueue {
8
+    /**
9
+     * Creates a new instance of {@link TaskQueue} and sets initial instance
10
+     * variable values.
11
+     */
12
+    constructor() {
13
+        this._queue = [];
14
+        this._currentTask = null;
15
+
16
+        this._onTaskComplete = this._onTaskComplete.bind(this);
17
+    }
18
+
19
+    /**
20
+     * Adds a new function to the queue. It will be immediately invoked if no
21
+     * other functions are queued.
22
+     *
23
+     * @param {Function} taskFunction - The function to be queued for execution.
24
+     * @private
25
+     * @returns {void}
26
+     */
27
+    enqueue(taskFunction) {
28
+        this._queue.push(taskFunction);
29
+        this._executeNext();
30
+    }
31
+
32
+    /**
33
+     * If no queued task is currently executing, invokes the first task in the
34
+     * queue if any.
35
+     *
36
+     * @private
37
+     * @returns {void}
38
+     */
39
+    _executeNext() {
40
+        if (this._currentTask) {
41
+            logger.warn('Task queued while a task is in progress.');
42
+
43
+            return;
44
+        }
45
+
46
+        this._currentTask = this._queue.shift() || null;
47
+
48
+        if (this._currentTask) {
49
+            this._currentTask(this._onTaskComplete);
50
+        }
51
+    }
52
+
53
+    /**
54
+     * Prepares to invoke the next function in the queue.
55
+     *
56
+     * @private
57
+     * @returns {void}
58
+     */
59
+    _onTaskComplete() {
60
+        this._currentTask = null;
61
+        this._executeNext();
62
+    }
63
+}

+ 11
- 0
modules/util/helpers.js Visa fil

@@ -1,3 +1,5 @@
1
+import { TaskQueue } from './TaskQueue';
2
+
1 3
 /**
2 4
  * Create deferred object.
3 5
  *
@@ -13,3 +15,12 @@ export function createDeferred() {
13 15
 
14 16
     return deferred;
15 17
 }
18
+
19
+/**
20
+ * Returns an instance of {@link TaskQueue}.
21
+ *
22
+ * @returns {Object}
23
+ */
24
+export function createTaskQueue() {
25
+    return new TaskQueue();
26
+}

Laddar…
Avbryt
Spara