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", "image7" : "Sunrise",
"desktopShareError": "Could not create desktop share", "desktopShareError": "Could not create desktop share",
"desktopShare":"Desktop share", "desktopShare":"Desktop share",
"webAssemblyWarning": "WebAssembly not supported" "webAssemblyWarning": "WebAssembly not supported",
"backgroundEffectError": "Failed to apply background effect."
}, },
"feedback": { "feedback": {
"average": "Average", "average": "Average",

View File

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

View File

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

View File

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