mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-11 18:42:29 +00:00
Create separate tabs for Audio and Video in the Settings Dialog Move some settings from the More tab to Audio/ Video tab Implement redesign Convert some files to TS Move some styles from SCSS to JSS Enable device selection on welcome page
369 lines
11 KiB
TypeScript
369 lines
11 KiB
TypeScript
import { Theme } from '@mui/material';
|
|
import { withStyles } from '@mui/styles';
|
|
import React from 'react';
|
|
import { WithTranslation } from 'react-i18next';
|
|
import { connect } from 'react-redux';
|
|
|
|
import { IReduxState, IStore } from '../../app/types';
|
|
import { getAvailableDevices } from '../../base/devices/actions.web';
|
|
import AbstractDialogTab, {
|
|
type IProps as AbstractDialogTabProps
|
|
} from '../../base/dialog/components/web/AbstractDialogTab';
|
|
import { translate } from '../../base/i18n/functions';
|
|
import { createLocalTrack } from '../../base/lib-jitsi-meet/functions.web';
|
|
import Checkbox from '../../base/ui/components/web/Checkbox';
|
|
import Select from '../../base/ui/components/web/Select';
|
|
import { SS_DEFAULT_FRAME_RATE } from '../../settings/constants';
|
|
import logger from '../logger';
|
|
|
|
import DeviceSelector from './DeviceSelector.web';
|
|
import VideoInputPreview from './VideoInputPreview';
|
|
|
|
/**
|
|
* The type of the React {@code Component} props of {@link VideoDeviceSelection}.
|
|
*/
|
|
export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
|
|
|
/**
|
|
* All known audio and video devices split by type. This prop comes from
|
|
* the app state.
|
|
*/
|
|
availableDevices: { videoInput?: MediaDeviceInfo[]; };
|
|
|
|
/**
|
|
* CSS classes object.
|
|
*/
|
|
classes: any;
|
|
|
|
/**
|
|
* The currently selected desktop share frame rate in the frame rate select dropdown.
|
|
*/
|
|
currentFramerate: string;
|
|
|
|
/**
|
|
* All available desktop capture frame rates.
|
|
*/
|
|
desktopShareFramerates: Array<number>;
|
|
|
|
/**
|
|
* True if device changing is configured to be disallowed. Selectors
|
|
* will display as disabled.
|
|
*/
|
|
disableDeviceChange: boolean;
|
|
|
|
/**
|
|
* Whether video input dropdown should be enabled or not.
|
|
*/
|
|
disableVideoInputSelect: boolean;
|
|
|
|
/**
|
|
* Redux dispatch.
|
|
*/
|
|
dispatch: IStore['dispatch'];
|
|
|
|
/**
|
|
* Whether or not the audio permission was granted.
|
|
*/
|
|
hasVideoPermission: boolean;
|
|
|
|
/**
|
|
* Whether to hide the additional settings or not.
|
|
*/
|
|
hideAdditionalSettings: boolean;
|
|
|
|
/**
|
|
* Whether video input preview should be displayed or not.
|
|
* (In the case of iOS Safari).
|
|
*/
|
|
hideVideoInputPreview: boolean;
|
|
|
|
/**
|
|
* Whether or not the local video is flipped.
|
|
*/
|
|
localFlipX: boolean;
|
|
|
|
/**
|
|
* The id of the video input device to preview.
|
|
*/
|
|
selectedVideoInputId: string;
|
|
}
|
|
|
|
/**
|
|
* The type of the React {@code Component} state of {@link VideoDeviceSelection}.
|
|
*/
|
|
type State = {
|
|
|
|
/**
|
|
* The JitsiTrack to use for previewing video input.
|
|
*/
|
|
previewVideoTrack: any | null;
|
|
|
|
/**
|
|
* The error message from trying to use a video input device.
|
|
*/
|
|
previewVideoTrackError: string | null;
|
|
};
|
|
|
|
const styles = (theme: Theme) => {
|
|
return {
|
|
container: {
|
|
display: 'flex',
|
|
flexDirection: 'column' as const,
|
|
padding: '0 2px',
|
|
width: '100%'
|
|
},
|
|
|
|
checkboxContainer: {
|
|
margin: `${theme.spacing(4)} 0`
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* React {@code Component} for previewing audio and video input/output devices.
|
|
*
|
|
* @augments Component
|
|
*/
|
|
class VideoDeviceSelection extends AbstractDialogTab<IProps, State> {
|
|
|
|
/**
|
|
* Whether current component is mounted or not.
|
|
*
|
|
* In component did mount we start a Promise to create tracks and
|
|
* set the tracks in the state, if we unmount the component in the meanwhile
|
|
* tracks will be created and will never been disposed (dispose tracks is
|
|
* in componentWillUnmount). When tracks are created and component is
|
|
* unmounted we dispose the tracks.
|
|
*/
|
|
_unMounted: boolean;
|
|
|
|
/**
|
|
* Initializes a new DeviceSelection instance.
|
|
*
|
|
* @param {Object} props - The read-only React Component props with which
|
|
* the new instance is to be initialized.
|
|
*/
|
|
constructor(props: IProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
previewVideoTrack: null,
|
|
previewVideoTrackError: null
|
|
};
|
|
this._unMounted = true;
|
|
|
|
this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Generate the initial previews for audio input and video input.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentDidMount() {
|
|
this._unMounted = false;
|
|
Promise.all([
|
|
this._createVideoInputTrack(this.props.selectedVideoInputId)
|
|
])
|
|
.catch(err => logger.warn('Failed to initialize preview tracks', err))
|
|
.then(() => {
|
|
this.props.dispatch(getAvailableDevices());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks if audio / video permissions were granted. Updates audio input and
|
|
* video input previews.
|
|
*
|
|
* @param {Object} prevProps - Previous props this component received.
|
|
* @returns {void}
|
|
*/
|
|
componentDidUpdate(prevProps: IProps) {
|
|
|
|
if (prevProps.selectedVideoInputId
|
|
!== this.props.selectedVideoInputId) {
|
|
this._createVideoInputTrack(this.props.selectedVideoInputId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure preview tracks are destroyed to prevent continued use.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentWillUnmount() {
|
|
this._unMounted = true;
|
|
this._disposeVideoInputPreview();
|
|
}
|
|
|
|
/**
|
|
* Implements React's {@link Component#render()}.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
render() {
|
|
const {
|
|
classes,
|
|
hideAdditionalSettings,
|
|
hideVideoInputPreview,
|
|
localFlipX,
|
|
t
|
|
} = this.props;
|
|
|
|
return (
|
|
<div className = { classes.container }>
|
|
{ !hideVideoInputPreview
|
|
&& <VideoInputPreview
|
|
error = { this.state.previewVideoTrackError }
|
|
localFlipX = { localFlipX }
|
|
track = { this.state.previewVideoTrack } />
|
|
}
|
|
<div
|
|
aria-live = 'polite'>
|
|
{this._renderVideoSelector()}
|
|
</div>
|
|
{!hideAdditionalSettings && (
|
|
<>
|
|
<div className = { classes.checkboxContainer }>
|
|
<Checkbox
|
|
checked = { localFlipX }
|
|
label = { t('videothumbnail.mirrorVideo') }
|
|
// eslint-disable-next-line react/jsx-no-bind
|
|
onChange = { () => super._onChange({ localFlipX: !localFlipX }) } />
|
|
</div>
|
|
{this._renderFramerateSelect()}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates the JitsiTrack for the video input preview.
|
|
*
|
|
* @param {string} deviceId - The id of video device to preview.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_createVideoInputTrack(deviceId: string) {
|
|
const { hideVideoInputPreview } = this.props;
|
|
|
|
if (hideVideoInputPreview) {
|
|
return;
|
|
}
|
|
|
|
return this._disposeVideoInputPreview()
|
|
.then(() => createLocalTrack('video', deviceId, 5000))
|
|
.then(jitsiLocalTrack => {
|
|
if (!jitsiLocalTrack) {
|
|
return Promise.reject();
|
|
}
|
|
|
|
if (this._unMounted) {
|
|
jitsiLocalTrack.dispose();
|
|
|
|
return;
|
|
}
|
|
|
|
this.setState({
|
|
previewVideoTrack: jitsiLocalTrack,
|
|
previewVideoTrackError: null
|
|
});
|
|
})
|
|
.catch(() => {
|
|
this.setState({
|
|
previewVideoTrack: null,
|
|
previewVideoTrackError:
|
|
this.props.t('deviceSelection.previewUnavailable')
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Utility function for disposing the current video input preview.
|
|
*
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_disposeVideoInputPreview(): Promise<any> {
|
|
return this.state.previewVideoTrack
|
|
? this.state.previewVideoTrack.dispose() : Promise.resolve();
|
|
}
|
|
|
|
/**
|
|
* Creates a DeviceSelector instance based on the passed in configuration.
|
|
*
|
|
* @private
|
|
* @returns {ReactElement}
|
|
*/
|
|
_renderVideoSelector() {
|
|
const { availableDevices, hasVideoPermission } = this.props;
|
|
|
|
const videoConfig = {
|
|
devices: availableDevices.videoInput,
|
|
hasPermission: hasVideoPermission,
|
|
icon: 'icon-camera',
|
|
isDisabled: this.props.disableVideoInputSelect || this.props.disableDeviceChange,
|
|
key: 'videoInput',
|
|
id: 'videoInput',
|
|
label: 'settings.selectCamera',
|
|
onSelect: (selectedVideoInputId: string) => super._onChange({ selectedVideoInputId }),
|
|
selectedDeviceId: this.state.previewVideoTrack
|
|
? this.state.previewVideoTrack.getDeviceId() : this.props.selectedVideoInputId
|
|
};
|
|
|
|
return (
|
|
<DeviceSelector
|
|
{ ...videoConfig }
|
|
key = { videoConfig.id } />
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Callback invoked to select a frame rate from the select dropdown.
|
|
*
|
|
* @param {Object} e - The key event to handle.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onFramerateItemSelect(e: React.ChangeEvent<HTMLSelectElement>) {
|
|
const frameRate = e.target.value;
|
|
|
|
super._onChange({ currentFramerate: frameRate });
|
|
}
|
|
|
|
/**
|
|
* Returns the React Element for the desktop share frame rate dropdown.
|
|
*
|
|
* @returns {JSX}
|
|
*/
|
|
_renderFramerateSelect() {
|
|
const { currentFramerate, desktopShareFramerates, t } = this.props;
|
|
const frameRateItems = desktopShareFramerates.map((frameRate: number) => {
|
|
return {
|
|
value: frameRate,
|
|
label: `${frameRate} ${t('settings.framesPerSecond')}`
|
|
};
|
|
});
|
|
|
|
return (
|
|
<Select
|
|
bottomLabel = { parseInt(currentFramerate, 10) > SS_DEFAULT_FRAME_RATE
|
|
? t('settings.desktopShareHighFpsWarning')
|
|
: t('settings.desktopShareWarning') }
|
|
label = { t('settings.desktopShareFramerate') }
|
|
onChange = { this._onFramerateItemSelect }
|
|
options = { frameRateItems }
|
|
value = { currentFramerate } />
|
|
);
|
|
}
|
|
}
|
|
|
|
const mapStateToProps = (state: IReduxState) => {
|
|
return {
|
|
availableDevices: state['features/base/devices'].availableDevices ?? {}
|
|
};
|
|
};
|
|
|
|
export default connect(mapStateToProps)(withStyles(styles)(translate(VideoDeviceSelection)));
|