| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- import { Theme } from '@mui/material';
- import { withStyles } from '@mui/styles';
- import React, { PureComponent } from 'react';
- import { WithTranslation } from 'react-i18next';
- import { connect } from 'react-redux';
-
- import { IStore } from '../../app/types';
- import { hideDialog } from '../../base/dialog/actions';
- import { translate } from '../../base/i18n/functions';
- import { Video } from '../../base/media/components/index';
- import { equals } from '../../base/redux/functions';
- import { createLocalTracksF } from '../../base/tracks/functions';
- import Spinner from '../../base/ui/components/web/Spinner';
- import { showWarningNotification } from '../../notifications/actions';
- import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
- import { toggleBackgroundEffect } from '../actions';
- import logger from '../logger';
-
- /**
- * The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
- */
- export interface IProps extends WithTranslation {
-
- /**
- * An object containing the CSS classes.
- */
- classes: any;
-
- /**
- * The redux {@code dispatch} function.
- */
- dispatch: IStore['dispatch'];
-
- /**
- * Dialog callback that indicates if the background preview was loaded.
- */
- loadedPreview: Function;
-
- /**
- * Represents the virtual background set options.
- */
- options: any;
-
- /**
- * The id of the selected video device.
- */
- selectedVideoInputId: string;
- }
-
- /**
- * The type of the React {@code Component} state of {@link VirtualBackgroundPreview}.
- */
- interface IState {
-
- /**
- * Activate the selected device camera only.
- */
- jitsiTrack: Object | null;
-
- /**
- * Loader activated on setting virtual background.
- */
- loading: boolean;
-
- /**
- * Flag that indicates if the local track was loaded.
- */
- localTrackLoaded: boolean;
- }
-
- /**
- * Creates the styles for the component.
- *
- * @param {Object} theme - The current UI theme.
- *
- * @returns {Object}
- */
- const styles = (theme: Theme) => {
- return {
- virtualBackgroundPreview: {
- height: 'auto',
- width: '100%',
- overflow: 'hidden',
- marginBottom: theme.spacing(3),
- zIndex: 2,
- borderRadius: '3px',
- backgroundColor: theme.palette.uiBackground,
- position: 'relative' as const
- },
-
- previewLoader: {
- height: '220px',
-
- '& svg': {
- position: 'absolute' as const,
- top: '40%',
- left: '45%'
- }
- },
-
- previewVideo: {
- height: '100%',
- width: '100%',
- objectFit: 'cover' as const
- },
-
- error: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '100%',
- height: '220px',
- position: 'relative' as const
- }
- };
- };
-
- /**
- * Implements a React {@link PureComponent} which displays the virtual
- * background preview.
- *
- * @augments PureComponent
- */
- class VirtualBackgroundPreview extends PureComponent<IProps, IState> {
- _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: IProps) {
- super(props);
-
- this.state = {
- loading: false,
- localTrackLoaded: false,
- jitsiTrack: null
- };
- }
-
- /**
- * Destroys the jitsiTrack object.
- *
- * @param {Object} jitsiTrack - The track that needs to be disposed.
- * @returns {Promise<void>}
- */
- _stopStream(jitsiTrack: any) {
- if (jitsiTrack) {
- jitsiTrack.dispose();
- }
- }
-
- /**
- * Creates and updates the track data.
- *
- * @returns {void}
- */
- async _setTracks() {
- try {
- this.setState({ loading: true });
- const [ jitsiTrack ] = await createLocalTracksF({
- cameraDeviceId: this.props.selectedVideoInputId,
- 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);
-
- 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.'
- }, NOTIFICATION_TIMEOUT_TYPE.LONG)
- );
- logger.error('Failed to access camera device. Error on apply background effect.');
-
- return;
- }
- }
-
- /**
- * Apply background effect on video preview.
- *
- * @returns {Promise}
- */
- 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 });
- }
-
- /**
- * Apply video preview loader.
- *
- * @returns {Promise}
- */
- _loadVideoPreview() {
- return (
- <div className = { this.props.classes.previewLoader }>
- <Spinner size = 'large' />
- </div>
- );
- }
-
- /**
- * Renders a preview entry.
- *
- * @param {Object} data - The track data.
- * @returns {React$Node}
- */
- _renderPreviewEntry(data: Object) {
- const { classes, t } = this.props;
-
- if (this.state.loading) {
- return this._loadVideoPreview();
- }
- if (!data) {
- return (
- <div className = { classes.error }>{t('deviceSelection.previewUnavailable')}</div>
- );
- }
-
- return (
- <Video
- className = { classes.previewVideo }
- playsinline = { true }
- videoTrack = {{ jitsiTrack: data }} />
- );
- }
-
- /**
- * Implements React's {@link Component#componentDidMount}.
- *
- * @inheritdoc
- */
- componentDidMount() {
- this._setTracks();
- }
-
- /**
- * Implements React's {@link Component#componentWillUnmount}.
- *
- * @inheritdoc
- */
- componentWillUnmount() {
- this._componentWasUnmounted = true;
- this._stopStream(this.state.jitsiTrack);
- }
-
- /**
- * Implements React's {@link Component#componentDidUpdate}.
- *
- * @inheritdoc
- */
- async componentDidUpdate(prevProps: IProps) {
- if (!equals(this.props.selectedVideoInputId, prevProps.selectedVideoInputId)) {
- this._setTracks();
- }
- if (!equals(this.props.options, prevProps.options) && this.state.localTrackLoaded) {
- this._applyBackgroundEffect();
- }
- }
-
- /**
- * Implements React's {@link Component#render}.
- *
- * @inheritdoc
- */
- render() {
- const { jitsiTrack } = this.state;
- const { classes } = this.props;
-
- return (
- <div className = { classes.virtualBackgroundPreview }>
- {jitsiTrack
- ? this._renderPreviewEntry(jitsiTrack)
- : this._loadVideoPreview()
- }</div>
- );
- }
- }
-
- export default translate(connect()(withStyles(styles)(VirtualBackgroundPreview)));
|