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

Queue replaceLocalTrack
This commit is contained in:
virtuacoplenny 2018-09-05 10:20:12 -07:00 committed by GitHub
commit 2043845d52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 40 deletions

View File

@ -11,6 +11,7 @@ import * as RemoteControlEvents
from './service/remotecontrol/RemoteControlEvents';
import UIEvents from './service/UI/UIEvents';
import UIUtil from './modules/UI/util/UIUtil';
import { createTaskQueue } from './modules/util/helpers';
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
import {
@ -274,6 +275,27 @@ function redirectToStaticPage(pathname) {
windowLocation.pathname = newPathname;
}
/**
* A queue for the async replaceLocalTrack action so that multiple audio
* replacements cannot happen simultaneously. This solves the issue where
* replaceLocalTrack is called multiple times with an oldTrack of null, causing
* multiple local tracks of the same type to be used.
*
* @private
* @type {Object}
*/
const _replaceLocalAudioTrackQueue = createTaskQueue();
/**
* A task queue for replacement local video tracks. This separate queue exists
* so video replacement is not blocked by audio replacement tasks in the queue
* {@link _replaceLocalAudioTrackQueue}.
*
* @private
* @type {Object}
*/
const _replaceLocalVideoTrackQueue = createTaskQueue();
/**
*
*/
@ -856,9 +878,6 @@ export default {
return;
}
// FIXME it is possible to queue this task twice, but it's not causing
// any issues. Specifically this can happen when the previous
// get user media call is blocked on "ask user for permissions" dialog.
if (!this.localVideo && !mute) {
const maybeShowErrorDialog = error => {
showUI && APP.UI.showCameraErrorNotification(error);
@ -1261,7 +1280,9 @@ export default {
* @returns {Promise}
*/
useVideoStream(newStream) {
return APP.store.dispatch(
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
APP.store.dispatch(
replaceLocalTrack(this.localVideo, newStream, room))
.then(() => {
this.localVideo = newStream;
@ -1270,6 +1291,11 @@ export default {
APP.UI.addLocalStream(newStream);
}
this.setVideoMuteStatus(this.isLocalVideoMuted());
})
.then(resolve)
.catch(reject)
.then(onFinish);
});
});
},
@ -1300,7 +1326,9 @@ export default {
* @returns {Promise}
*/
useAudioStream(newStream) {
return APP.store.dispatch(
return new Promise((resolve, reject) => {
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
APP.store.dispatch(
replaceLocalTrack(this.localAudio, newStream, room))
.then(() => {
this.localAudio = newStream;
@ -1308,6 +1336,11 @@ export default {
APP.UI.addLocalStream(newStream);
}
this.setAudioMuteStatus(this.isLocalAudioMuted());
})
.then(resolve)
.catch(reject)
.then(onFinish);
});
});
},
@ -2375,11 +2408,24 @@ export default {
createLocalTracksF,
newDevices.videoinput,
newDevices.audioinput)
.then(tracks =>
Promise.all(this._setLocalAudioVideoStreams(tracks)))
.then(tracks => {
// If audio or video muted before, or we unplugged current
// device and selected new one, then mute new track.
const muteSyncPromises = tracks.map(track => {
if ((track.isVideoTrack() && videoWasMuted)
|| (track.isAudioTrack() && audioWasMuted)) {
return track.mute();
}
return Promise.resolve();
});
return Promise.all(muteSyncPromises)
.then(() => Promise.all(
this._setLocalAudioVideoStreams(tracks)));
})
.then(() => {
// If audio was muted before, or we unplugged current device
// and selected new one, then mute new audio track.
// Log and sync known mute state.
if (audioWasMuted) {
sendAnalytics(createTrackMutedEvent(
'audio',
@ -2388,8 +2434,6 @@ export default {
muteLocalAudio(true);
}
// If video was muted before, or we unplugged current device
// and selected new one, then mute new video track.
if (!this.isSharingScreen && videoWasMuted) {
sendAnalytics(createTrackMutedEvent(
'video',

View File

@ -216,8 +216,6 @@ export class VideoContainer extends LargeContainer {
this.emitter = emitter;
this.resizeContainer = resizeContainer;
this.isVisible = false;
/**
* Whether the background should fit the height of the container
* (portrait) or fit the width of the container (landscape).
@ -603,17 +601,11 @@ export class VideoContainer extends LargeContainer {
* TODO: refactor this since Temasys is no longer supported.
*/
show() {
// its already visible
if (this.isVisible) {
return Promise.resolve();
}
return new Promise(resolve => {
this.$wrapperParent.css('visibility', 'visible').fadeTo(
FADE_DURATION_MS,
1,
() => {
this.isVisible = true;
resolve();
}
);
@ -628,15 +620,9 @@ export class VideoContainer extends LargeContainer {
// hide its avatar
this.showAvatar(false);
// its already hidden
if (!this.isVisible) {
return Promise.resolve();
}
return new Promise(resolve => {
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
this.$wrapperParent.css('visibility', 'hidden');
this.isVisible = false;
resolve();
});
});

63
modules/util/TaskQueue.js Normal file
View File

@ -0,0 +1,63 @@
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Manages a queue of functions where the current function in progress will
* automatically execute the next queued function.
*/
export class TaskQueue {
/**
* Creates a new instance of {@link TaskQueue} and sets initial instance
* variable values.
*/
constructor() {
this._queue = [];
this._currentTask = null;
this._onTaskComplete = this._onTaskComplete.bind(this);
}
/**
* Adds a new function to the queue. It will be immediately invoked if no
* other functions are queued.
*
* @param {Function} taskFunction - The function to be queued for execution.
* @private
* @returns {void}
*/
enqueue(taskFunction) {
this._queue.push(taskFunction);
this._executeNext();
}
/**
* If no queued task is currently executing, invokes the first task in the
* queue if any.
*
* @private
* @returns {void}
*/
_executeNext() {
if (this._currentTask) {
logger.warn('Task queued while a task is in progress.');
return;
}
this._currentTask = this._queue.shift() || null;
if (this._currentTask) {
this._currentTask(this._onTaskComplete);
}
}
/**
* Prepares to invoke the next function in the queue.
*
* @private
* @returns {void}
*/
_onTaskComplete() {
this._currentTask = null;
this._executeNext();
}
}

View File

@ -1,3 +1,5 @@
import { TaskQueue } from './TaskQueue';
/**
* Create deferred object.
*
@ -13,3 +15,12 @@ export function createDeferred() {
return deferred;
}
/**
* Returns an instance of {@link TaskQueue}.
*
* @returns {Object}
*/
export function createTaskQueue() {
return new TaskQueue();
}