diff --git a/lang/main.json b/lang/main.json index 17979e3a63..09a2f8c270 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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", diff --git a/react/features/stream-effects/virtual-background/index.js b/react/features/stream-effects/virtual-background/index.js index f59ffb75ea..634f8f6f55 100644 --- a/react/features/stream-effects/virtual-background/index.js +++ b/react/features/stream-effects/virtual-background/index.js @@ -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; diff --git a/react/features/virtual-background/components/VirtualBackgroundDialog.js b/react/features/virtual-background/components/VirtualBackgroundDialog.js index 8fecdce210..9b3968b5ad 100644 --- a/react/features/virtual-background/components/VirtualBackgroundDialog.js +++ b/react/features/virtual-background/components/VirtualBackgroundDialog.js @@ -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>((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 ( - + {loading ? (
) : (
- + } { this.state = { loading: false, + localTrackLoaded: false, jitsiTrack: null }; } @@ -99,24 +113,42 @@ class VirtualBackgroundPreview extends PureComponent { * @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 { */ 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 { 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(); } diff --git a/react/features/virtual-background/functions.js b/react/features/virtual-background/functions.js index dab0d88ee7..6554007984 100644 --- a/react/features/virtual-background/functions.js +++ b/react/features/virtual-background/functions.js @@ -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 { + return new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('408')); + + return; + }, milliseconds); + + promise.then(resolve, reject); + }); +}