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 (
{i18n.Messages.FORM_LABEL_AUTOMATIC_VAD}
);
}
}
renderSlider() {
const {auto, threshold, speaking} = this.props;
if (auto) {
return (
{i18n.Messages.FORM_HELP_AUTOMATIC_VAD}
);
} else {
return (
);
}
}
renderInputDisabledWarning() {
if (!MediaEngineStore.isEnabled()) {
return (
{i18n.Messages.FORM_WARNING_INPUT_SENSITIVTY.format({onEnableClick: AudioActionCreators.enable})}
);
}
}
render() {
return (
{i18n.Messages.FORM_LABEL_INPUT_SENSITIVTY}
{this.renderAutomaticVADToggle()}
{this.renderSlider()}
{this.renderInputDisabledWarning()}
);
}
}
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 (
);
});
}
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 (
{i18n.Messages.RESET_VOICE_SETTINGS_BODY}
);
});
}
handleSubsystemChanged() {
ModalActionCreators.push(props => {
return (
{i18n.Messages.SWITCH_SUBSYSTEM_BODY}
);
});
}
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 = (
{i18n.Messages.BROWSER_INPUT_DEVICE_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text Input Devices'),
})}
);
}
let outputWarning;
if (!canSetOutputDevice) {
outputWarning = (
{i18n.Messages.BROWSER_OUTPUT_DEVICE_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text Output Devices'),
})}
);
}
const inputDevicesDisabled = lodash(inputDevices).values().first().disabled || inputWarning != null;
const outputDevicesDisabled = lodash(outputDevices).values().first().disabled || outputWarning != null;
return (
{i18n.Messages.FORM_LABEL_INPUT_DEVICE}
{i18n.Messages.FORM_LABEL_OUTPUT_DEVICE}
);
}
renderVolumeControls() {
const {inputVolume, outputVolume} = this.props;
return (
{i18n.Messages.FORM_LABEL_INPUT_VOLUME}
{i18n.Messages.FORM_LABEL_OUTPUT_VOLUME}
);
}
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 (
);
}
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 = (
{i18n.Messages.PTT_LIMITED_WARNING.format({
onDownloadClick: this.handleDownload.bind(this, 'Help Text PTT'),
})}
);
} else {
secondaryMessage = (
{i18n.Messages.USER_SETTINGS_VOICE_ADD_MULTIPLE.format({
onClick: () => UserSettingsModalActionCreators.setSection(UserSettingsSections.KEYBINDS),
})}
);
}
return (
);
}
renderVoiceSensitivityTools() {
const {inputMode, vadThreshold, vadAutoThreshold} = this.props;
if (inputMode !== InputModes.VOICE_ACTIVITY) {
return null;
}
return (
);
}
renderVideoDevices() {
const {videoDevices, videoDeviceId} = this.props;
let videoWarning;
if (!MediaEngineStore.isEnabled()) {
videoWarning = (
{i18n.Messages.FORM_WARNING_VIDEO_PREVIEW.format({onEnableClick: AudioActionCreators.enable})}
);
}
const videoDevicesDisabled = lodash(videoDevices).values().first().disabled || videoWarning != null;
const {Camera} = MediaEngineStore.getMediaEngine();
return (
{i18n.Messages.FORM_LABEL_VIDEO_DEVICE}
{i18n.Messages.FORM_LABEL_VIDEO_PREVIEW}
);
}
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 (
);
}
renderVoiceProcessing() {
if (!MediaEngineStore.supports(MediaEngineFeatures.VOICE_PROCESSING)) {
return null;
}
const {noiseSuppression, echoCancellation, automaticGainControl} = this.props;
return (
{i18n.Messages.ECHO_CANCELLATION}
{i18n.Messages.NOISE_SUPPRESSION}
{i18n.Messages.AUTOMATIC_GAIN_CONTROL}
);
}
renderQoS() {
if (!MediaEngineStore.supports(MediaEngineFeatures.QOS)) {
return null;
}
const {qosEnabled} = this.props;
return (
{i18n.Messages.FORM_CHECKBOX_QOS}
);
}
renderAttenutation() {
if (!MediaEngineStore.supports(MediaEngineFeatures.ATTENUATION)) {
return null;
}
const {attenuation, attenuateWhileSpeakingSelf, attenuateWhileSpeakingOthers} = this.props;
return (
{i18n.Messages.FORM_HELP_ATTENUATION}
{i18n.Messages.ATTENUATE_WHILE_SPEAKING_SELF}
{i18n.Messages.ATTENUATE_WHILE_SPEAKING_OTHERS}
);
}
renderSubsystemSettings() {
if (!MediaEngineStore.supports(MediaEngineFeatures.LEGACY_SUBSYSTEM)) {
return null;
}
const {legacySubsystemEnabled} = this.props;
return (
{i18n.Messages.FORM_CHECKBOX_LEGACY_SUBSYSTEM}
);
}
renderDiagnostics() {
if (!NativeUtils.embedded) {
return null;
}
const {silenceWarning} = this.props;
return (
{i18n.Messages.DISPLAY_SILENCE_WARNING}
);
}
renderResetVoiceSettings() {
return (
);
}
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 (
{i18n.Messages.FORM_LABEL_MEDIA_ENGINE}
);
}
}
renderVideoSettings() {
if (this.props.videoSettings) {
return (
{i18n.Messages.VIDEO_SETTINGS}
{this.renderVideoDevices()}
);
}
return null;
}
render() {
return (
{this.renderDevices()}
{this.renderVolumeControls()}
{this.renderInputMode()}
{this.renderPTTTools()}
{this.renderVoiceSensitivityTools()}
{this.renderVideoSettings()}
{i18n.Messages.SETTINGS_ADVANCED}
{this.renderCodec()}
{this.renderMediaEngineImplGroup()}
{this.renderVoiceProcessing()}
{this.renderQoS()}
{this.renderAttenutation()}
{this.renderSubsystemSettings()}
{this.renderDiagnostics()}
{this.renderResetVoiceSettings()}
);
}
}
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