Merge pull request #3419 from virtuacoplenny/lenny/queue-replace-track
Queue replaceLocalTrack
This commit is contained in:
commit
2043845d52
|
@ -11,6 +11,7 @@ import * as RemoteControlEvents
|
||||||
from './service/remotecontrol/RemoteControlEvents';
|
from './service/remotecontrol/RemoteControlEvents';
|
||||||
import UIEvents from './service/UI/UIEvents';
|
import UIEvents from './service/UI/UIEvents';
|
||||||
import UIUtil from './modules/UI/util/UIUtil';
|
import UIUtil from './modules/UI/util/UIUtil';
|
||||||
|
import { createTaskQueue } from './modules/util/helpers';
|
||||||
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -274,6 +275,27 @@ function redirectToStaticPage(pathname) {
|
||||||
windowLocation.pathname = newPathname;
|
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;
|
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) {
|
if (!this.localVideo && !mute) {
|
||||||
const maybeShowErrorDialog = error => {
|
const maybeShowErrorDialog = error => {
|
||||||
showUI && APP.UI.showCameraErrorNotification(error);
|
showUI && APP.UI.showCameraErrorNotification(error);
|
||||||
|
@ -1261,7 +1280,9 @@ export default {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
useVideoStream(newStream) {
|
useVideoStream(newStream) {
|
||||||
return APP.store.dispatch(
|
return new Promise((resolve, reject) => {
|
||||||
|
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||||
|
APP.store.dispatch(
|
||||||
replaceLocalTrack(this.localVideo, newStream, room))
|
replaceLocalTrack(this.localVideo, newStream, room))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.localVideo = newStream;
|
this.localVideo = newStream;
|
||||||
|
@ -1270,6 +1291,11 @@ export default {
|
||||||
APP.UI.addLocalStream(newStream);
|
APP.UI.addLocalStream(newStream);
|
||||||
}
|
}
|
||||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||||
|
})
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject)
|
||||||
|
.then(onFinish);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1300,7 +1326,9 @@ export default {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
useAudioStream(newStream) {
|
useAudioStream(newStream) {
|
||||||
return APP.store.dispatch(
|
return new Promise((resolve, reject) => {
|
||||||
|
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
||||||
|
APP.store.dispatch(
|
||||||
replaceLocalTrack(this.localAudio, newStream, room))
|
replaceLocalTrack(this.localAudio, newStream, room))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.localAudio = newStream;
|
this.localAudio = newStream;
|
||||||
|
@ -1308,6 +1336,11 @@ export default {
|
||||||
APP.UI.addLocalStream(newStream);
|
APP.UI.addLocalStream(newStream);
|
||||||
}
|
}
|
||||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||||
|
})
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject)
|
||||||
|
.then(onFinish);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2375,11 +2408,24 @@ export default {
|
||||||
createLocalTracksF,
|
createLocalTracksF,
|
||||||
newDevices.videoinput,
|
newDevices.videoinput,
|
||||||
newDevices.audioinput)
|
newDevices.audioinput)
|
||||||
.then(tracks =>
|
.then(tracks => {
|
||||||
Promise.all(this._setLocalAudioVideoStreams(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(() => {
|
.then(() => {
|
||||||
// If audio was muted before, or we unplugged current device
|
// Log and sync known mute state.
|
||||||
// and selected new one, then mute new audio track.
|
|
||||||
if (audioWasMuted) {
|
if (audioWasMuted) {
|
||||||
sendAnalytics(createTrackMutedEvent(
|
sendAnalytics(createTrackMutedEvent(
|
||||||
'audio',
|
'audio',
|
||||||
|
@ -2388,8 +2434,6 @@ export default {
|
||||||
muteLocalAudio(true);
|
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) {
|
if (!this.isSharingScreen && videoWasMuted) {
|
||||||
sendAnalytics(createTrackMutedEvent(
|
sendAnalytics(createTrackMutedEvent(
|
||||||
'video',
|
'video',
|
||||||
|
|
|
@ -216,8 +216,6 @@ export class VideoContainer extends LargeContainer {
|
||||||
this.emitter = emitter;
|
this.emitter = emitter;
|
||||||
this.resizeContainer = resizeContainer;
|
this.resizeContainer = resizeContainer;
|
||||||
|
|
||||||
this.isVisible = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the background should fit the height of the container
|
* Whether the background should fit the height of the container
|
||||||
* (portrait) or fit the width of the container (landscape).
|
* (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.
|
* TODO: refactor this since Temasys is no longer supported.
|
||||||
*/
|
*/
|
||||||
show() {
|
show() {
|
||||||
// its already visible
|
|
||||||
if (this.isVisible) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.$wrapperParent.css('visibility', 'visible').fadeTo(
|
this.$wrapperParent.css('visibility', 'visible').fadeTo(
|
||||||
FADE_DURATION_MS,
|
FADE_DURATION_MS,
|
||||||
1,
|
1,
|
||||||
() => {
|
() => {
|
||||||
this.isVisible = true;
|
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -628,15 +620,9 @@ export class VideoContainer extends LargeContainer {
|
||||||
// hide its avatar
|
// hide its avatar
|
||||||
this.showAvatar(false);
|
this.showAvatar(false);
|
||||||
|
|
||||||
// its already hidden
|
|
||||||
if (!this.isVisible) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
|
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
|
||||||
this.$wrapperParent.css('visibility', 'hidden');
|
this.$wrapperParent.css('visibility', 'hidden');
|
||||||
this.isVisible = false;
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { TaskQueue } from './TaskQueue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create deferred object.
|
* Create deferred object.
|
||||||
*
|
*
|
||||||
|
@ -13,3 +15,12 @@ export function createDeferred() {
|
||||||
|
|
||||||
return deferred;
|
return deferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of {@link TaskQueue}.
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function createTaskQueue() {
|
||||||
|
return new TaskQueue();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue