Feat: recorder settings (#1004)
* add recorder settings * update locales * add description
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -739,5 +739,8 @@
|
||||
"logs": "日志文件,帮助开发者调试问题",
|
||||
"cache": "缓存文件,会自动清理。"
|
||||
},
|
||||
"recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。"
|
||||
"recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。",
|
||||
"recorderConfig": "录音设置",
|
||||
"recorderConfigSaved": "录音设置已保存",
|
||||
"recorderConfigDescription": "调整录音高级设置"
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -29,3 +29,5 @@ export * from "./proxy-settings";
|
||||
|
||||
export * from "./whisper-model-options";
|
||||
export * from "./network-state";
|
||||
|
||||
export * from "./recorder-settings";
|
||||
@@ -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 = () => {
|
||||
<Separator />
|
||||
<NetworkState />
|
||||
<Separator />
|
||||
<RecorderSettings />
|
||||
<Separator />
|
||||
<ResetSettings />
|
||||
<Separator />
|
||||
<ResetAllSettings />
|
||||
|
||||
172
enjoy/src/renderer/components/preferences/recorder-settings.tsx
Normal file
172
enjoy/src/renderer/components/preferences/recorder-settings.tsx
Normal file
@@ -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<z.infer<typeof recorderConfigSchema>>({
|
||||
resolver: zodResolver(recorderConfigSchema),
|
||||
values: recorderConfig,
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof recorderConfigSchema>) => {
|
||||
setRecorderConfig({
|
||||
...recorderConfig,
|
||||
...data,
|
||||
})
|
||||
.then(() => toast.success(t("recorderConfigSaved")))
|
||||
.finally(() => setEditing(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="flex items-start justify-between py-4">
|
||||
<div className="">
|
||||
<div className="mb-4">{t("recorderConfig")}</div>
|
||||
<div className="text-sm text-muted-foreground mb-3">
|
||||
{t("recorderConfigDescription")}
|
||||
</div>
|
||||
<div
|
||||
className={`text-sm text-muted-foreground space-y-3 ${
|
||||
editing ? "" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="autoGainControl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
autoGainControl
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
disabled={!editing}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="echoCancellation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
echoCancellation
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
disabled={!editing}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="noiseSuppression"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
noiseSuppression
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
disabled={!editing}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sampleRate"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">sampleRate</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled={!editing} {...field} />
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sampleSize"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">sampleSize</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled={!editing} {...field} />
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant={editing ? "outline" : "secondary"}
|
||||
size="sm"
|
||||
type="reset"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
form.reset();
|
||||
setEditing(!editing);
|
||||
}}
|
||||
>
|
||||
{editing ? t("cancel") : t("edit")}
|
||||
</Button>
|
||||
<Button className={editing ? "" : "hidden"} size="sm" type="submit">
|
||||
{t("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -29,6 +29,8 @@ type AppSettingsProviderState = {
|
||||
setProxy?: (config: ProxyConfigType) => Promise<void>;
|
||||
cable?: Consumer;
|
||||
ahoy?: typeof ahoy;
|
||||
recorderConfig?: RecorderConfigType;
|
||||
setRecorderConfig?: (config: RecorderConfigType) => Promise<void>;
|
||||
// remote config
|
||||
ipaMappings?: { [key: string]: string };
|
||||
};
|
||||
@@ -58,6 +60,7 @@ export const AppSettingsProvider = ({
|
||||
const [learningLanguage, setLearningLanguage] = useState<string>("en-US");
|
||||
const [proxy, setProxy] = useState<ProxyConfigType>();
|
||||
const EnjoyApp = window.__ENJOY_APP__;
|
||||
const [recorderConfig, setRecorderConfig] = useState<RecorderConfigType>();
|
||||
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,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
|
||||
8
enjoy/src/types/index.d.ts
vendored
8
enjoy/src/types/index.d.ts
vendored
@@ -189,3 +189,11 @@ type DiskUsageType = {
|
||||
path: string;
|
||||
size: number;
|
||||
}[];
|
||||
|
||||
type RecorderConfigType = {
|
||||
autoGainControl: boolean;
|
||||
echoCancellation: boolean;
|
||||
noiseSuppression: boolean;
|
||||
sampleRate: number;
|
||||
sampleSize: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user