2017-06-08_509bba0/509bba0_unpacked_with_node_.../discord_app/components/user_settings/UserSettingsVoice.js

799 lines
25 KiB
JavaScript
Executable File

import React from 'react';
import Flux from '../../lib/flux';
import {Timeout} from '../../lib/timers';
import classNames from 'classnames';
import {MediaEngineFeatures, supportedMediaEngineImpl} from '../../lib/MediaEngine';
import MediaEngineStore from '../../stores/MediaEngineStore';
import SpeakingStore from '../../stores/SpeakingStore';
import UserSettingsStore from '../../stores/UserSettingsStore';
import VideoCallExperimentStore from '../../stores/experiments/VideoCallExperimentStore';
import ExperimentStore from '../../stores/ExperimentStore';
import AudioActionCreators from '../../actions/AudioActionCreators';
import ModalActionCreators from '../../actions/ModalActionCreators';
import UserSettingsModalActionCreators from '../../actions/UserSettingsModalActionCreators';
import DownloadApps from '../DownloadApps';
import NativeUtils from '../../utils/NativeUtils';
import i18n from '../../i18n';
import lodash from 'lodash';
import FormSection from '../../uikit/form/FormSection';
import FormNotice, {Types as FormNoticeTypes} from '../../uikit/form/FormNotice';
import Flex from '../../uikit/Flex';
import FormItem from '../../uikit/form/FormItem';
import FormDivider from '../../uikit/form/FormDivider';
import FormText from '../../uikit/form/FormText';
import Switch from '../../uikit/Switch';
import Select from '../../uikit/Select';
import Slider from '../../uikit/Slider';
import RadioGroup from '../../uikit/RadioGroup';
import KeyRecorder from '../common/KeyRecorder';
import SwitchItem from '../../uikit/SwitchItem';
import FormTitle, {Tags} from '../../uikit/form/FormTitle';
import Button, {ButtonLooks, ButtonColors, ButtonSizes} from '../../uikit/Button';
import Alert from '../Alert';
import ConfirmModal from '../ConfirmModal';
import {InputModes, MAX_PTT_RELEASE_DELAY, ThemeTypes, UserSettingsSections, ExperimentTypes} from '../../Constants';
import '../../styles/user_settings_voice.styl';
class VoiceSensitivity extends React.PureComponent {
_timeout = new Timeout();
constructor(props) {
super(props);
this.state = {
volume: -100,
};
lodash.bindAll(this, [
'setupVoiceActivity',
'handleVoiceActivity',
'handleAutoThresholdChange',
'handleSensitivityChange',
'handleValueRender',
]);
}
componentDidMount() {
// Delay voice activity to allow many required rendering to take place.
this._timeout.start(1000, this.setupVoiceActivity);
}
componentWillUnmount() {
MediaEngineStore.getMediaEngine().removeListener('voiceactivity', this.handleVoiceActivity);
this._timeout.stop();
}
setupVoiceActivity() {
MediaEngineStore.getMediaEngine().on('voiceactivity', this.handleVoiceActivity);
this._timeout.stop();
}
handleVoiceActivity(volume) {
this.setState({volume});
}
handleAutoThresholdChange(e) {
const {onThresholdChange, threshold} = this.props;
onThresholdChange && onThresholdChange(threshold, e.target.checked);
}
handleSensitivityChange(value) {
const {onThresholdChange, auto} = this.props;
onThresholdChange && onThresholdChange((100 - value) * -1, auto);
}
handleValueRender(v) {
return `${((100 - v) * -1).toFixed(0)}dB`;
}
renderAutomaticVADToggle() {
const {auto} = this.props;
if (MediaEngineStore.supports(MediaEngineFeatures.AUTOMATIC_VAD)) {
return (
<div className="flex-horizontal margin-bottom-4">
<FormTitle tag={Tags.H3} className="margin-reset">{i18n.Messages.FORM_LABEL_AUTOMATIC_VAD}</FormTitle>
<Switch value={auto} onChange={this.handleAutoThresholdChange} />
</div>
);
}
}
renderSlider() {
const {auto, threshold, speaking} = this.props;
if (auto) {
return (
<section className="input-sensitivity-toggle">
<div className="ui-slider">
<div className={classNames('slider-bar', {speaking})} />
</div>
<FormText type={FormText.Types.DESCRIPTION} className="input-sensitivity-note margin-bottom-8">
{i18n.Messages.FORM_HELP_AUTOMATIC_VAD}
</FormText>
</section>
);
} else {
return (
<section className="input-sensitivity-toggle manual">
<Slider
defaultValue={threshold + 100}
onValueRender={this.handleValueRender}
onValueChange={this.handleSensitivityChange}>
<div className="slider-bar microphone">
<div className="fill" style={{width: this.state.volume + 100 + '%'}} />
<div className="grow" />
</div>
</Slider>
</section>
);
}
}
renderInputDisabledWarning() {
if (!MediaEngineStore.isEnabled()) {
return (
<FormText type={FormText.Types.DESCRIPTION} className="input-disabled-warning margin-bottom-8">
{i18n.Messages.FORM_WARNING_INPUT_SENSITIVTY.format({onEnableClick: AudioActionCreators.enable})}
</FormText>
);
}
}
render() {
return (
<FormItem className="sensitivity">
<FormTitle tag={Tags.H5} className="margin-bottom-8">{i18n.Messages.FORM_LABEL_INPUT_SENSITIVTY}</FormTitle>
<div>
{this.renderAutomaticVADToggle()}
{this.renderSlider()}
</div>
{this.renderInputDisabledWarning()}
</FormItem>
);
}
}
const VoiceSensitivityConnected = Flux.connectStores([SpeakingStore], () => {
return {
speaking: SpeakingStore.isCurrentUserSpeaking(),
};
})(VoiceSensitivity);
class UserSettingsVoice extends React.PureComponent {
constructor(props) {
super(props);
lodash.bindAll(this, [
'handleInputDeviceChange',
'handleOutputDeviceChange',
'handleInputVolumeChange',
'handleOutputVolumeChange',
'handleOutputRender',
'handleInputModeChange',
'handleDelayChange',
'handleThresholdChange',
'handleInputModeChange',
'handleVideoDeviceChange',
'handleEchoCancellationChange',
'handleNoiseSuppressionChange',
'handleAutomaticGainControlChange',
'handleSilenceWarningChange',
'handleQoSChanged',
'handleMediaEngineChange',
'handleAttenuationChange',
'handleAttenuateSelfChanged',
'handleAttenuateOthersChanged',
'handleVoiceReset',
'handleSubsystemChanged',
'handleShortcutChange',
]);
}
handleInputDeviceChange({value}) {
AudioActionCreators.setInputDevice(value);
}
handleOutputDeviceChange({value}) {
AudioActionCreators.setOutputDevice(value);
}
handleDownload(source) {
ModalActionCreators.push(DownloadApps, {source});
}
handleInputVolumeChange(inputVolume) {
AudioActionCreators.setInputVolume(inputVolume);
}
handleOutputVolumeChange(value) {
const outputVolume = value * 2;
AudioActionCreators.setOutputVolume(outputVolume);
}
handleOutputRender(v) {
return `${(v * 2).toFixed(0)}%`;
}
handleInputModeChange({value: inputMode}) {
if (inputMode === InputModes.PUSH_TO_TALK && this.props.usePTTLimited) {
ModalActionCreators.push(props => {
return (
<Alert
title={i18n.Messages.PTT_LIMITED_TITLE}
confirmText={i18n.Messages.DOWNLOAD}
cancelText={i18n.Messages.OKAY}
onConfirm={this.handleDownload.bind(this, 'PTT Limited Modal')}
body={i18n.Messages.PTT_LIMITED_BODY}
iconUrl={require('../../images/push_to_talk.svg')}
{...props}
/>
);
});
}
this.setMode(inputMode);
}
handleDelayChange(delay) {
this.setMode(this.props.inputMode, {delay});
}
handleThresholdChange(threshold, autoThreshold) {
this.setMode(this.props.inputMode, {threshold, autoThreshold});
}
handleDelayValueRender(delay) {
if (delay >= 1000) {
delay /= 1000;
return `${delay.toFixed(2)}s`;
}
return `${delay.toFixed(0)} ms`;
}
handleVideoDeviceChange({value}) {
AudioActionCreators.setVideoDevice(value);
}
handleEchoCancellationChange(e) {
AudioActionCreators.setEchoCancellation(e.currentTarget.checked);
}
handleNoiseSuppressionChange(e) {
AudioActionCreators.setNoiseSuppression(e.currentTarget.checked);
}
handleAutomaticGainControlChange(e) {
AudioActionCreators.setAutomaticGainControl(e.currentTarget.checked);
}
handleSilenceWarningChange(e) {
AudioActionCreators.setSilenceWarning(e.currentTarget.checked);
}
handleQoSChanged(e) {
AudioActionCreators.setQoS(e.currentTarget.checked);
}
handleMediaEngineChange({value}) {
AudioActionCreators.setMediaEngine(value);
}
handleAttenuationChange(attenuation) {
AudioActionCreators.setAttenuation(
attenuation,
this.props.attenuateWhileSpeakingSelf,
this.props.attenuateWhileSpeakingOthers
);
}
handleAttenuateSelfChanged(e) {
AudioActionCreators.setAttenuation(
this.props.attenuation,
e.currentTarget.checked,
this.props.attenuateWhileSpeakingOthers
);
}
handleAttenuateOthersChanged(e) {
AudioActionCreators.setAttenuation(
this.props.attenuation,
this.props.attenuateWhileSpeakingSelf,
e.currentTarget.checked
);
}
handleVoiceReset() {
ModalActionCreators.push(props => {
return (
<ConfirmModal
header={i18n.Messages.RESET_VOICE_SETTINGS}
confirmText={i18n.Messages.OKAY}
cancelText={i18n.Messages.CANCEL}
onConfirm={AudioActionCreators.reset}
red={true}
{...props}>
<p>{i18n.Messages.RESET_VOICE_SETTINGS_BODY}</p>
</ConfirmModal>
);
});
}
handleSubsystemChanged() {
ModalActionCreators.push(props => {
return (
<ConfirmModal
header={i18n.Messages.SWITCH_SUBSYSTEM}
confirmText={i18n.Messages.OKAY}
cancelText={i18n.Messages.CANCEL}
onConfirm={AudioActionCreators.switchSubsystem}
red={true}
{...props}>
<p>{i18n.Messages.SWITCH_SUBSYSTEM_BODY}</p>
</ConfirmModal>
);
});
}
handleShortcutChange(shortcut) {
this.setMode(this.props.inputMode, {shortcut});
}
setMode(mode, options = {}) {
const {vadThreshold, vadAutoThreshold, shortcut, delay} = this.props;
AudioActionCreators.setMode(mode, {
threshold: vadThreshold,
autoThreshold: vadAutoThreshold,
shortcut,
delay,
...options,
});
}
renderDevices() {
const {
inputDevices,
inputDeviceId,
outputDeviceId,
outputDevices,
canSetInputDevice,
canSetOutputDevice,
} = this.props;
let inputWarning;
if (!canSetInputDevice) {
inputWarning = (
<FormText type={FormText.Types.DESCRIPTION} className="margin-top-8">
{i18n.Messages.BROWSER_INPUT_DEVICE_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text Input Devices'),
})}
</FormText>
);
}
let outputWarning;
if (!canSetOutputDevice) {
outputWarning = (
<FormText type={FormText.Types.DESCRIPTION} className="margin-top-8">
{i18n.Messages.BROWSER_OUTPUT_DEVICE_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text Output Devices'),
})}
</FormText>
);
}
const inputDevicesDisabled = lodash(inputDevices).values().first().disabled || inputWarning != null;
const outputDevicesDisabled = lodash(outputDevices).values().first().disabled || outputWarning != null;
return (
<Flex className="margin-bottom-20">
<Flex.Child>
<FormTitle tag={Tags.H5} className="margin-bottom-8">{i18n.Messages.FORM_LABEL_INPUT_DEVICE}</FormTitle>
<Select
value={inputDeviceId}
clearable={false}
searchable={false}
onChange={this.handleInputDeviceChange}
options={lodash.map(inputDevices, ({id: value, name: label}) => ({value, label}))}
disabled={inputDevicesDisabled}
/>
{inputWarning}
</Flex.Child>
<Flex.Child>
<FormTitle tag={Tags.H5} className="margin-bottom-8">{i18n.Messages.FORM_LABEL_OUTPUT_DEVICE}</FormTitle>
<Select
value={outputDeviceId}
clearable={false}
searchable={false}
onChange={this.handleOutputDeviceChange}
options={lodash.map(outputDevices, ({id: value, name: label}) => ({value, label}))}
disabled={outputDevicesDisabled}
/>
{outputWarning}
</Flex.Child>
</Flex>
);
}
renderVolumeControls() {
const {inputVolume, outputVolume} = this.props;
return (
<Flex className="volume">
<Flex.Child>
<FormTitle tag={Tags.H5} className="margin-bottom-4">{i18n.Messages.FORM_LABEL_INPUT_VOLUME}</FormTitle>
<Slider defaultValue={inputVolume} asValueChanges={this.handleInputVolumeChange} />
</Flex.Child>
<Flex.Child>
<FormTitle tag={Tags.H5} className="margin-bottom-4">{i18n.Messages.FORM_LABEL_OUTPUT_VOLUME}</FormTitle>
<Slider
defaultValue={outputVolume / 2}
onValueRender={this.handleOutputRender}
asValueChanges={this.handleOutputVolumeChange}
/>
</Flex.Child>
</Flex>
);
}
renderInputMode() {
const {usePTTLimited, inputMode} = this.props;
const inputModes = [
{
value: InputModes.VOICE_ACTIVITY,
name: i18n.Messages.INPUT_MODE_VAD,
},
{
value: InputModes.PUSH_TO_TALK,
name: usePTTLimited ? i18n.Messages.INPUT_MODE_PTT_LIMITED : i18n.Messages.INPUT_MODE_PTT,
},
];
return (
<FormItem title={i18n.Messages.FORM_LABEL_INPUT_MODE} className="margin-bottom-20">
<RadioGroup onChange={this.handleInputModeChange} options={inputModes} value={inputMode} />
</FormItem>
);
}
renderPTTTools() {
const {inputMode, delay, shortcut} = this.props;
if (inputMode !== InputModes.PUSH_TO_TALK) {
return null;
}
let secondaryMessage;
if (!NativeUtils.embedded && inputMode === InputModes.PUSH_TO_TALK) {
secondaryMessage = (
<FormText type={FormText.Types.DESCRIPTION} className="ptt-tools-message ptt-tools-warning margin-bottom-8">
{i18n.Messages.PTT_LIMITED_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text PTT'),
})}
</FormText>
);
} else {
secondaryMessage = (
<FormText type={FormText.Types.DESCRIPTION} className="ptt-tools-message margin-bottom-8">
{i18n.Messages.USER_SETTINGS_VOICE_ADD_MULTIPLE.format({
onClick: () => UserSettingsModalActionCreators.setSection(UserSettingsSections.KEYBINDS),
})}
</FormText>
);
}
return (
<div className="ptt-tools">
<div className="flex-horizontal">
<FormItem title={i18n.Messages.FORM_LABEL_SHORTCUT}>
<KeyRecorder defaultValue={shortcut} onChange={this.handleShortcutChange} />
</FormItem>
<FormItem title={i18n.Messages.INPUT_MODE_PTT_RELEASE_DELAY}>
<Slider
defaultValue={delay}
onValueChange={this.handleDelayChange}
onValueRender={this.handleDelayValueRender}
maxValue={MAX_PTT_RELEASE_DELAY}
/>
</FormItem>
</div>
{secondaryMessage}
</div>
);
}
renderVoiceSensitivityTools() {
const {inputMode, vadThreshold, vadAutoThreshold} = this.props;
if (inputMode !== InputModes.VOICE_ACTIVITY) {
return null;
}
return (
<VoiceSensitivityConnected
inputMode={inputMode}
threshold={vadThreshold}
auto={vadAutoThreshold}
onThresholdChange={this.handleThresholdChange}
/>
);
}
renderVideoDevices() {
const {videoDevices, videoDeviceId} = this.props;
let videoWarning;
if (!MediaEngineStore.isEnabled()) {
videoWarning = (
<FormText type={FormText.Types.DESCRIPTION} className="margin-top-8">
{i18n.Messages.FORM_WARNING_VIDEO_PREVIEW.format({onEnableClick: AudioActionCreators.enable})}
</FormText>
);
}
const videoDevicesDisabled = lodash(videoDevices).values().first().disabled || videoWarning != null;
const {Camera} = MediaEngineStore.getMediaEngine();
return (
<Flex>
<Flex.Child basis="50%">
<FormTitle tag={Tags.H5} className="margin-bottom-8">{i18n.Messages.FORM_LABEL_VIDEO_DEVICE}</FormTitle>
<Select
value={videoDeviceId}
clearable={false}
searchable={false}
disabled={videoDevicesDisabled}
onChange={this.handleVideoDeviceChange}
options={lodash.map(videoDevices, ({id: value, name: label}) => ({value, label}))}
/>
{videoWarning}
</Flex.Child>
<Flex.Child basis="50%">
<FormTitle tag={Tags.H5} className="margin-bottom-8">{i18n.Messages.FORM_LABEL_VIDEO_PREVIEW}</FormTitle>
<Camera deviceId={videoDeviceId} disabled={videoDevicesDisabled} />
</Flex.Child>
</Flex>
);
}
renderCodec() {
const {theme} = this.props;
let src;
if (theme === ThemeTypes.LIGHT) {
src = require('../../images/user_settings/img_opus_logo_light.png');
} else {
src = require('../../images/user_settings/img_opus_logo_dark.png');
}
return (
<FormNotice
className="margin-bottom-40"
type={FormNoticeTypes.PRIMARY}
imageData={{
src,
width: 70,
height: 40,
}}
title={i18n.Messages.USER_SETTINGS_VOICE_CODEC_TITLE}
body={i18n.Messages.USER_SETTINGS_VOICE_CODEC_DESCRIPTION}
/>
);
}
renderVoiceProcessing() {
if (!MediaEngineStore.supports(MediaEngineFeatures.VOICE_PROCESSING)) {
return null;
}
const {noiseSuppression, echoCancellation, automaticGainControl} = this.props;
return (
<FormSection className="margin-bottom-40" title={i18n.Messages.FORM_LABEL_VOICE_PROCESSING}>
<SwitchItem
className="margin-top-8 margin-bottom-20"
value={echoCancellation}
onChange={this.handleEchoCancellationChange}>
{i18n.Messages.ECHO_CANCELLATION}
</SwitchItem>
<SwitchItem value={noiseSuppression} onChange={this.handleNoiseSuppressionChange}>
{i18n.Messages.NOISE_SUPPRESSION}
</SwitchItem>
<SwitchItem className="" value={automaticGainControl} onChange={this.handleAutomaticGainControlChange}>
{i18n.Messages.AUTOMATIC_GAIN_CONTROL}
</SwitchItem>
</FormSection>
);
}
renderQoS() {
if (!MediaEngineStore.supports(MediaEngineFeatures.QOS)) {
return null;
}
const {qosEnabled} = this.props;
return (
<FormSection className="margin-bottom-40" title={i18n.Messages.FORM_LABEL_QOS}>
<SwitchItem className="" value={qosEnabled} onChange={this.handleQoSChanged} note={i18n.Messages.FORM_HELP_QOS}>
{i18n.Messages.FORM_CHECKBOX_QOS}
</SwitchItem>
</FormSection>
);
}
renderAttenutation() {
if (!MediaEngineStore.supports(MediaEngineFeatures.ATTENUATION)) {
return null;
}
const {attenuation, attenuateWhileSpeakingSelf, attenuateWhileSpeakingOthers} = this.props;
return (
<FormSection className="margin-bottom-40" title={i18n.Messages.FORM_LABEL_ATTENUATION}>
<Slider
defaultValue={attenuation}
onValueRender={this.handleOutputRender}
onValueChange={this.handleAttenuationChange}
/>
<FormText className="margin-bottom-20" type={FormText.Types.DESCRIPTION}>
{i18n.Messages.FORM_HELP_ATTENUATION}
</FormText>
<FormDivider lineOnly className="margin-bottom-20" />
<SwitchItem value={attenuateWhileSpeakingSelf} onChange={this.handleAttenuateSelfChanged}>
{i18n.Messages.ATTENUATE_WHILE_SPEAKING_SELF}
</SwitchItem>
<SwitchItem className="" value={attenuateWhileSpeakingOthers} onChange={this.handleAttenuateOthersChanged}>
{i18n.Messages.ATTENUATE_WHILE_SPEAKING_OTHERS}
</SwitchItem>
</FormSection>
);
}
renderSubsystemSettings() {
if (!MediaEngineStore.supports(MediaEngineFeatures.LEGACY_SUBSYSTEM)) {
return null;
}
const {legacySubsystemEnabled} = this.props;
return (
<FormSection className="margin-bottom-40" title={i18n.Messages.FORM_LABEL_SUBSYSTEM}>
<SwitchItem
className=""
value={legacySubsystemEnabled}
onChange={this.handleSubsystemChanged}
note={i18n.Messages.FORM_HELP_LEGACY_SUBSYSTEM}>
{i18n.Messages.FORM_CHECKBOX_LEGACY_SUBSYSTEM}
</SwitchItem>
</FormSection>
);
}
renderDiagnostics() {
if (!NativeUtils.embedded) {
return null;
}
const {silenceWarning} = this.props;
return (
<FormSection className="margin-bottom-40" title={i18n.Messages.FORM_LABEL_VOICE_DIAGNOSTICS}>
<SwitchItem className="" value={silenceWarning} onChange={this.handleSilenceWarningChange}>
{i18n.Messages.DISPLAY_SILENCE_WARNING}
</SwitchItem>
</FormSection>
);
}
renderResetVoiceSettings() {
return (
<FormItem>
<Button
className="reset-button"
look={ButtonLooks.OUTLINED}
color={ButtonColors.RED}
onClick={this.handleVoiceReset}
size={ButtonSizes.SMALL}>
{i18n.Messages.RESET_VOICE_SETTINGS}
</Button>
</FormItem>
);
}
renderMediaEngineImplGroup() {
if (process.env.NODE_ENV === 'development') {
const options = [];
for (const [impl, supported] of Object.entries(supportedMediaEngineImpl)) {
if (supported) {
options.push({
value: impl,
label: impl,
});
}
}
return (
<FormSection className="margin-bottom-40">
<FormTitle>{i18n.Messages.FORM_LABEL_MEDIA_ENGINE}</FormTitle>
<Select
value={this.props.mediaEngineImpl}
clearable={false}
searchable={false}
options={options}
onChange={this.handleMediaEngineChange}
/>
</FormSection>
);
}
}
renderVideoSettings() {
if (this.props.videoSettings) {
return (
<div>
<FormTitle tag={Tags.H2} className="margin-bottom-20">{i18n.Messages.VIDEO_SETTINGS}</FormTitle>
{this.renderVideoDevices()}
<FormDivider className="margin-bottom-40 margin-top-40" />
</div>
);
}
return null;
}
render() {
return (
<FormSection className="user-settings-voice" tag={Tags.H2} title={i18n.Messages.VOICE_SETTINGS}>
{this.renderDevices()}
{this.renderVolumeControls()}
<FormDivider className="margin-top-8 margin-bottom-40" />
{this.renderInputMode()}
{this.renderPTTTools()}
{this.renderVoiceSensitivityTools()}
<FormDivider className="margin-bottom-40" />
{this.renderVideoSettings()}
<FormTitle tag={Tags.H2} className="margin-bottom-20">{i18n.Messages.SETTINGS_ADVANCED}</FormTitle>
{this.renderCodec()}
{this.renderMediaEngineImplGroup()}
{this.renderVoiceProcessing()}
{this.renderQoS()}
{this.renderAttenutation()}
{this.renderSubsystemSettings()}
{this.renderDiagnostics()}
{this.renderResetVoiceSettings()}
</FormSection>
);
}
}
export default Flux.connectStores([UserSettingsStore, MediaEngineStore, VideoCallExperimentStore], () => {
let videoSettings = false;
const override = ExperimentStore.getOverrideExperimentDescriptor(VideoCallExperimentStore.getExperimentId());
if ((override != null && override.type === ExperimentTypes.DEVELOPER) || VideoCallExperimentStore.hasVideoCall()) {
videoSettings = true;
}
return {
theme: UserSettingsStore.theme,
canSetInputDevice: MediaEngineStore.supports(MediaEngineFeatures.AUDIO_INPUT_DEVICE),
canSetOutputDevice: MediaEngineStore.supports(MediaEngineFeatures.AUDIO_OUTPUT_DEVICE),
inputVolume: MediaEngineStore.getInputVolume(),
outputVolume: MediaEngineStore.getOutputVolume(),
inputDeviceId: MediaEngineStore.getInputDeviceId(),
inputDevices: MediaEngineStore.getInputDevices(),
outputDevices: MediaEngineStore.getOutputDevices(),
outputDeviceId: MediaEngineStore.getOutputDeviceId(),
videoDevices: MediaEngineStore.getVideoDevices(),
videoDeviceId: MediaEngineStore.getVideoDeviceId(),
echoCancellation: MediaEngineStore.getEchoCancellation(),
noiseSuppression: MediaEngineStore.getNoiseSuppression(),
automaticGainControl: MediaEngineStore.getAutomaticGainControl(),
inputMode: MediaEngineStore.getMode(),
shortcut: MediaEngineStore.getModeOptions().shortcut,
vadThreshold: MediaEngineStore.getModeOptions().threshold,
vadAutoThreshold: MediaEngineStore.getModeOptions().autoThreshold,
delay: MediaEngineStore.getModeOptions().delay,
usePTTLimited: !NativeUtils.embedded,
attenuation: MediaEngineStore.getAttenuation(),
attenuateWhileSpeakingSelf: MediaEngineStore.getAttenuateWhileSpeakingSelf(),
attenuateWhileSpeakingOthers: MediaEngineStore.getAttenuateWhileSpeakingOthers(),
silenceWarning: MediaEngineStore.getEnableSilenceWarning(),
qosEnabled: MediaEngineStore.getQoS(),
legacySubsystemEnabled: MediaEngineStore.isUsingLegacySubsystem(),
mediaEngineImpl: MediaEngineStore.getMediaEngineImpl(),
videoSettings,
};
})(UserSettingsVoice);
// WEBPACK FOOTER //
// ./discord_app/components/user_settings/UserSettingsVoice.js