123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- 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}.
- */
- interface IState {
-
- /**
- * 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, IState> {
-
- /**
- * 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') }
- id = 'more-framerate-select'
- 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)));
|