2020-08-11 00:25:20 +02:00
|
|
|
import * as React from "react";
|
2021-03-30 11:20:27 +02:00
|
|
|
import {useContext, useEffect, useRef, useState} from "react";
|
2020-10-01 10:56:54 +02:00
|
|
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
2020-08-11 00:25:20 +02:00
|
|
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
2020-08-19 22:40:40 +02:00
|
|
|
import {Registry} from "tc-shared/events";
|
2021-03-30 11:20:27 +02:00
|
|
|
import {MicrophoneDevice, MicrophoneSettingsEvents, SelectedMicrophone} from "tc-shared/ui/modal/settings/MicrophoneDefinitions";
|
2020-08-11 00:25:20 +02:00
|
|
|
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
|
|
|
import {ClientIcon} from "svg-sprites/client-icons";
|
|
|
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
|
|
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
|
|
|
import {Slider} from "tc-shared/ui/react-elements/Slider";
|
|
|
|
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
|
|
|
|
import {VadType} from "tc-shared/voice/RecorderProfile";
|
2021-03-18 18:25:20 +01:00
|
|
|
import {getKeyDescription, KeyDescriptor} from "tc-shared/PPTListener";
|
2020-08-11 00:25:20 +02:00
|
|
|
import {spawnKeySelect} from "tc-shared/ui/modal/ModalKeySelect";
|
|
|
|
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
|
|
|
import {BoxedInputField} from "tc-shared/ui/react-elements/InputField";
|
2020-08-19 22:40:40 +02:00
|
|
|
import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight";
|
2021-03-18 18:25:20 +01:00
|
|
|
import {InputDevice} from "tc-shared/audio/Recorder";
|
2021-03-30 11:20:27 +02:00
|
|
|
import {joinClassList} from "tc-shared/ui/react-elements/Helper";
|
|
|
|
import {IconTooltip} from "tc-shared/ui/react-elements/Tooltip";
|
|
|
|
import _ from "lodash";
|
2021-04-24 13:59:49 +02:00
|
|
|
import {tra} from "tc-shared/i18n/localize";
|
2020-08-11 00:25:20 +02:00
|
|
|
|
|
|
|
const cssStyle = require("./Microphone.scss");
|
2021-03-30 11:20:27 +02:00
|
|
|
const EventContext = React.createContext<Registry<MicrophoneSettingsEvents>>(undefined);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
|
|
|
type MicrophoneSelectedState = "selected" | "applying" | "unselected";
|
|
|
|
const MicrophoneStatus = (props: { state: MicrophoneSelectedState }) => {
|
|
|
|
switch (props.state) {
|
|
|
|
case "applying":
|
|
|
|
return (
|
|
|
|
<div key={"applying"} className={cssStyle.iconLoading}>
|
2020-09-12 15:49:20 +02:00
|
|
|
<img draggable={false} src="img/icon_settings_loading.svg" alt={tr("applying")}/>
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
case "unselected":
|
|
|
|
return null;
|
|
|
|
|
|
|
|
case "selected":
|
2021-03-30 11:20:27 +02:00
|
|
|
return (
|
|
|
|
<ClientIconRenderer key={"selected"} icon={ClientIcon.Apply} />
|
|
|
|
);
|
2020-08-11 00:25:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
type ActivityBarStatus =
|
2021-03-30 11:20:27 +02:00
|
|
|
{ mode: "success", level: number }
|
2020-09-12 15:49:20 +02:00
|
|
|
| { mode: "error", message: string }
|
|
|
|
| { mode: "loading" }
|
|
|
|
| { mode: "uninitialized" };
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const ActivityBarStatusContext = React.createContext<ActivityBarStatus>({ mode: "loading" });
|
2020-09-29 16:31:09 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const DeviceActivityBarStatusProvider = React.memo((props: { deviceId: string, children }) => {
|
|
|
|
const events = useContext(EventContext);
|
|
|
|
const [ status, setStatus ] = useState<ActivityBarStatus>({ mode: "loading" });
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const updateState = (newState: ActivityBarStatus) => {
|
|
|
|
if(!_.isEqual(newState, status)) {
|
|
|
|
setStatus(newState);
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_device_level", event => {
|
|
|
|
if(event.status === "uninitialized") {
|
|
|
|
updateState({ mode: "uninitialized" });
|
|
|
|
} else if(event.status === "no-permissions") {
|
|
|
|
updateState({ mode: "error", message: tr("no permissions") });
|
|
|
|
} else if(event.status === "healthy") {
|
2020-08-13 13:05:37 +02:00
|
|
|
const device = event.level[props.deviceId];
|
2020-09-12 15:49:20 +02:00
|
|
|
if (!device) {
|
2021-03-30 11:20:27 +02:00
|
|
|
updateState({ mode: "loading" });
|
|
|
|
} else if(device.status === "success") {
|
|
|
|
updateState({ mode: "success", level: device.level });
|
|
|
|
} else {
|
|
|
|
updateState({ mode: "error", message: device.error });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, undefined, []);
|
2020-08-13 13:05:37 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
return (
|
|
|
|
<ActivityBarStatusContext.Provider value={status}>
|
|
|
|
{props.children}
|
|
|
|
</ActivityBarStatusContext.Provider>
|
|
|
|
);
|
|
|
|
});
|
2021-02-15 17:02:53 +01:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const InputActivityBarStatusProvider = React.memo((props: { children }) => {
|
|
|
|
const events = useContext(EventContext);
|
|
|
|
const [ status, setStatus ] = useState<ActivityBarStatus>(() => {
|
|
|
|
events.fire("query_input_level");
|
|
|
|
return { mode: "loading" };
|
|
|
|
});
|
2020-08-13 13:05:37 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_input_level", event => {
|
|
|
|
switch (event.level.status) {
|
|
|
|
case "success":
|
|
|
|
setStatus({ mode: "success", level: event.level.level });
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "error":
|
|
|
|
setStatus({ mode: "error", message: event.level.message });
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "uninitialized":
|
|
|
|
setStatus({ mode: "uninitialized" });
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
setStatus({ mode: "error", message: tr("unknown status") });
|
|
|
|
break;
|
2020-08-11 00:25:20 +02:00
|
|
|
}
|
2021-03-30 11:20:27 +02:00
|
|
|
}, undefined, []);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ActivityBarStatusContext.Provider value={status}>
|
|
|
|
{props.children}
|
|
|
|
</ActivityBarStatusContext.Provider>
|
|
|
|
);
|
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const ActivityBar = (props: { disabled?: boolean }) => {
|
|
|
|
const status = useContext(ActivityBarStatusContext);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
let error = undefined;
|
|
|
|
let hiderWidth = "100%";
|
2020-08-11 00:25:20 +02:00
|
|
|
switch (status.mode) {
|
|
|
|
case "error":
|
2021-03-30 11:20:27 +02:00
|
|
|
error = (
|
|
|
|
<div className={joinClassList(cssStyle.text, cssStyle.error)} key={"error"}>
|
|
|
|
{status.message}
|
|
|
|
</div>
|
|
|
|
);
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "loading":
|
2021-03-30 11:20:27 +02:00
|
|
|
error = (
|
|
|
|
<div className={cssStyle.text} key={"loading"}>
|
|
|
|
<Translatable>Loading</Translatable> <LoadingDots/>
|
|
|
|
</div>
|
|
|
|
);
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "success":
|
2021-03-30 11:20:27 +02:00
|
|
|
hiderWidth = (100 - status.level) + "%";
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-03-30 11:20:27 +02:00
|
|
|
|
2020-08-11 00:25:20 +02:00
|
|
|
return (
|
2020-09-12 15:49:20 +02:00
|
|
|
<div
|
|
|
|
className={cssStyle.containerActivityBar + " " + cssStyle.bar + " " + (props.disabled ? cssStyle.disabled : "")}>
|
2021-03-30 11:20:27 +02:00
|
|
|
<div className={cssStyle.hider} style={{ width: hiderWidth }} />
|
2020-08-11 00:25:20 +02:00
|
|
|
{error}
|
|
|
|
</div>
|
2021-03-30 11:20:27 +02:00
|
|
|
);
|
2020-08-11 00:25:20 +02:00
|
|
|
};
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const Microphone = React.memo((props: { device: MicrophoneDevice, state: MicrophoneSelectedState, onClick: () => void }) => {
|
|
|
|
let activityBar;
|
|
|
|
if(props.device.id !== InputDevice.NoDeviceId) {
|
|
|
|
activityBar = (
|
|
|
|
<DeviceActivityBarStatusProvider deviceId={props.device.id} key={"bar"}>
|
|
|
|
<ActivityBar />
|
|
|
|
</DeviceActivityBarStatusProvider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-11 00:25:20 +02:00
|
|
|
return (
|
2020-09-12 15:49:20 +02:00
|
|
|
<div className={cssStyle.device + " " + (props.state === "unselected" ? "" : cssStyle.selected)}
|
|
|
|
onClick={props.onClick}>
|
2020-08-11 00:25:20 +02:00
|
|
|
<div className={cssStyle.containerSelected}>
|
2020-09-12 15:49:20 +02:00
|
|
|
<MicrophoneStatus state={props.state}/>
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
<div className={cssStyle.containerName}>
|
2021-02-15 17:02:53 +01:00
|
|
|
<div className={cssStyle.driver}>{props.device.driver + (props.device.default ? " (Default Device)" : "")}</div>
|
2020-08-11 00:25:20 +02:00
|
|
|
<div className={cssStyle.name}>{props.device.name}</div>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.containerActivity}>
|
2021-03-30 11:20:27 +02:00
|
|
|
{activityBar}
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
2021-03-30 11:20:27 +02:00
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2020-08-13 13:05:37 +02:00
|
|
|
type MicrophoneListState = {
|
|
|
|
type: "normal" | "loading" | "audio-not-initialized"
|
|
|
|
} | {
|
|
|
|
type: "error",
|
|
|
|
message: string
|
|
|
|
} | {
|
|
|
|
type: "no-permissions",
|
|
|
|
bySystem: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
const PermissionDeniedOverlay = (props: { bySystem: boolean, shown: boolean, onRequestPermission: () => void }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (props.bySystem) {
|
2020-08-13 13:05:37 +02:00
|
|
|
return (
|
|
|
|
<div key={"system"} className={cssStyle.overlay + " " + (props.shown ? undefined : cssStyle.hidden)}>
|
2020-09-12 15:49:20 +02:00
|
|
|
<ClientIconRenderer icon={ClientIcon.MicrophoneBroken}/>
|
2020-08-13 13:05:37 +02:00
|
|
|
<a><Translatable>Microphone access has been blocked by your browser.</Translatable></a>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<div key={"user"} className={cssStyle.overlay + " " + (props.shown ? undefined : cssStyle.hidden)}>
|
|
|
|
<a><Translatable>Please grant access to your microphone.</Translatable></a>
|
|
|
|
<Button
|
|
|
|
key={"request"}
|
|
|
|
color={"green"}
|
|
|
|
type={"small"}
|
|
|
|
onClick={props.onRequestPermission}
|
|
|
|
><Translatable>Request access</Translatable></Button>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 00:25:20 +02:00
|
|
|
const MicrophoneList = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
const [state, setState] = useState<MicrophoneListState>(() => {
|
2020-08-11 00:25:20 +02:00
|
|
|
props.events.fire("query_devices");
|
2020-09-12 15:49:20 +02:00
|
|
|
return {type: "loading"};
|
2020-08-11 00:25:20 +02:00
|
|
|
});
|
2021-02-15 17:02:53 +01:00
|
|
|
const [selectedDevice, setSelectedDevice] = useState<{
|
2021-02-15 17:47:04 +01:00
|
|
|
selectedDevice: SelectedMicrophone,
|
|
|
|
selectingDevice: SelectedMicrophone | undefined
|
2021-02-15 17:02:53 +01:00
|
|
|
}>();
|
2020-09-12 15:49:20 +02:00
|
|
|
const [deviceList, setDeviceList] = useState<MicrophoneDevice[]>([]);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
|
|
|
props.events.reactUse("notify_devices", event => {
|
|
|
|
setSelectedDevice(undefined);
|
|
|
|
switch (event.status) {
|
|
|
|
case "success":
|
|
|
|
setDeviceList(event.devices.slice(0));
|
2020-09-12 15:49:20 +02:00
|
|
|
setState({type: "normal"});
|
2021-02-15 17:02:53 +01:00
|
|
|
setSelectedDevice({
|
|
|
|
selectedDevice: event.selectedDevice,
|
|
|
|
selectingDevice: undefined
|
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "error":
|
2020-09-12 15:49:20 +02:00
|
|
|
setState({type: "error", message: event.error || tr("Unknown error")});
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "audio-not-initialized":
|
2020-09-12 15:49:20 +02:00
|
|
|
setState({type: "audio-not-initialized"});
|
2020-08-13 13:05:37 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "no-permissions":
|
2020-09-12 15:49:20 +02:00
|
|
|
setState({type: "no-permissions", bySystem: event.shouldAsk});
|
2020-08-11 00:25:20 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("action_set_selected_device", event => {
|
2021-02-15 17:02:53 +01:00
|
|
|
setSelectedDevice({
|
|
|
|
selectedDevice: selectedDevice?.selectedDevice,
|
|
|
|
selectingDevice: event.target
|
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("action_set_selected_device_result", event => {
|
2021-02-15 17:02:53 +01:00
|
|
|
if (event.status === "error") {
|
|
|
|
createErrorModal(tr("Failed to select microphone"), tra("Failed to select microphone:\n{}", event.reason)).open();
|
|
|
|
setSelectedDevice({
|
|
|
|
selectedDevice: selectedDevice?.selectedDevice,
|
|
|
|
selectingDevice: undefined
|
|
|
|
});
|
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
});
|
|
|
|
|
2021-02-15 17:47:04 +01:00
|
|
|
props.events.reactUse("notify_device_selected", event => {
|
|
|
|
setSelectedDevice({ selectedDevice: event.device, selectingDevice: undefined });
|
|
|
|
})
|
|
|
|
|
2021-02-15 17:02:53 +01:00
|
|
|
const deviceSelectState = (device: MicrophoneDevice | "none" | "default"): MicrophoneSelectedState => {
|
2021-02-15 17:47:04 +01:00
|
|
|
let selected: SelectedMicrophone;
|
2021-02-15 17:02:53 +01:00
|
|
|
let mode: MicrophoneSelectedState;
|
|
|
|
if(typeof selectedDevice?.selectingDevice !== "undefined") {
|
|
|
|
selected = selectedDevice.selectingDevice;
|
|
|
|
mode = "applying";
|
|
|
|
} else if(typeof selectedDevice?.selectedDevice !== "undefined") {
|
|
|
|
selected = selectedDevice.selectedDevice;
|
|
|
|
mode = "selected";
|
|
|
|
} else {
|
|
|
|
return "unselected";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(selected.type === "default") {
|
|
|
|
return device === "default" || (typeof device === "object" && device.default) ? mode : "unselected";
|
|
|
|
} else if(selected.type === "none") {
|
|
|
|
return device === "none" ? mode : "unselected";
|
|
|
|
} else {
|
|
|
|
return typeof device === "object" && device.id === selected.deviceId ? mode : "unselected";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 00:25:20 +02:00
|
|
|
return (
|
|
|
|
<div className={cssStyle.body + " " + cssStyle.containerDevices}>
|
2020-09-12 15:49:20 +02:00
|
|
|
<div
|
|
|
|
className={cssStyle.overlay + " " + (state.type !== "audio-not-initialized" ? cssStyle.hidden : undefined)}>
|
2020-08-11 00:25:20 +02:00
|
|
|
<a>
|
|
|
|
<Translatable>The web audio play hasn't been initialized yet.</Translatable>
|
|
|
|
<Translatable>Click somewhere on the base to initialize it.</Translatable>
|
|
|
|
</a>
|
|
|
|
</div>
|
2020-08-13 13:05:37 +02:00
|
|
|
<div className={cssStyle.overlay + " " + (state.type !== "error" ? cssStyle.hidden : undefined)}>
|
2020-08-19 22:40:40 +02:00
|
|
|
<a>{state.type === "error" ? state.message : undefined}</a>
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
2020-08-13 13:05:37 +02:00
|
|
|
<div className={cssStyle.overlay + " " + (state.type !== "no-permissions" ? cssStyle.hidden : undefined)}>
|
|
|
|
<a><Translatable>Please grant access to your microphone.</Translatable></a>
|
|
|
|
<Button
|
|
|
|
color={"green"}
|
|
|
|
type={"small"}
|
2020-09-12 15:49:20 +02:00
|
|
|
onClick={() => props.events.fire("action_request_permissions")}
|
2020-08-13 13:05:37 +02:00
|
|
|
><Translatable>Request access</Translatable></Button>
|
|
|
|
</div>
|
|
|
|
<PermissionDeniedOverlay
|
|
|
|
bySystem={state.type === "no-permissions" ? state.bySystem : false}
|
|
|
|
shown={state.type === "no-permissions"}
|
|
|
|
onRequestPermission={() => props.events.fire("action_request_permissions")}
|
|
|
|
/>
|
|
|
|
<div className={cssStyle.overlay + " " + (state.type !== "loading" ? cssStyle.hidden : undefined)}>
|
2020-08-11 00:25:20 +02:00
|
|
|
<a><Translatable>Loading</Translatable> <LoadingDots/></a>
|
|
|
|
</div>
|
2021-02-15 17:02:53 +01:00
|
|
|
<Microphone key={"d-no-device"}
|
|
|
|
device={{
|
|
|
|
id: "none",
|
|
|
|
driver: tr("No device"),
|
|
|
|
name: tr("No device"),
|
|
|
|
default: false
|
|
|
|
}}
|
|
|
|
state={deviceSelectState("none")}
|
2020-08-13 13:05:37 +02:00
|
|
|
onClick={() => {
|
2021-02-15 17:02:53 +01:00
|
|
|
if (state.type !== "normal" || selectedDevice?.selectingDevice) {
|
2020-08-13 13:05:37 +02:00
|
|
|
return;
|
2021-02-15 17:02:53 +01:00
|
|
|
}
|
2020-08-13 13:05:37 +02:00
|
|
|
|
2021-02-15 17:02:53 +01:00
|
|
|
props.events.fire("action_set_selected_device", { target: { type: "none" } });
|
2020-08-13 13:05:37 +02:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
{deviceList.map(device => (
|
|
|
|
<Microphone
|
|
|
|
key={"d-" + device.id}
|
|
|
|
device={device}
|
|
|
|
state={deviceSelectState(device)}
|
|
|
|
onClick={() => {
|
|
|
|
if (state.type !== "normal" || selectedDevice?.selectingDevice) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(device.default) {
|
|
|
|
props.events.fire("action_set_selected_device", { target: { type: "default" } });
|
|
|
|
} else {
|
|
|
|
props.events.fire("action_set_selected_device", { target: { type: "device", deviceId: device.id } });
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
))}
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const ListRefreshButton = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
const [updateTimeout, setUpdateTimeout] = useState(Date.now() + 2000);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (updateTimeout === 0)
|
2020-08-11 00:25:20 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
const id = setTimeout(() => {
|
|
|
|
setUpdateTimeout(0);
|
|
|
|
}, Math.max(updateTimeout - Date.now(), 0));
|
|
|
|
return () => clearTimeout(id);
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("query_devices", () => setUpdateTimeout(Date.now() + 2000));
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
return <Button disabled={updateTimeout > 0} color={"blue"}
|
|
|
|
onClick={() => props.events.fire("query_devices", {refresh_list: true})}>
|
2020-08-11 00:25:20 +02:00
|
|
|
<Translatable>Update</Translatable>
|
|
|
|
</Button>;
|
|
|
|
}
|
|
|
|
|
|
|
|
const VolumeSettings = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
|
|
|
const refSlider = useRef<Slider>();
|
2020-09-12 15:49:20 +02:00
|
|
|
const [value, setValue] = useState<"loading" | number>(() => {
|
|
|
|
props.events.fire("query_setting", {setting: "volume"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
})
|
|
|
|
|
|
|
|
props.events.reactUse("notify_setting", event => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (event.setting !== "volume")
|
2020-08-11 00:25:20 +02:00
|
|
|
return;
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
refSlider.current?.setState({value: event.value});
|
2020-08-11 00:25:20 +02:00
|
|
|
setValue(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.containerVolume}>
|
|
|
|
<a><Translatable>Volume</Translatable></a>
|
|
|
|
<Slider
|
|
|
|
ref={refSlider}
|
|
|
|
|
|
|
|
minValue={0}
|
|
|
|
maxValue={100}
|
|
|
|
stepSize={1}
|
|
|
|
value={value === "loading" ? 0 : value}
|
|
|
|
unit={"%"}
|
|
|
|
disabled={value === "loading"}
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={value => props.events.fire("action_set_setting", {setting: "volume", value: value})}
|
2020-08-11 00:25:20 +02:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
const PPTKeyButton = React.memo((props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
const [key, setKey] = useState<"loading" | KeyDescriptor>(() => {
|
|
|
|
props.events.fire("query_setting", {setting: "ppt-key"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
const [isActive, setActive] = useState(false);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
|
|
|
props.events.reactUse("notify_setting", event => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (event.setting === "vad-type")
|
2020-08-11 00:25:20 +02:00
|
|
|
setActive(event.value === "push_to_talk");
|
2020-09-12 15:49:20 +02:00
|
|
|
else if (event.setting === "ppt-key")
|
2020-08-11 00:25:20 +02:00
|
|
|
setKey(event.value);
|
|
|
|
});
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
if (key === "loading") {
|
|
|
|
return <Button color={"none"} disabled={true} key={"loading"}><Translatable>loading</Translatable>
|
|
|
|
<LoadingDots/></Button>;
|
2020-08-11 00:25:20 +02:00
|
|
|
} else {
|
|
|
|
return <Button
|
|
|
|
color={"none"}
|
|
|
|
key={"key"}
|
|
|
|
disabled={!isActive}
|
|
|
|
onClick={() => {
|
|
|
|
spawnKeySelect(key => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (!key) return;
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
props.events.fire("action_set_setting", {setting: "ppt-key", value: key});
|
2020-08-11 00:25:20 +02:00
|
|
|
});
|
|
|
|
}}
|
2021-03-18 18:25:20 +01:00
|
|
|
>{getKeyDescription(key)}</Button>;
|
2020-08-11 00:25:20 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const PPTDelaySettings = React.memo(() => {
|
|
|
|
const events = useContext(EventContext);
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
const [delayActive, setDelayActive] = useState<"loading" | boolean>(() => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("query_setting", {setting: "ppt-release-delay"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
const [delay, setDelay] = useState<"loading" | number>(() => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("query_setting", {setting: "ppt-release-delay-active"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
const [isActive, setActive] = useState(false);
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_setting", event => {
|
|
|
|
if (event.setting === "vad-type") {
|
2020-08-11 00:25:20 +02:00
|
|
|
setActive(event.value === "push_to_talk");
|
2021-03-30 11:20:27 +02:00
|
|
|
} else if (event.setting === "ppt-release-delay") {
|
2020-08-11 00:25:20 +02:00
|
|
|
setDelay(event.value);
|
2021-03-30 11:20:27 +02:00
|
|
|
} else if (event.setting === "ppt-release-delay-active") {
|
2020-08-11 00:25:20 +02:00
|
|
|
setDelayActive(event.value);
|
2021-03-30 11:20:27 +02:00
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.containerPptDelay}>
|
|
|
|
<Checkbox
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={value => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("action_set_setting", {setting: "ppt-release-delay-active", value: value})
|
2020-09-12 15:49:20 +02:00
|
|
|
}}
|
2020-08-11 00:25:20 +02:00
|
|
|
disabled={!isActive}
|
|
|
|
value={delayActive === true}
|
|
|
|
label={<Translatable>Delay on Push to Talk</Translatable>}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<BoxedInputField
|
|
|
|
className={cssStyle.input}
|
|
|
|
disabled={!isActive || delayActive === "loading" || !delayActive}
|
|
|
|
suffix={"ms"}
|
|
|
|
inputBox={() => <input
|
|
|
|
type="number"
|
|
|
|
min={0}
|
|
|
|
max={4000}
|
|
|
|
step={1}
|
|
|
|
value={delay}
|
|
|
|
disabled={!isActive || delayActive === "loading" || !delayActive}
|
|
|
|
onChange={event => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (event.target.value === "") {
|
2020-08-11 00:25:20 +02:00
|
|
|
const target = event.target;
|
|
|
|
setImmediate(() => target.value = "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const newValue = event.target.valueAsNumber;
|
2021-03-30 11:20:27 +02:00
|
|
|
if (isNaN(newValue)) {
|
2020-08-11 00:25:20 +02:00
|
|
|
return;
|
2021-03-30 11:20:27 +02:00
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
if (newValue < 0 || newValue > 4000) {
|
2020-08-11 00:25:20 +02:00
|
|
|
return;
|
2021-03-30 11:20:27 +02:00
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("action_set_setting", {setting: "ppt-release-delay", value: newValue});
|
2020-08-11 00:25:20 +02:00
|
|
|
}}
|
|
|
|
/>}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
2021-03-30 11:20:27 +02:00
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const RNNoiseLabel = React.memo(() => (
|
|
|
|
<VariadicTranslatable text={"Enable RNNoise cancellation ({})"}>
|
2020-10-01 10:56:54 +02:00
|
|
|
<a href={"https://jmvalin.ca/demo/rnnoise/"} target={"_blank"} style={{ margin: 0 }}><Translatable>more info</Translatable></a>
|
|
|
|
</VariadicTranslatable>
|
2021-03-30 11:20:27 +02:00
|
|
|
));
|
2020-10-01 10:56:54 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const RNNoiseSettings = React.memo(() => {
|
2020-10-01 10:56:54 +02:00
|
|
|
if(__build.target === "web") {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const events = useContext(EventContext);
|
2020-10-01 10:56:54 +02:00
|
|
|
const [ enabled, setEnabled ] = useState<boolean | "loading">(() => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("query_setting", { setting: "rnnoise" });
|
2020-10-01 10:56:54 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_setting", event => event.setting === "rnnoise" && setEnabled(event.value));
|
2020-10-01 10:56:54 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Checkbox label={<RNNoiseLabel />}
|
|
|
|
disabled={enabled === "loading"}
|
|
|
|
value={enabled === true}
|
2021-03-30 11:20:27 +02:00
|
|
|
onChange={value => events.fire("action_set_setting", { setting: "rnnoise", value: value })}
|
2020-10-01 10:56:54 +02:00
|
|
|
/>
|
|
|
|
)
|
2021-03-30 11:20:27 +02:00
|
|
|
});
|
2020-10-01 10:56:54 +02:00
|
|
|
|
2020-08-11 00:25:20 +02:00
|
|
|
const VadSelector = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
const [selectedType, setSelectedType] = useState<VadType | "loading">(() => {
|
|
|
|
props.events.fire("query_setting", {setting: "vad-type"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("notify_setting", event => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (event.setting !== "vad-type")
|
2020-08-11 00:25:20 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
setSelectedType(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.containerSelectVad}>
|
|
|
|
<div className={cssStyle.fieldset}>
|
|
|
|
<div className={cssStyle.containerOption}>
|
|
|
|
<RadioButton
|
|
|
|
name={"vad-type"}
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={() => {
|
|
|
|
props.events.fire("action_set_setting", {setting: "vad-type", value: "push_to_talk"})
|
|
|
|
}}
|
2020-08-11 00:25:20 +02:00
|
|
|
selected={selectedType === "push_to_talk"}
|
|
|
|
disabled={selectedType === "loading"}
|
|
|
|
>
|
|
|
|
<a><Translatable>Push to Talk</Translatable></a>
|
|
|
|
</RadioButton>
|
|
|
|
<div className={cssStyle.containerButton}>
|
2020-09-12 15:49:20 +02:00
|
|
|
<PPTKeyButton events={props.events}/>
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.containerOption}>
|
|
|
|
<RadioButton
|
|
|
|
name={"vad-type"}
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={() => {
|
|
|
|
props.events.fire("action_set_setting", {setting: "vad-type", value: "threshold"})
|
|
|
|
}}
|
2020-08-11 00:25:20 +02:00
|
|
|
selected={selectedType === "threshold"}
|
|
|
|
disabled={selectedType === "loading"}
|
|
|
|
>
|
|
|
|
<a><Translatable>Voice activity detection</Translatable></a>
|
|
|
|
</RadioButton>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.containerOption}>
|
|
|
|
<RadioButton
|
|
|
|
name={"vad-type"}
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={() => {
|
|
|
|
props.events.fire("action_set_setting", {setting: "vad-type", value: "active"})
|
|
|
|
}}
|
2020-08-11 00:25:20 +02:00
|
|
|
selected={selectedType === "active"}
|
|
|
|
disabled={selectedType === "loading"}
|
|
|
|
>
|
|
|
|
<a><Translatable>Always active</Translatable></a>
|
|
|
|
</RadioButton>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const ThresholdSelector = React.memo(() => {
|
|
|
|
const events = useContext(EventContext);
|
2020-08-11 00:25:20 +02:00
|
|
|
const refSlider = useRef<Slider>();
|
2020-09-12 15:49:20 +02:00
|
|
|
const [value, setValue] = useState<"loading" | number>(() => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("query_setting", {setting: "threshold-threshold"});
|
2020-08-11 00:25:20 +02:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
2021-02-15 17:02:53 +01:00
|
|
|
const [currentDevice, setCurrentDevice] = useState<{ type: "none" } | { type: "device", deviceId: string }>({ type: "none" });
|
|
|
|
const defaultDeviceId = useRef<string | undefined>();
|
|
|
|
const [isVadActive, setVadActive] = useState(false);
|
|
|
|
|
2021-02-15 17:47:04 +01:00
|
|
|
const changeCurrentDevice = (selected: SelectedMicrophone) => {
|
2021-02-15 17:02:53 +01:00
|
|
|
switch (selected.type) {
|
|
|
|
case "none":
|
|
|
|
setCurrentDevice({ type: "none" });
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "device":
|
|
|
|
setCurrentDevice({ type: "device", deviceId: selected.deviceId });
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "default":
|
|
|
|
if(defaultDeviceId.current) {
|
|
|
|
setCurrentDevice({ type: "device", deviceId: defaultDeviceId.current });
|
|
|
|
} else {
|
|
|
|
setCurrentDevice({ type: "none" });
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw tr("invalid device type");
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_setting", event => {
|
2020-09-12 15:49:20 +02:00
|
|
|
if (event.setting === "threshold-threshold") {
|
|
|
|
refSlider.current?.setState({value: event.value});
|
2020-08-11 00:25:20 +02:00
|
|
|
setValue(event.value);
|
2020-09-12 15:49:20 +02:00
|
|
|
} else if (event.setting === "vad-type") {
|
2021-02-15 17:02:53 +01:00
|
|
|
setVadActive(event.value === "threshold");
|
2020-08-11 00:25:20 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_devices", event => {
|
2021-02-15 17:02:53 +01:00
|
|
|
if(event.status === "success") {
|
|
|
|
const defaultDevice = event.devices.find(device => device.default);
|
|
|
|
defaultDeviceId.current = defaultDevice?.id;
|
|
|
|
changeCurrentDevice(event.selectedDevice);
|
|
|
|
} else {
|
|
|
|
defaultDeviceId.current = undefined;
|
|
|
|
setCurrentDevice({ type: "none" });
|
|
|
|
}
|
2020-08-13 13:05:37 +02:00
|
|
|
});
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
events.reactUse("notify_device_selected", event => changeCurrentDevice(event.device));
|
2020-08-13 13:05:37 +02:00
|
|
|
|
2021-02-15 17:02:53 +01:00
|
|
|
let isActive = isVadActive && currentDevice.type === "device";
|
2020-08-11 00:25:20 +02:00
|
|
|
return (
|
|
|
|
<div className={cssStyle.containerSensitivity}>
|
|
|
|
<div className={cssStyle.containerBar}>
|
2021-03-30 11:20:27 +02:00
|
|
|
<InputActivityBarStatusProvider>
|
|
|
|
<ActivityBar disabled={!isActive || !currentDevice} key={"activity-" + currentDevice} />
|
|
|
|
</InputActivityBarStatusProvider>
|
2020-08-11 00:25:20 +02:00
|
|
|
</div>
|
|
|
|
<Slider
|
|
|
|
ref={refSlider}
|
|
|
|
|
|
|
|
className={cssStyle.slider}
|
|
|
|
classNameFiller={cssStyle.filler}
|
|
|
|
|
|
|
|
minValue={0}
|
|
|
|
maxValue={100}
|
|
|
|
stepSize={1}
|
|
|
|
value={value === "loading" ? 0 : value}
|
|
|
|
unit={"%"}
|
|
|
|
|
|
|
|
disabled={value === "loading" || !isActive}
|
|
|
|
|
2020-09-12 15:49:20 +02:00
|
|
|
onChange={value => {
|
2021-03-30 11:20:27 +02:00
|
|
|
events.fire("action_set_setting", {setting: "threshold-threshold", value: value})
|
2020-09-12 15:49:20 +02:00
|
|
|
}}
|
2020-08-11 00:25:20 +02:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)
|
2021-03-30 11:20:27 +02:00
|
|
|
});
|
2020-08-11 00:25:20 +02:00
|
|
|
|
2020-08-19 22:40:40 +02:00
|
|
|
const HelpText0 = () => (
|
|
|
|
<HighlightText highlightId={"hs-0"} className={cssStyle.help}>
|
|
|
|
<Translatable>
|
2020-09-12 15:49:20 +02:00
|
|
|
Firstly we need to setup a microphone.<br/>
|
|
|
|
Let me guide you thru the basic UI elements.<br/>
|
|
|
|
<br/>
|
2020-08-19 22:40:40 +02:00
|
|
|
To continue click anywhere on the screen.
|
|
|
|
</Translatable>
|
|
|
|
</HighlightText>
|
|
|
|
);
|
|
|
|
|
|
|
|
const HelpText1 = () => (
|
|
|
|
<HighlightText highlightId={"hs-1"} className={cssStyle.help + " " + cssStyle.paddingTop}>
|
|
|
|
<Translatable>
|
2020-09-12 15:49:20 +02:00
|
|
|
All your available microphones are listed within this box.<br/>
|
|
|
|
<br/>
|
|
|
|
The currently selected microphone<br/>
|
|
|
|
is marked with a green checkmark. To change the selected microphone<br/>
|
|
|
|
just click on the new one.<br/>
|
|
|
|
<br/>
|
2020-08-19 22:40:40 +02:00
|
|
|
To continue click anywhere on the screen.
|
|
|
|
</Translatable>
|
|
|
|
</HighlightText>
|
|
|
|
);
|
|
|
|
|
|
|
|
const HelpText2 = () => (
|
|
|
|
<HighlightText highlightId={"hs-2"} className={cssStyle.help + " " + cssStyle.paddingTop}>
|
|
|
|
<a>
|
|
|
|
<Translatable>TeaSpeak has three voice activity detection types:</Translatable>
|
|
|
|
</a>
|
|
|
|
<ol>
|
|
|
|
<li>
|
|
|
|
<Translatable>
|
2020-09-12 15:49:20 +02:00
|
|
|
To transmit audio data you'll have to<br/>
|
2020-08-19 22:40:40 +02:00
|
|
|
press a key. The key could be selected via the button right to the radio button
|
|
|
|
</Translatable>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<Translatable>
|
|
|
|
In this mode, TeaSpeak will continuously analyze your microphone input.
|
|
|
|
If the audio level is grater than a certain threshold,
|
|
|
|
the audio will be transmitted.
|
|
|
|
The threshold is changeable via the "Sensitivity Settings" slider
|
|
|
|
</Translatable>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<Translatable>Continuously transmit any audio data.</Translatable>
|
|
|
|
</li>
|
|
|
|
</ol>
|
|
|
|
<a>
|
|
|
|
<Translatable>
|
2020-09-12 15:49:20 +02:00
|
|
|
Now you're ready to configure your microphone.<br/>
|
2020-08-19 22:40:40 +02:00
|
|
|
Just click anywhere on the screen.
|
|
|
|
</Translatable>
|
|
|
|
</a>
|
|
|
|
</HighlightText>
|
|
|
|
);
|
|
|
|
|
2021-03-30 11:20:27 +02:00
|
|
|
const InputProcessorButton = React.memo(() => {
|
|
|
|
const events = useContext(EventContext);
|
|
|
|
|
|
|
|
if(__build.target !== "client") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Button
|
|
|
|
color={"none"}
|
|
|
|
type={"extra-small"}
|
|
|
|
onClick={() => events.fire("action_open_processor_properties")}
|
|
|
|
>
|
|
|
|
<Translatable>Input processor properties</Translatable>
|
|
|
|
</Button>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2020-08-19 22:40:40 +02:00
|
|
|
export const MicrophoneSettings = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
|
2020-09-12 15:49:20 +02:00
|
|
|
const [highlighted, setHighlighted] = useState(() => {
|
2020-08-19 22:40:40 +02:00
|
|
|
props.events.fire("query_help");
|
|
|
|
return undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("notify_highlight", event => setHighlighted(event.field));
|
|
|
|
|
|
|
|
return (
|
2021-03-30 11:20:27 +02:00
|
|
|
<EventContext.Provider value={props.events}>
|
|
|
|
<HighlightContainer
|
|
|
|
highlightedId={highlighted}
|
|
|
|
onClick={() => props.events.fire("action_help_click")}
|
|
|
|
classList={cssStyle.highlightContainer}
|
|
|
|
>
|
|
|
|
<div className={cssStyle.container}>
|
|
|
|
<HelpText0/>
|
|
|
|
<HighlightRegion className={cssStyle.left} highlightId={"hs-1"}>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<div className={cssStyle.text}>
|
|
|
|
<Translatable>Select your Microphone Device</Translatable>
|
|
|
|
</div>
|
|
|
|
<ListRefreshButton events={props.events}/>
|
2020-08-19 22:40:40 +02:00
|
|
|
</div>
|
2021-03-30 11:20:27 +02:00
|
|
|
<MicrophoneList events={props.events}/>
|
|
|
|
<HelpText2/>
|
|
|
|
</HighlightRegion>
|
|
|
|
<HighlightRegion className={cssStyle.right} highlightId={"hs-2"}>
|
|
|
|
<HelpText1/>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<div className={cssStyle.text}>
|
|
|
|
<Translatable>Microphone Settings</Translatable>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.body}>
|
|
|
|
<VolumeSettings events={props.events}/>
|
|
|
|
<VadSelector events={props.events}/>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<div className={cssStyle.text}>
|
|
|
|
<Translatable>Sensitivity Settings</Translatable>
|
|
|
|
</div>
|
|
|
|
<IconTooltip className={cssStyle.icon}>
|
|
|
|
<div className={cssStyle.tooltipContainer}>
|
|
|
|
<Translatable>The volume meter will show the processed audio volume as others would hear you.</Translatable>
|
|
|
|
</div>
|
|
|
|
</IconTooltip>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.body}>
|
|
|
|
<ThresholdSelector />
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<div className={cssStyle.text}>
|
|
|
|
<Translatable>Advanced Settings</Translatable>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.body}>
|
|
|
|
<div className={cssStyle.containerAdvanced}>
|
|
|
|
<PPTDelaySettings />
|
|
|
|
<RNNoiseSettings />
|
|
|
|
<InputProcessorButton />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</HighlightRegion>
|
|
|
|
</div>
|
|
|
|
</HighlightContainer>
|
|
|
|
</EventContext.Provider>
|
2020-08-19 22:40:40 +02:00
|
|
|
);
|
|
|
|
}
|