From 9cfd058671ed9e345261b8473ed46721ca7a6276 Mon Sep 17 00:00:00 2001 From: an-lee Date: Wed, 21 Aug 2024 16:30:42 +0800 Subject: [PATCH] Feat: recorder settings (#1004) * add recorder settings * update locales * add description --- enjoy/src/i18n/en.json | 5 +- enjoy/src/i18n/zh-CN.json | 5 +- enjoy/src/main/settings.ts | 2 +- .../renderer/components/preferences/index.ts | 2 + .../components/preferences/preferences.tsx | 3 + .../preferences/recorder-settings.tsx | 172 ++++++++++++++++++ .../context/app-settings-provider.tsx | 28 +++ .../context/media-player-provider.tsx | 6 +- enjoy/src/types/index.d.ts | 8 + 9 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 enjoy/src/renderer/components/preferences/recorder-settings.tsx diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 722d91ba..d059c087 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -739,5 +739,8 @@ "logs": "Contains some logs helpful for debugging.", "cache": "Contains cached files. They will be cleaned up automatically." }, - "recordingIsTooLongToAssess": "Recording is too long to assess. The maximum duration is 60 seconds." + "recordingIsTooLongToAssess": "Recording is too long to assess. The maximum duration is 60 seconds.", + "recorderConfig": "Recorder config", + "recorderConfigSaved": "Recorder config saved", + "recorderConfigDescription": "Advanced settings for recorder" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 1940de12..c50d02e4 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -739,5 +739,8 @@ "logs": "日志文件,帮助开发者调试问题", "cache": "缓存文件,会自动清理。" }, - "recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。" + "recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。", + "recorderConfig": "录音设置", + "recorderConfigSaved": "录音设置已保存", + "recorderConfigDescription": "调整录音高级设置" } diff --git a/enjoy/src/main/settings.ts b/enjoy/src/main/settings.ts index 8cb2b3bb..05df2629 100644 --- a/enjoy/src/main/settings.ts +++ b/enjoy/src/main/settings.ts @@ -182,7 +182,7 @@ export default { return settings.setSync("defaultHotkeys", records); }); - ipcMain.handle("settings-get-api-url", (_event, url) => { + ipcMain.handle("settings-get-api-url", (_event) => { return settings.getSync("apiUrl"); }); diff --git a/enjoy/src/renderer/components/preferences/index.ts b/enjoy/src/renderer/components/preferences/index.ts index b6355c1a..53a9d1fa 100644 --- a/enjoy/src/renderer/components/preferences/index.ts +++ b/enjoy/src/renderer/components/preferences/index.ts @@ -29,3 +29,5 @@ export * from "./proxy-settings"; export * from "./whisper-model-options"; export * from "./network-state"; + +export * from "./recorder-settings"; \ No newline at end of file diff --git a/enjoy/src/renderer/components/preferences/preferences.tsx b/enjoy/src/renderer/components/preferences/preferences.tsx index 59a52f78..95179f23 100644 --- a/enjoy/src/renderer/components/preferences/preferences.tsx +++ b/enjoy/src/renderer/components/preferences/preferences.tsx @@ -17,6 +17,7 @@ import { NativeLanguageSettings, LearningLanguageSettings, NetworkState, + RecorderSettings, } from "@renderer/components"; import { useState } from "react"; import { Tooltip } from "react-tooltip"; @@ -59,6 +60,8 @@ export const Preferences = () => { + + diff --git a/enjoy/src/renderer/components/preferences/recorder-settings.tsx b/enjoy/src/renderer/components/preferences/recorder-settings.tsx new file mode 100644 index 00000000..efe63732 --- /dev/null +++ b/enjoy/src/renderer/components/preferences/recorder-settings.tsx @@ -0,0 +1,172 @@ +import { t } from "i18next"; +import { + Button, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + Input, + Switch, + toast, +} from "@renderer/components/ui"; +import { AppSettingsProviderContext } from "@renderer/context"; +import { useContext, useState } from "react"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; + +export const RecorderSettings = () => { + const [editing, setEditing] = useState(false); + const { recorderConfig, setRecorderConfig } = useContext( + AppSettingsProviderContext + ); + + const recorderConfigSchema = z.object({ + autoGainControl: z.boolean(), + echoCancellation: z.boolean(), + noiseSuppression: z.boolean(), + sampleRate: z.number(), + sampleSize: z.number(), + }); + + const form = useForm>({ + resolver: zodResolver(recorderConfigSchema), + values: recorderConfig, + }); + + const onSubmit = async (data: z.infer) => { + setRecorderConfig({ + ...recorderConfig, + ...data, + }) + .then(() => toast.success(t("recorderConfigSaved"))) + .finally(() => setEditing(false)); + }; + + return ( +
+ +
+
+
{t("recorderConfig")}
+
+ {t("recorderConfigDescription")} +
+
+ ( + +
+ + autoGainControl + + + + +
+
+ )} + /> + ( + +
+ + echoCancellation + + + + +
+
+ )} + /> + ( + +
+ + noiseSuppression + + + + +
+
+ )} + /> + ( + +
+ sampleRate + + + +
+
+ )} + /> + ( + +
+ sampleSize + + + +
+
+ )} + /> +
+
+
+ + +
+
+
+ + ); +}; diff --git a/enjoy/src/renderer/context/app-settings-provider.tsx b/enjoy/src/renderer/context/app-settings-provider.tsx index 3b5e599b..35a58749 100644 --- a/enjoy/src/renderer/context/app-settings-provider.tsx +++ b/enjoy/src/renderer/context/app-settings-provider.tsx @@ -29,6 +29,8 @@ type AppSettingsProviderState = { setProxy?: (config: ProxyConfigType) => Promise; cable?: Consumer; ahoy?: typeof ahoy; + recorderConfig?: RecorderConfigType; + setRecorderConfig?: (config: RecorderConfigType) => Promise; // remote config ipaMappings?: { [key: string]: string }; }; @@ -58,6 +60,7 @@ export const AppSettingsProvider = ({ const [learningLanguage, setLearningLanguage] = useState("en-US"); const [proxy, setProxy] = useState(); const EnjoyApp = window.__ENJOY_APP__; + const [recorderConfig, setRecorderConfig] = useState(); const [ipaMappings, setIpaMappings] = useState<{ [key: string]: string }>( IPA_MAPPINGS ); @@ -177,6 +180,28 @@ export const AppSettingsProvider = ({ setCable(consumer); }; + const fetchRecorderConfig = async () => { + const config = await EnjoyApp.settings.get("recorderConfig"); + if (config) { + setRecorderConfig(config); + } else { + const defaultConfig: RecorderConfigType = { + autoGainControl: true, + echoCancellation: true, + noiseSuppression: true, + sampleRate: 16000, + sampleSize: 16, + }; + setRecorderConfigHandler(defaultConfig); + } + }; + + const setRecorderConfigHandler = async (config: RecorderConfigType) => { + return EnjoyApp.settings.set("recorderConfig", config).then(() => { + setRecorderConfig(config); + }); + }; + useEffect(() => { fetchVersion(); fetchUser(); @@ -184,6 +209,7 @@ export const AppSettingsProvider = ({ fetchLanguages(); fetchProxyConfig(); initSentry(); + fetchRecorderConfig(); }, []); useEffect(() => { @@ -238,6 +264,8 @@ export const AppSettingsProvider = ({ initialized: Boolean(user && libraryPath), ahoy, cable, + recorderConfig, + setRecorderConfig: setRecorderConfigHandler, ipaMappings, }} > diff --git a/enjoy/src/renderer/context/media-player-provider.tsx b/enjoy/src/renderer/context/media-player-provider.tsx index c908988f..d2fcd8a8 100644 --- a/enjoy/src/renderer/context/media-player-provider.tsx +++ b/enjoy/src/renderer/context/media-player-provider.tsx @@ -138,7 +138,7 @@ export const MediaPlayerProvider = ({ children: React.ReactNode; }) => { const minPxPerSec = 150; - const { EnjoyApp, webApi, learningLanguage } = useContext( + const { EnjoyApp, learningLanguage, recorderConfig } = useContext( AppSettingsProviderContext ); @@ -207,7 +207,9 @@ export const MediaPlayerProvider = ({ isPaused, recordingTime, mediaRecorder, - } = useAudioRecorder(); + } = useAudioRecorder(recorderConfig, (exception) => { + toast.error(exception.message); + }); const { segment, createSegment } = useSegments({ targetId: media?.id, diff --git a/enjoy/src/types/index.d.ts b/enjoy/src/types/index.d.ts index 45d2f0a3..fdc7e127 100644 --- a/enjoy/src/types/index.d.ts +++ b/enjoy/src/types/index.d.ts @@ -189,3 +189,11 @@ type DiskUsageType = { path: string; size: number; }[]; + +type RecorderConfigType = { + autoGainControl: boolean; + echoCancellation: boolean; + noiseSuppression: boolean; + sampleRate: number; + sampleSize: number; +};