mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-10 21:02:30 +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
388 lines
12 KiB
TypeScript
388 lines
12 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 logger from '../logger';
|
|
|
|
import AudioInputPreview from './AudioInputPreview';
|
|
import AudioOutputPreview from './AudioOutputPreview';
|
|
import DeviceHidContainer from './DeviceHidContainer.web';
|
|
import DeviceSelector from './DeviceSelector.web';
|
|
|
|
/**
|
|
* The type of the React {@code Component} props of {@link AudioDevicesSelection}.
|
|
*/
|
|
interface IProps extends AbstractDialogTabProps, WithTranslation {
|
|
|
|
/**
|
|
* All known audio and video devices split by type. This prop comes from
|
|
* the app state.
|
|
*/
|
|
availableDevices: {
|
|
audioInput?: MediaDeviceInfo[];
|
|
audioOutput?: MediaDeviceInfo[];
|
|
};
|
|
|
|
/**
|
|
* CSS classes object.
|
|
*/
|
|
classes: any;
|
|
|
|
/**
|
|
* Whether or not the audio selector can be interacted with. If true,
|
|
* the audio input selector will be rendered as disabled. This is
|
|
* specifically used to prevent audio device changing in Firefox, which
|
|
* currently does not work due to a browser-side regression.
|
|
*/
|
|
disableAudioInputChange: boolean;
|
|
|
|
/**
|
|
* True if device changing is configured to be disallowed. Selectors
|
|
* will display as disabled.
|
|
*/
|
|
disableDeviceChange: boolean;
|
|
|
|
/**
|
|
* Redux dispatch function.
|
|
*/
|
|
dispatch: IStore['dispatch'];
|
|
|
|
/**
|
|
* Whether or not the audio permission was granted.
|
|
*/
|
|
hasAudioPermission: boolean;
|
|
|
|
/**
|
|
* If true, the audio meter will not display. Necessary for browsers or
|
|
* configurations that do not support local stats to prevent a
|
|
* non-responsive mic preview from displaying.
|
|
*/
|
|
hideAudioInputPreview: boolean;
|
|
|
|
/**
|
|
* If true, the button to play a test sound on the selected speaker will not be displayed.
|
|
* This needs to be hidden on browsers that do not support selecting an audio output device.
|
|
*/
|
|
hideAudioOutputPreview: boolean;
|
|
|
|
/**
|
|
* Whether or not the audio output source selector should display. If
|
|
* true, the audio output selector and test audio link will not be
|
|
* rendered.
|
|
*/
|
|
hideAudioOutputSelect: boolean;
|
|
|
|
/**
|
|
* Whether or not the hid device container should display.
|
|
*/
|
|
hideDeviceHIDContainer: boolean;
|
|
|
|
/**
|
|
* Whether to hide noise suppression checkbox or not.
|
|
*/
|
|
hideNoiseSuppression: boolean;
|
|
|
|
/**
|
|
* Wether noise suppression is on or not.
|
|
*/
|
|
noiseSuppressionEnabled: boolean;
|
|
|
|
/**
|
|
* The id of the audio input device to preview.
|
|
*/
|
|
selectedAudioInputId: string;
|
|
|
|
/**
|
|
* The id of the audio output device to preview.
|
|
*/
|
|
selectedAudioOutputId: string;
|
|
}
|
|
|
|
/**
|
|
* The type of the React {@code Component} state of {@link AudioDevicesSelection}.
|
|
*/
|
|
type State = {
|
|
|
|
/**
|
|
* The JitsiTrack to use for previewing audio input.
|
|
*/
|
|
previewAudioTrack?: any | null;
|
|
};
|
|
|
|
const styles = (theme: Theme) => {
|
|
return {
|
|
container: {
|
|
display: 'flex',
|
|
flexDirection: 'column' as const,
|
|
padding: '0 2px',
|
|
width: '100%'
|
|
},
|
|
|
|
inputContainer: {
|
|
marginBottom: theme.spacing(3)
|
|
},
|
|
|
|
outputContainer: {
|
|
margin: `${theme.spacing(5)} 0`,
|
|
display: 'flex',
|
|
alignItems: 'flex-end'
|
|
},
|
|
|
|
outputButton: {
|
|
marginLeft: theme.spacing(3)
|
|
},
|
|
|
|
noiseSuppressionContainer: {
|
|
marginBottom: theme.spacing(5)
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* React {@code Component} for previewing audio and video input/output devices.
|
|
*
|
|
* @augments Component
|
|
*/
|
|
class AudioDevicesSelection 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 = {
|
|
previewAudioTrack: null
|
|
};
|
|
this._unMounted = true;
|
|
}
|
|
|
|
/**
|
|
* Generate the initial previews for audio input and video input.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentDidMount() {
|
|
this._unMounted = false;
|
|
Promise.all([
|
|
this._createAudioInputTrack(this.props.selectedAudioInputId)
|
|
])
|
|
.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.selectedAudioInputId
|
|
!== this.props.selectedAudioInputId) {
|
|
this._createAudioInputTrack(this.props.selectedAudioInputId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure preview tracks are destroyed to prevent continued use.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentWillUnmount() {
|
|
this._unMounted = true;
|
|
this._disposeAudioInputPreview();
|
|
}
|
|
|
|
/**
|
|
* Implements React's {@link Component#render()}.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
render() {
|
|
const {
|
|
classes,
|
|
hasAudioPermission,
|
|
hideAudioInputPreview,
|
|
hideAudioOutputPreview,
|
|
hideDeviceHIDContainer,
|
|
hideNoiseSuppression,
|
|
noiseSuppressionEnabled,
|
|
selectedAudioOutputId,
|
|
t
|
|
} = this.props;
|
|
const { audioInput, audioOutput } = this._getSelectors();
|
|
|
|
return (
|
|
<div className = { classes.container }>
|
|
<div
|
|
aria-live = 'polite'
|
|
className = { classes.inputContainer }>
|
|
{this._renderSelector(audioInput)}
|
|
</div>
|
|
{!hideAudioInputPreview && hasAudioPermission
|
|
&& <AudioInputPreview
|
|
track = { this.state.previewAudioTrack } />}
|
|
<div
|
|
aria-live = 'polite'
|
|
className = { classes.outputContainer }>
|
|
{this._renderSelector(audioOutput)}
|
|
{!hideAudioOutputPreview && hasAudioPermission
|
|
&& <AudioOutputPreview
|
|
className = { classes.outputButton }
|
|
deviceId = { selectedAudioOutputId } />}
|
|
</div>
|
|
{!hideNoiseSuppression && (
|
|
<div className = { classes.noiseSuppressionContainer }>
|
|
<Checkbox
|
|
checked = { noiseSuppressionEnabled }
|
|
label = { t('toolbar.enableNoiseSuppression') }
|
|
// eslint-disable-next-line react/jsx-no-bind
|
|
onChange = { () => super._onChange({
|
|
noiseSuppressionEnabled: !noiseSuppressionEnabled
|
|
}) } />
|
|
</div>
|
|
)}
|
|
{!hideDeviceHIDContainer
|
|
&& <DeviceHidContainer />}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates the JitsiTrack for the audio input preview.
|
|
*
|
|
* @param {string} deviceId - The id of audio input device to preview.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_createAudioInputTrack(deviceId: string) {
|
|
const { hideAudioInputPreview } = this.props;
|
|
|
|
if (hideAudioInputPreview) {
|
|
return;
|
|
}
|
|
|
|
return this._disposeAudioInputPreview()
|
|
.then(() => createLocalTrack('audio', deviceId, 5000))
|
|
.then(jitsiLocalTrack => {
|
|
if (this._unMounted) {
|
|
jitsiLocalTrack.dispose();
|
|
|
|
return;
|
|
}
|
|
|
|
this.setState({
|
|
previewAudioTrack: jitsiLocalTrack
|
|
});
|
|
})
|
|
.catch(() => {
|
|
this.setState({
|
|
previewAudioTrack: null
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Utility function for disposing the current audio input preview.
|
|
*
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_disposeAudioInputPreview(): Promise<any> {
|
|
return this.state.previewAudioTrack
|
|
? this.state.previewAudioTrack.dispose() : Promise.resolve();
|
|
}
|
|
|
|
/**
|
|
* Creates a DeviceSelector instance based on the passed in configuration.
|
|
*
|
|
* @private
|
|
* @param {Object} deviceSelectorProps - The props for the DeviceSelector.
|
|
* @returns {ReactElement}
|
|
*/
|
|
_renderSelector(deviceSelectorProps: any) {
|
|
return deviceSelectorProps ? (
|
|
<DeviceSelector
|
|
{ ...deviceSelectorProps }
|
|
key = { deviceSelectorProps.id } />
|
|
) : null;
|
|
}
|
|
|
|
/**
|
|
* Returns object configurations for audio input and output.
|
|
*
|
|
* @private
|
|
* @returns {Object} Configurations.
|
|
*/
|
|
_getSelectors() {
|
|
const { availableDevices, hasAudioPermission } = this.props;
|
|
|
|
const audioInput = {
|
|
devices: availableDevices.audioInput,
|
|
hasPermission: hasAudioPermission,
|
|
icon: 'icon-microphone',
|
|
isDisabled: this.props.disableAudioInputChange || this.props.disableDeviceChange,
|
|
key: 'audioInput',
|
|
id: 'audioInput',
|
|
label: 'settings.selectMic',
|
|
onSelect: (selectedAudioInputId: string) => super._onChange({ selectedAudioInputId }),
|
|
selectedDeviceId: this.state.previewAudioTrack
|
|
? this.state.previewAudioTrack.getDeviceId() : this.props.selectedAudioInputId
|
|
};
|
|
let audioOutput;
|
|
|
|
if (!this.props.hideAudioOutputSelect) {
|
|
audioOutput = {
|
|
devices: availableDevices.audioOutput,
|
|
hasPermission: hasAudioPermission,
|
|
icon: 'icon-speaker',
|
|
isDisabled: this.props.disableDeviceChange,
|
|
key: 'audioOutput',
|
|
id: 'audioOutput',
|
|
label: 'settings.selectAudioOutput',
|
|
onSelect: (selectedAudioOutputId: string) => super._onChange({ selectedAudioOutputId }),
|
|
selectedDeviceId: this.props.selectedAudioOutputId
|
|
};
|
|
}
|
|
|
|
return { audioInput,
|
|
audioOutput };
|
|
}
|
|
}
|
|
|
|
const mapStateToProps = (state: IReduxState) => {
|
|
return {
|
|
availableDevices: state['features/base/devices'].availableDevices ?? {}
|
|
};
|
|
};
|
|
|
|
export default connect(mapStateToProps)(withStyles(styles)(translate(AudioDevicesSelection)));
|