fix(virtual-background) upload bkg image on poor connection

This commit is contained in:
Tudor D. Pop 2021-08-26 16:33:43 +03:00 committed by GitHub
parent 6c40329f6a
commit eb4aefbca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 26 deletions

View File

@ -386,7 +386,8 @@
"image7" : "Sunrise",
"desktopShareError": "Could not create desktop share",
"desktopShare":"Desktop share",
"webAssemblyWarning": "WebAssembly not supported"
"webAssemblyWarning": "WebAssembly not supported",
"backgroundEffectError": "Failed to apply background effect."
},
"feedback": {
"average": "Average",

View File

@ -1,6 +1,7 @@
// @flow
import { showWarningNotification } from '../../notifications/actions';
import { timeout } from '../../virtual-background/functions';
import logger from '../../virtual-background/logger';
import JitsiStreamBackgroundEffect from './JitsiStreamBackgroundEffect';
@ -44,17 +45,26 @@ export async function createVirtualBackgroundEffect(virtualBackground: Object, d
try {
wasmCheck = require('wasm-check');
const tfliteTimeout = 10000;
if (wasmCheck?.feature?.simd) {
tflite = await createTFLiteSIMDModule();
tflite = await timeout(tfliteTimeout, createTFLiteSIMDModule());
} else {
tflite = await createTFLiteModule();
tflite = await timeout(tfliteTimeout, createTFLiteModule());
}
} catch (err) {
logger.error('Looks like WebAssembly is disabled or not supported on this browser');
dispatch(showWarningNotification({
titleKey: 'virtualBackground.webAssemblyWarning',
description: 'WebAssembly disabled or not supported by this browser'
}));
if (err?.message === '408') {
logger.error('Failed to download tflite model!');
dispatch(showWarningNotification({
titleKey: 'virtualBackground.backgroundEffectError'
}));
} else {
logger.error('Looks like WebAssembly is disabled or not supported on this browser');
dispatch(showWarningNotification({
titleKey: 'virtualBackground.webAssemblyWarning',
description: 'WebAssembly disabled or not supported by this browser'
}));
}
return;

View File

@ -153,6 +153,7 @@ function VirtualBackground({
initialOptions,
t
}: Props) {
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
const [ options, setOptions ] = useState({ ...initialOptions });
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
@ -190,7 +191,6 @@ function VirtualBackground({
}
}, [ storedImages ]);
const enableBlur = useCallback(async () => {
setOptions({
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
@ -425,15 +425,21 @@ function VirtualBackground({
dispatch(hideDialog());
});
const loadedPreviewState = useCallback(async loaded => {
await setPreviewIsLoaded(loaded);
});
return (
<Dialog
hideCancelButton = { false }
okKey = { 'virtualBackground.apply' }
onCancel = { cancelVirtualBackground }
onSubmit = { applyVirtualBackground }
submitDisabled = { !options || loading }
submitDisabled = { !options || loading || !previewIsLoaded }
titleKey = { 'virtualBackground.title' } >
<VirtualBackgroundPreview options = { options } />
<VirtualBackgroundPreview
loadedPreview = { loadedPreviewState }
options = { options } />
{loading ? (
<div className = 'virtual-background-loading'>
<Spinner
@ -442,7 +448,7 @@ function VirtualBackground({
</div>
) : (
<div>
<label
{previewIsLoaded && <label
aria-label = { t('virtualBackground.uploadImage') }
className = 'file-upload-label'
htmlFor = 'file-upload'
@ -453,7 +459,7 @@ function VirtualBackground({
size = { 20 }
src = { IconPlusCircle } />
{t('virtualBackground.addBackground')}
</label>
</label> }
<input
accept = 'image/*'
className = 'file-upload-btn'

View File

@ -3,15 +3,18 @@
import Spinner from '@atlaskit/spinner';
import React, { PureComponent } from 'react';
import { hideDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { VIDEO_TYPE } from '../../base/media';
import Video from '../../base/media/components/Video';
import { connect, equals } from '../../base/redux';
import { getCurrentCameraDeviceId } from '../../base/settings';
import { createLocalTracksF } from '../../base/tracks/functions';
import { showWarningNotification } from '../../notifications/actions';
import { toggleBackgroundEffect } from '../actions';
import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
import { localTrackStopped } from '../functions';
import logger from '../logger';
const videoClassName = 'video-preview-video';
@ -30,6 +33,11 @@ export type Props = {
*/
dispatch: Function,
/**
* Dialog callback that indicates if the background preview was loaded.
*/
loadedPreview: Function,
/**
* Represents the virtual background setted options.
*/
@ -51,6 +59,11 @@ type State = {
*/
loading: boolean,
/**
* Flag that indicates if the local track was loaded.
*/
localTrackLoaded: boolean,
/**
* Activate the selected device camera only.
*/
@ -77,6 +90,7 @@ class VirtualBackgroundPreview extends PureComponent<Props, State> {
this.state = {
loading: false,
localTrackLoaded: false,
jitsiTrack: null
};
}
@ -99,24 +113,42 @@ class VirtualBackgroundPreview extends PureComponent<Props, State> {
* @returns {void}
*/
async _setTracks() {
const [ jitsiTrack ] = await createLocalTracksF({
cameraDeviceId: this.props._currentCameraDeviceId,
devices: [ 'video' ]
});
try {
this.setState({ loading: true });
const [ jitsiTrack ] = await createLocalTracksF({
cameraDeviceId: this.props._currentCameraDeviceId,
devices: [ 'video' ]
});
this.setState({ localTrackLoaded: true });
// In case the component gets unmounted before the tracks are created
// avoid a leak by not setting the state
if (this._componentWasUnmounted) {
this._stopStream(jitsiTrack);
// In case the component gets unmounted before the tracks are created
// avoid a leak by not setting the state
if (this._componentWasUnmounted) {
this._stopStream(jitsiTrack);
return;
}
this.setState({
jitsiTrack,
loading: false
});
this.props.loadedPreview(true);
} catch (error) {
this.props.dispatch(hideDialog());
this.props.dispatch(
showWarningNotification({
titleKey: 'virtualBackground.backgroundEffectError',
description: 'Failed to access camera device.'
})
);
logger.error('Failed to access camera device. Error on apply background effect.');
return;
}
this.setState({
jitsiTrack
});
if (this.props.options.backgroundType === VIRTUAL_BACKGROUND_TYPE.DESKTOP_SHARE) {
if (this.props.options.backgroundType === VIRTUAL_BACKGROUND_TYPE.DESKTOP_SHARE
&& this.state.localTrackLoaded) {
this._applyBackgroundEffect();
}
}
@ -128,7 +160,9 @@ class VirtualBackgroundPreview extends PureComponent<Props, State> {
*/
async _applyBackgroundEffect() {
this.setState({ loading: true });
this.props.loadedPreview(false);
await this.props.dispatch(toggleBackgroundEffect(this.props.options, this.state.jitsiTrack));
this.props.loadedPreview(true);
this.setState({ loading: false });
}
@ -212,7 +246,7 @@ class VirtualBackgroundPreview extends PureComponent<Props, State> {
if (!equals(this.props._currentCameraDeviceId, prevProps._currentCameraDeviceId)) {
this._setTracks();
}
if (!equals(this.props.options, prevProps.options)) {
if (!equals(this.props.options, prevProps.options) && this.state.localTrackLoaded) {
if (prevProps.options.backgroundType === VIRTUAL_BACKGROUND_TYPE.DESKTOP_SHARE) {
prevProps.options.url.dispose();
}

View File

@ -118,3 +118,25 @@ export function localTrackStopped(dispatch: Function, desktopTrack: Object, curr
}));
});
}
/**
* Creating a wrapper for promises on a specific time interval.
*
* @param {number} milliseconds - The number of milliseconds to wait the specified
* {@code promise} to settle before automatically rejecting the returned
* {@code Promise}.
* @param {Promise} promise - The {@code Promise} for which automatic rejecting
* after the specified timeout is to be implemented.
* @returns {Promise}
*/
export function timeout(milliseconds: number, promise: Promise<*>): Promise<Object> {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('408'));
return;
}, milliseconds);
promise.then(resolve, reject);
});
}