feat(virtual-background) add virtual background preview
Also enable background selection while muted.
This commit is contained in:
parent
2f51d9fd3e
commit
9ef984ca3d
|
@ -118,6 +118,13 @@
|
||||||
|
|
||||||
.modal-dialog-form .virtual-background-loading {
|
.modal-dialog-form .virtual-background-loading {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: 10px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.modal-dialog-form .video-preview {
|
||||||
|
height: 250px;
|
||||||
}
|
}
|
||||||
.file-upload-btn {
|
.file-upload-btn {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -126,6 +133,7 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
margin-top: 16px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #669AEC;
|
color: #669AEC;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -150,10 +158,29 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-content-text{
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-background{
|
.add-background{
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apply-background-btn{
|
||||||
|
margin-top: 16px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-background-preview-entry{
|
||||||
|
height: 250px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 572px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
@media (min-width: 432px) and (max-width: 632px) {
|
||||||
|
width: 340px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview-loader{
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 35%;
|
||||||
|
transform: translate(-50%,-35%);
|
||||||
|
}
|
|
@ -339,12 +339,12 @@
|
||||||
"title": "Embed this meeting"
|
"title": "Embed this meeting"
|
||||||
},
|
},
|
||||||
"virtualBackground": {
|
"virtualBackground": {
|
||||||
|
"apply": "Apply",
|
||||||
"title": "Virtual backgrounds",
|
"title": "Virtual backgrounds",
|
||||||
"blur": "Blur",
|
"blur": "Blur",
|
||||||
"slightBlur": "Slight Blur",
|
"slightBlur": "Slight Blur",
|
||||||
"removeBackground": "Remove background",
|
"removeBackground": "Remove background",
|
||||||
"addBackground": "Add background",
|
"addBackground": "Add background",
|
||||||
"pleaseWait": "Please wait...",
|
|
||||||
"none": "None"
|
"none": "None"
|
||||||
},
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
|
|
|
@ -6,12 +6,14 @@ import uuid from 'uuid';
|
||||||
|
|
||||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||||
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
||||||
|
import { isVideoMutedByUser } from '../base/media';
|
||||||
import {
|
import {
|
||||||
getLocalAudioTrack,
|
getLocalAudioTrack,
|
||||||
getLocalVideoTrack,
|
getLocalVideoTrack,
|
||||||
trackAdded,
|
trackAdded,
|
||||||
replaceLocalTrack
|
replaceLocalTrack
|
||||||
} from '../base/tracks';
|
} from '../base/tracks';
|
||||||
|
import { createLocalTracksF } from '../base/tracks/functions';
|
||||||
import { openURLInBrowser } from '../base/util';
|
import { openURLInBrowser } from '../base/util';
|
||||||
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
|
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
|
||||||
import { showErrorNotification } from '../notifications';
|
import { showErrorNotification } from '../notifications';
|
||||||
|
@ -309,10 +311,17 @@ export function replaceVideoTrackById(deviceId: Object) {
|
||||||
return async (dispatch: Function, getState: Function) => {
|
return async (dispatch: Function, getState: Function) => {
|
||||||
try {
|
try {
|
||||||
const tracks = getState()['features/base/tracks'];
|
const tracks = getState()['features/base/tracks'];
|
||||||
const newTrack = await createLocalTrack('video', deviceId);
|
const wasVideoMuted = isVideoMutedByUser(getState());
|
||||||
|
const [ newTrack ] = await createLocalTracksF(
|
||||||
|
{ cameraDeviceId: deviceId,
|
||||||
|
devices: [ 'video' ] },
|
||||||
|
{ dispatch,
|
||||||
|
getState }
|
||||||
|
);
|
||||||
const oldTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
const oldTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||||
|
|
||||||
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
||||||
|
wasVideoMuted && newTrack.mute();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
|
dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
|
||||||
logger.log('Error replacing video track', err);
|
logger.log('Error replacing video track', err);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getLocalVideoTrack } from '../base/tracks';
|
|
||||||
import { createVirtualBackgroundEffect } from '../stream-effects/virtual-background';
|
import { createVirtualBackgroundEffect } from '../stream-effects/virtual-background';
|
||||||
|
|
||||||
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
|
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
|
||||||
|
@ -10,26 +9,28 @@ import logger from './logger';
|
||||||
* Signals the local participant activate the virtual background video or not.
|
* Signals the local participant activate the virtual background video or not.
|
||||||
*
|
*
|
||||||
* @param {Object} options - Represents the virtual background setted options.
|
* @param {Object} options - Represents the virtual background setted options.
|
||||||
|
* @param {Object} jitsiTrack - Represents the jitsi track that will have backgraund effect applied.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function toggleBackgroundEffect(options: Object) {
|
export function toggleBackgroundEffect(options: Object, jitsiTrack: Object) {
|
||||||
return async function(dispatch: Object => Object, getState: () => any) {
|
return async function(dispatch: Object => Object, getState: () => any) {
|
||||||
await dispatch(backgroundEnabled(options.enabled));
|
await dispatch(backgroundEnabled(options.enabled));
|
||||||
await dispatch(setVirtualBackground(options));
|
await dispatch(setVirtualBackground(options));
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
|
|
||||||
const virtualBackground = state['features/virtual-background'];
|
const virtualBackground = state['features/virtual-background'];
|
||||||
|
|
||||||
try {
|
if (jitsiTrack) {
|
||||||
if (options.enabled) {
|
try {
|
||||||
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground));
|
if (options.enabled) {
|
||||||
} else {
|
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground));
|
||||||
await jitsiTrack.setEffect(undefined);
|
} else {
|
||||||
|
await jitsiTrack.setEffect(undefined);
|
||||||
|
dispatch(backgroundEnabled(false));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
dispatch(backgroundEnabled(false));
|
dispatch(backgroundEnabled(false));
|
||||||
|
logger.error('Error on apply background effect:', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
dispatch(backgroundEnabled(false));
|
|
||||||
logger.error('Error on apply backgroun effect:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { IconVirtualBackground } from '../../base/icons';
|
||||||
import { connect } from '../../base/redux';
|
import { connect } from '../../base/redux';
|
||||||
import { AbstractButton } from '../../base/toolbox/components';
|
import { AbstractButton } from '../../base/toolbox/components';
|
||||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||||
import { isLocalCameraTrackMuted } from '../../base/tracks';
|
|
||||||
|
|
||||||
import { VirtualBackgroundDialog } from './index';
|
import { VirtualBackgroundDialog } from './index';
|
||||||
|
|
||||||
|
@ -20,11 +19,6 @@ type Props = AbstractButtonProps & {
|
||||||
*/
|
*/
|
||||||
_isBackgroundEnabled: boolean,
|
_isBackgroundEnabled: boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether video is currently muted or not.
|
|
||||||
*/
|
|
||||||
_videoMuted: boolean,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The redux {@code dispatch} function.
|
* The redux {@code dispatch} function.
|
||||||
*/
|
*/
|
||||||
|
@ -63,17 +57,6 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
|
||||||
_isToggled() {
|
_isToggled() {
|
||||||
return this.props._isBackgroundEnabled;
|
return this.props._isBackgroundEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code boolean} value indicating if disabled state is
|
|
||||||
* enabled or not.
|
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
_isDisabled() {
|
|
||||||
return this.props._videoMuted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,11 +70,9 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state): Object {
|
function _mapStateToProps(state): Object {
|
||||||
const tracks = state['features/base/tracks'];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled),
|
_isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled)
|
||||||
_videoMuted: isLocalCameraTrackMuted(tracks)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,17 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import { Dialog } from '../../base/dialog';
|
import { Dialog, hideDialog } from '../../base/dialog';
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import { Icon, IconCloseSmall, IconPlusCircle } from '../../base/icons';
|
import { Icon, IconCloseSmall, IconPlusCircle } from '../../base/icons';
|
||||||
import { connect } from '../../base/redux';
|
import { connect } from '../../base/redux';
|
||||||
|
import { getLocalVideoTrack } from '../../base/tracks';
|
||||||
import { toggleBackgroundEffect } from '../actions';
|
import { toggleBackgroundEffect } from '../actions';
|
||||||
import { resizeImage, toDataURL } from '../functions';
|
import { resizeImage, toDataURL } from '../functions';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
|
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||||
|
|
||||||
// The limit of virtual background uploads is 24. When the number
|
// The limit of virtual background uploads is 24. When the number
|
||||||
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
||||||
// the first/oldest uploaded background.
|
// the first/oldest uploaded background.
|
||||||
|
@ -49,6 +52,11 @@ const images = [
|
||||||
];
|
];
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the jitsi track that will have backgraund effect applied.
|
||||||
|
*/
|
||||||
|
_jitsiTrack: Object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the selected thumbnail identifier.
|
* Returns the selected thumbnail identifier.
|
||||||
*/
|
*/
|
||||||
|
@ -70,7 +78,8 @@ type Props = {
|
||||||
*
|
*
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
function VirtualBackground({ _jitsiTrack, _selectedThumbnail, dispatch, t }: Props) {
|
||||||
|
const [ options, setOptions ] = useState({});
|
||||||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||||
const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
|
const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
|
||||||
const [ loading, isloading ] = useState(false);
|
const [ loading, isloading ] = useState(false);
|
||||||
|
@ -95,55 +104,39 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
}, [ storedImages ]);
|
}, [ storedImages ]);
|
||||||
|
|
||||||
const enableBlur = async (blurValue, selection) => {
|
const enableBlur = async (blurValue, selection) => {
|
||||||
isloading(true);
|
setOptions({
|
||||||
await dispatch(
|
backgroundType: 'blur',
|
||||||
toggleBackgroundEffect({
|
enabled: true,
|
||||||
backgroundType: 'blur',
|
blurValue,
|
||||||
enabled: true,
|
selectedThumbnail: selection
|
||||||
blurValue,
|
});
|
||||||
selectedThumbnail: selection
|
|
||||||
})
|
|
||||||
);
|
|
||||||
isloading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeBackground = async () => {
|
const removeBackground = async () => {
|
||||||
isloading(true);
|
setOptions({
|
||||||
await dispatch(
|
enabled: false,
|
||||||
toggleBackgroundEffect({
|
selectedThumbnail: 'none'
|
||||||
enabled: false,
|
});
|
||||||
selectedThumbnail: 'none'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
isloading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setUploadedImageBackground = async image => {
|
const setUploadedImageBackground = async image => {
|
||||||
isloading(true);
|
setOptions({
|
||||||
await dispatch(
|
backgroundType: 'image',
|
||||||
toggleBackgroundEffect({
|
enabled: true,
|
||||||
backgroundType: 'image',
|
url: image.src,
|
||||||
enabled: true,
|
selectedThumbnail: image.id
|
||||||
url: image.src,
|
});
|
||||||
selectedThumbnail: image.id
|
|
||||||
})
|
|
||||||
);
|
|
||||||
isloading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setImageBackground = async image => {
|
const setImageBackground = async image => {
|
||||||
isloading(true);
|
|
||||||
const url = await toDataURL(image.src);
|
const url = await toDataURL(image.src);
|
||||||
|
|
||||||
await dispatch(
|
setOptions({
|
||||||
toggleBackgroundEffect({
|
backgroundType: 'image',
|
||||||
backgroundType: 'image',
|
enabled: true,
|
||||||
enabled: true,
|
url,
|
||||||
url,
|
selectedThumbnail: image.id
|
||||||
selectedThumbnail: image.id
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
isloading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadImage = async imageFile => {
|
const uploadImage = async imageFile => {
|
||||||
|
@ -154,7 +147,6 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
const url = await resizeImage(reader.result);
|
const url = await resizeImage(reader.result);
|
||||||
const uuId = uuid.v4();
|
const uuId = uuid.v4();
|
||||||
|
|
||||||
isloading(true);
|
|
||||||
setStoredImages([
|
setStoredImages([
|
||||||
...storedImages,
|
...storedImages,
|
||||||
{
|
{
|
||||||
|
@ -162,15 +154,12 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
src: url
|
src: url
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
await dispatch(
|
setOptions({
|
||||||
toggleBackgroundEffect({
|
backgroundType: 'image',
|
||||||
backgroundType: 'image',
|
enabled: true,
|
||||||
enabled: true,
|
url,
|
||||||
url,
|
selectedThumbnail: uuId
|
||||||
selectedThumbnail: uuId
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
isloading(false);
|
|
||||||
};
|
};
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
isloading(false);
|
isloading(false);
|
||||||
|
@ -178,15 +167,24 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applyVirtualBackground = async () => {
|
||||||
|
isloading(true);
|
||||||
|
await dispatch(toggleBackgroundEffect(options, _jitsiTrack));
|
||||||
|
await isloading(false);
|
||||||
|
dispatch(hideDialog());
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
hideCancelButton = { true }
|
hideCancelButton = { false }
|
||||||
submitDisabled = { true }
|
okKey = { 'virtualBackground.apply' }
|
||||||
|
onSubmit = { applyVirtualBackground }
|
||||||
|
submitDisabled = { !options || loading }
|
||||||
titleKey = { 'virtualBackground.title' }
|
titleKey = { 'virtualBackground.title' }
|
||||||
width = '640px'>
|
width = '640px'>
|
||||||
|
<VirtualBackgroundPreview options = { options } />
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className = 'virtual-background-loading'>
|
<div className = 'virtual-background-loading'>
|
||||||
<span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
|
|
||||||
<Spinner
|
<Spinner
|
||||||
isCompleting = { false }
|
isCompleting = { false }
|
||||||
size = 'medium' />
|
size = 'medium' />
|
||||||
|
@ -227,7 +225,11 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
</div>
|
</div>
|
||||||
{images.map((image, index) => (
|
{images.map((image, index) => (
|
||||||
<img
|
<img
|
||||||
className = { _selectedThumbnail === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
className = {
|
||||||
|
options.selectedThumbnail === image.id || _selectedThumbnail === image.id
|
||||||
|
? 'thumbnail-selected'
|
||||||
|
: 'thumbnail'
|
||||||
|
}
|
||||||
key = { index }
|
key = { index }
|
||||||
onClick = { () => setImageBackground(image) }
|
onClick = { () => setImageBackground(image) }
|
||||||
onError = { event => event.target.style.display = 'none' }
|
onError = { event => event.target.style.display = 'none' }
|
||||||
|
@ -262,13 +264,12 @@ function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{Props}}
|
||||||
* _selectedThumbnail: string
|
|
||||||
* }}
|
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state): Object {
|
function _mapStateToProps(state): Object {
|
||||||
return {
|
return {
|
||||||
_selectedThumbnail: state['features/virtual-background'].selectedThumbnail
|
_selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
|
||||||
|
_jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import Spinner from '@atlaskit/spinner';
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
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 { toggleBackgroundEffect } from '../actions';
|
||||||
|
|
||||||
|
const videoClassName = 'video-preview-video flipVideoX';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
|
||||||
|
*/
|
||||||
|
export type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deviceId of the camera device currently being used.
|
||||||
|
*/
|
||||||
|
_currentCameraDeviceId: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redux {@code dispatch} function.
|
||||||
|
*/
|
||||||
|
dispatch: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the virtual background setted options.
|
||||||
|
*/
|
||||||
|
options: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} state of {@link VirtualBackgroundPreview}.
|
||||||
|
*/
|
||||||
|
type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loader activated on setting virtual background.
|
||||||
|
*/
|
||||||
|
loading: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the selected device camera only.
|
||||||
|
*/
|
||||||
|
jitsiTrack: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link PureComponent} which displays the virtual
|
||||||
|
* background preview.
|
||||||
|
*
|
||||||
|
* @extends PureComponent
|
||||||
|
*/
|
||||||
|
class VirtualBackgroundPreview extends PureComponent<Props, State> {
|
||||||
|
_componentWasUnmounted: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code VirtualBackgroundPreview} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
jitsiTrack: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and updates the track data.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
async _setTracks() {
|
||||||
|
const [ jitsiTrack ] = await createLocalTracksF({
|
||||||
|
cameraDeviceId: this.props._currentCameraDeviceId,
|
||||||
|
devices: [ 'video' ]
|
||||||
|
});
|
||||||
|
|
||||||
|
// In case the component gets unmounted before the tracks are created
|
||||||
|
// avoid a leak by not setting the state
|
||||||
|
if (this._componentWasUnmounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
jitsiTrack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply background effect on video preview.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async _applyBackgroundEffect() {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
await this.props.dispatch(toggleBackgroundEffect(this.props.options, this.state.jitsiTrack));
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply video preview loader.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
_loadVideoPreview() {
|
||||||
|
return (
|
||||||
|
<div className = 'video-preview-loader'>
|
||||||
|
<Spinner
|
||||||
|
invertColor = { true }
|
||||||
|
isCompleting = { false }
|
||||||
|
size = { 'large' } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a preview entry.
|
||||||
|
*
|
||||||
|
* @param {Object} data - The track data.
|
||||||
|
* @returns {React$Node}
|
||||||
|
*/
|
||||||
|
_renderPreviewEntry(data) {
|
||||||
|
const { t } = this.props;
|
||||||
|
const className = 'video-background-preview-entry';
|
||||||
|
|
||||||
|
if (this.state.loading) {
|
||||||
|
return this._loadVideoPreview();
|
||||||
|
}
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className = { className }
|
||||||
|
video-preview-container = { true }>
|
||||||
|
<div className = 'video-preview-error'>{t('deviceSelection.previewUnavailable')}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const props: Object = {
|
||||||
|
className
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div { ...props }>
|
||||||
|
<Video
|
||||||
|
className = { videoClassName }
|
||||||
|
playsinline = { true }
|
||||||
|
videoTrack = {{ jitsiTrack: data }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentDidMount}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
this._setTracks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentWillUnmount}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._componentWasUnmounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentDidUpdate}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async componentDidUpdate(prevProps) {
|
||||||
|
if (!equals(this.props._currentCameraDeviceId, prevProps._currentCameraDeviceId)) {
|
||||||
|
this._setTracks();
|
||||||
|
}
|
||||||
|
if (!equals(this.props.options, prevProps.options)) {
|
||||||
|
this._applyBackgroundEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { jitsiTrack } = this.state;
|
||||||
|
|
||||||
|
return jitsiTrack
|
||||||
|
? <div className = 'video-preview'>{this._renderPreviewEntry(jitsiTrack)}</div>
|
||||||
|
: <div className = 'video-preview-loader'>{this._loadVideoPreview()}</div>
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to the associated props for the
|
||||||
|
* {@code VirtualBackgroundPreview} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{Props}}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state): Object {
|
||||||
|
return {
|
||||||
|
_currentCameraDeviceId: getCurrentCameraDeviceId(state)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(VirtualBackgroundPreview));
|
Loading…
Reference in New Issue