import * as React from "react"; import {useEffect, useRef, useState} from "react"; import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n"; import {Button} from "tc-shared/ui/react-elements/Button"; import {Registry} from "tc-shared/events"; import {MicrophoneDevice, MicrophoneSettingsEvents} from "tc-shared/ui/modal/settings/Microphone"; 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"; import {key_description, KeyDescriptor} from "tc-shared/PPTListener"; 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"; import {IDevice} from "tc-shared/audio/recorder"; import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight"; const cssStyle = require("./Microphone.scss"); type MicrophoneSelectedState = "selected" | "applying" | "unselected"; const MicrophoneStatus = (props: { state: MicrophoneSelectedState }) => { switch (props.state) { case "applying": return (
{tr("applying")}/
); case "unselected": return null; case "selected": return ; } } type ActivityBarStatus = { mode: "success" } | { mode: "error", message: string } | { mode: "loading" } | { mode: "uninitialized" }; const ActivityBar = (props: { events: Registry, deviceId: string, disabled?: boolean }) => { const refHider = useRef(); const [status, setStatus] = useState({mode: "loading"}); if(typeof props.deviceId === "undefined") { throw "invalid device id"; } props.events.reactUse("notify_device_level", event => { if (event.status === "uninitialized") { if (status.mode === "uninitialized") return; setStatus({mode: "uninitialized"}); } else if (event.status === "no-permissions") { const noPermissionsMessage = tr("no permissions"); if (status.mode === "error" && status.message === noPermissionsMessage) return; setStatus({mode: "error", message: noPermissionsMessage}); } else { const device = event.level[props.deviceId]; if (!device) { if (status.mode === "loading") { return; } setStatus({mode: "loading"}); } else if (device.status === "success") { if (status.mode !== "success") { setStatus({mode: "success"}); } refHider.current.style.width = (100 - device.level) + "%"; } else { if (status.mode === "error" && status.message === device.error) return; setStatus({mode: "error", message: device.error + ""}); } } }, true, [status]); let error; switch (status.mode) { case "error": error =
{status.message}
; break; case "loading": error =
Loading 
; break; case "success": error = undefined; break; } return (
{error}
) }; const Microphone = (props: { events: Registry, device: MicrophoneDevice, state: MicrophoneSelectedState, onClick: () => void }) => { return (
{props.device.driver}
{props.device.name}
{props.device.id === IDevice.NoDeviceId ? undefined : }
); }; 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 }) => { if (props.bySystem) { return ( ); } else { return ( ); } } const MicrophoneList = (props: { events: Registry }) => { const [state, setState] = useState(() => { props.events.fire("query_devices"); return {type: "loading"}; }); const [selectedDevice, setSelectedDevice] = useState<{ deviceId: string, mode: "selected" | "selecting" }>(); const [deviceList, setDeviceList] = useState([]); props.events.reactUse("notify_devices", event => { setSelectedDevice(undefined); switch (event.status) { case "success": setDeviceList(event.devices.slice(0)); setState({type: "normal"}); setSelectedDevice({mode: "selected", deviceId: event.selectedDevice}); break; case "error": setState({type: "error", message: event.error || tr("Unknown error")}); break; case "audio-not-initialized": setState({type: "audio-not-initialized"}); break; case "no-permissions": setState({type: "no-permissions", bySystem: event.shouldAsk}); break; } }); props.events.reactUse("action_set_selected_device", event => { setSelectedDevice({mode: "selecting", deviceId: event.deviceId}); }); props.events.reactUse("action_set_selected_device_result", event => { if (event.status === "error") createErrorModal(tr("Failed to select microphone"), tra("Failed to select microphone:\n{}", event.error)).open(); setSelectedDevice({mode: "selected", deviceId: event.deviceId}); }); return (
Please grant access to your microphone.
props.events.fire("action_request_permissions")} /> { if (state.type !== "normal" || selectedDevice?.mode === "selecting") return; props.events.fire("action_set_selected_device", {deviceId: IDevice.NoDeviceId}); }} /> {deviceList.map(e => { if (state.type !== "normal" || selectedDevice?.mode === "selecting") return; props.events.fire("action_set_selected_device", {deviceId: e.id}); }} />)}
) } const ListRefreshButton = (props: { events: Registry }) => { const [updateTimeout, setUpdateTimeout] = useState(Date.now() + 2000); useEffect(() => { if (updateTimeout === 0) return; const id = setTimeout(() => { setUpdateTimeout(0); }, Math.max(updateTimeout - Date.now(), 0)); return () => clearTimeout(id); }); props.events.reactUse("query_devices", () => setUpdateTimeout(Date.now() + 2000)); return ; } const VolumeSettings = (props: { events: Registry }) => { const refSlider = useRef(); const [value, setValue] = useState<"loading" | number>(() => { props.events.fire("query_setting", {setting: "volume"}); return "loading"; }) props.events.reactUse("notify_setting", event => { if (event.setting !== "volume") return; refSlider.current?.setState({value: event.value}); setValue(event.value); }); return (
Volume props.events.fire("action_set_setting", {setting: "volume", value: value})} />
) }; const PPTKeyButton = React.memo((props: { events: Registry }) => { const [key, setKey] = useState<"loading" | KeyDescriptor>(() => { props.events.fire("query_setting", {setting: "ppt-key"}); return "loading"; }); const [isActive, setActive] = useState(false); props.events.reactUse("notify_setting", event => { if (event.setting === "vad-type") setActive(event.value === "push_to_talk"); else if (event.setting === "ppt-key") setKey(event.value); }); if (key === "loading") { return ; } else { return ; } }); const PPTDelaySettings = (props: { events: Registry }) => { const [delayActive, setDelayActive] = useState<"loading" | boolean>(() => { props.events.fire("query_setting", {setting: "ppt-release-delay"}); return "loading"; }); const [delay, setDelay] = useState<"loading" | number>(() => { props.events.fire("query_setting", {setting: "ppt-release-delay-active"}); return "loading"; }); const [isActive, setActive] = useState(false); props.events.reactUse("notify_setting", event => { if (event.setting === "vad-type") setActive(event.value === "push_to_talk"); else if (event.setting === "ppt-release-delay") setDelay(event.value); else if (event.setting === "ppt-release-delay-active") setDelayActive(event.value); }); return (
{ props.events.fire("action_set_setting", {setting: "ppt-release-delay-active", value: value}) }} disabled={!isActive} value={delayActive === true} label={Delay on Push to Talk} /> { if (event.target.value === "") { const target = event.target; setImmediate(() => target.value = ""); return; } const newValue = event.target.valueAsNumber; if (isNaN(newValue)) return; if (newValue < 0 || newValue > 4000) return; props.events.fire("action_set_setting", {setting: "ppt-release-delay", value: newValue}); }} />} />
); } const RNNoiseLabel = () => ( more info ) const RNNoiseSettings = (props: { events: Registry }) => { if(__build.target === "web") { return null; } const [ enabled, setEnabled ] = useState(() => { props.events.fire("query_setting", { setting: "rnnoise" }); return "loading"; }); props.events.reactUse("notify_setting", event => event.setting === "rnnoise" && setEnabled(event.value)); return ( } disabled={enabled === "loading"} value={enabled === true} onChange={value => props.events.fire("action_set_setting", { setting: "rnnoise", value: value })} /> ) } const VadSelector = (props: { events: Registry }) => { const [selectedType, setSelectedType] = useState(() => { props.events.fire("query_setting", {setting: "vad-type"}); return "loading"; }); props.events.reactUse("notify_setting", event => { if (event.setting !== "vad-type") return; setSelectedType(event.value); }); return (
{ props.events.fire("action_set_setting", {setting: "vad-type", value: "push_to_talk"}) }} selected={selectedType === "push_to_talk"} disabled={selectedType === "loading"} > Push to Talk
{ props.events.fire("action_set_setting", {setting: "vad-type", value: "threshold"}) }} selected={selectedType === "threshold"} disabled={selectedType === "loading"} > Voice activity detection
{ props.events.fire("action_set_setting", {setting: "vad-type", value: "active"}) }} selected={selectedType === "active"} disabled={selectedType === "loading"} > Always active
); } const ThresholdSelector = (props: { events: Registry }) => { const refSlider = useRef(); const [value, setValue] = useState<"loading" | number>(() => { props.events.fire("query_setting", {setting: "threshold-threshold"}); return "loading"; }); const [currentDevice, setCurrentDevice] = useState(undefined); const [isActive, setActive] = useState(false); props.events.reactUse("notify_setting", event => { if (event.setting === "threshold-threshold") { refSlider.current?.setState({value: event.value}); setValue(event.value); } else if (event.setting === "vad-type") { setActive(event.value === "threshold"); } }); props.events.reactUse("notify_devices", event => { setCurrentDevice(event.selectedDevice); }); props.events.reactUse("action_set_selected_device_result", event => { setCurrentDevice(event.deviceId); }); return (
{ props.events.fire("action_set_setting", {setting: "threshold-threshold", value: value}) }} />
) }; const HelpText0 = () => ( Firstly we need to setup a microphone.
Let me guide you thru the basic UI elements.

To continue click anywhere on the screen.
); const HelpText1 = () => ( All your available microphones are listed within this box.

The currently selected microphone
is marked with a green checkmark. To change the selected microphone
just click on the new one.

To continue click anywhere on the screen.
); const HelpText2 = () => ( TeaSpeak has three voice activity detection types:
  1. To transmit audio data you'll have to
    press a key. The key could be selected via the button right to the radio button
  2. 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
  3. Continuously transmit any audio data.
Now you're ready to configure your microphone.
Just click anywhere on the screen.
); export const MicrophoneSettings = (props: { events: Registry }) => { const [highlighted, setHighlighted] = useState(() => { props.events.fire("query_help"); return undefined; }); props.events.reactUse("notify_highlight", event => setHighlighted(event.field)); return ( props.events.fire("action_help_click")} classList={cssStyle.highlightContainer}> ); }