Browse Source

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

Queue replaceLocalTrack
master
virtuacoplenny 7 years ago
parent
commit
2043845d52
No account linked to committer's email address
4 changed files with 144 additions and 40 deletions
  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 View File

11
     from './service/remotecontrol/RemoteControlEvents';
11
     from './service/remotecontrol/RemoteControlEvents';
12
 import UIEvents from './service/UI/UIEvents';
12
 import UIEvents from './service/UI/UIEvents';
13
 import UIUtil from './modules/UI/util/UIUtil';
13
 import UIUtil from './modules/UI/util/UIUtil';
14
+import { createTaskQueue } from './modules/util/helpers';
14
 import * as JitsiMeetConferenceEvents from './ConferenceEvents';
15
 import * as JitsiMeetConferenceEvents from './ConferenceEvents';
15
 
16
 
16
 import {
17
 import {
274
     windowLocation.pathname = newPathname;
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
             return;
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
         if (!this.localVideo && !mute) {
881
         if (!this.localVideo && !mute) {
863
             const maybeShowErrorDialog = error => {
882
             const maybeShowErrorDialog = error => {
864
                 showUI && APP.UI.showCameraErrorNotification(error);
883
                 showUI && APP.UI.showCameraErrorNotification(error);
1261
      * @returns {Promise}
1280
      * @returns {Promise}
1262
      */
1281
      */
1263
     useVideoStream(newStream) {
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
      * @returns {Promise}
1326
      * @returns {Promise}
1301
      */
1327
      */
1302
     useAudioStream(newStream) {
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
                     createLocalTracksF,
2408
                     createLocalTracksF,
2376
                     newDevices.videoinput,
2409
                     newDevices.videoinput,
2377
                     newDevices.audioinput)
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
                 .then(() => {
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
                     if (audioWasMuted) {
2429
                     if (audioWasMuted) {
2384
                         sendAnalytics(createTrackMutedEvent(
2430
                         sendAnalytics(createTrackMutedEvent(
2385
                             'audio',
2431
                             'audio',
2388
                         muteLocalAudio(true);
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
                     if (!this.isSharingScreen && videoWasMuted) {
2437
                     if (!this.isSharingScreen && videoWasMuted) {
2394
                         sendAnalytics(createTrackMutedEvent(
2438
                         sendAnalytics(createTrackMutedEvent(
2395
                             'video',
2439
                             'video',

+ 0
- 14
modules/UI/videolayout/VideoContainer.js View File

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

+ 63
- 0
modules/util/TaskQueue.js View File

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 View File

1
+import { TaskQueue } from './TaskQueue';
2
+
1
 /**
3
 /**
2
  * Create deferred object.
4
  * Create deferred object.
3
  *
5
  *
13
 
15
 
14
     return deferred;
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
+}

Loading…
Cancel
Save