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';
|
||||
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,16 +1280,23 @@ export default {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream(newStream) {
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1300,15 +1326,22 @@ export default {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
useAudioStream(newStream) {
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
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',
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
@ -13,3 +15,12 @@ export function createDeferred() {
|
|||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link TaskQueue}.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function createTaskQueue() {
|
||||
return new TaskQueue();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue