Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: recorder settings #1004

Merged
merged 3 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion enjoy/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 4 additions & 1 deletion enjoy/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -739,5 +739,8 @@
"logs": "日志文件,帮助开发者调试问题",
"cache": "缓存文件,会自动清理。"
},
"recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。"
"recordingIsTooLongToAssess": "录音时长过长,无法评估。最长支持 1 分钟。",
"recorderConfig": "录音设置",
"recorderConfigSaved": "录音设置已保存",
"recorderConfigDescription": "调整录音高级设置"
}
2 changes: 1 addition & 1 deletion enjoy/src/main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

Expand Down
2 changes: 2 additions & 0 deletions enjoy/src/renderer/components/preferences/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ export * from "./proxy-settings";

export * from "./whisper-model-options";
export * from "./network-state";

export * from "./recorder-settings";
3 changes: 3 additions & 0 deletions enjoy/src/renderer/components/preferences/preferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
NativeLanguageSettings,
LearningLanguageSettings,
NetworkState,
RecorderSettings,
} from "@renderer/components";
import { useState } from "react";
import { Tooltip } from "react-tooltip";
Expand Down Expand Up @@ -59,6 +60,8 @@ export const Preferences = () => {
<Separator />
<NetworkState />
<Separator />
<RecorderSettings />
<Separator />
<ResetSettings />
<Separator />
<ResetAllSettings />
Expand Down
172 changes: 172 additions & 0 deletions enjoy/src/renderer/components/preferences/recorder-settings.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
28 changes: 28 additions & 0 deletions enjoy/src/renderer/context/app-settings-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -177,13 +180,36 @@ 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();
fetchLibraryPath();
fetchLanguages();
fetchProxyConfig();
initSentry();
fetchRecorderConfig();
}, []);

useEffect(() => {
Expand Down Expand Up @@ -238,6 +264,8 @@ export const AppSettingsProvider = ({
initialized: Boolean(user && libraryPath),
ahoy,
cable,
recorderConfig,
setRecorderConfig: setRecorderConfigHandler,
ipaMappings,
}}
>
Expand Down
6 changes: 4 additions & 2 deletions enjoy/src/renderer/context/media-player-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const MediaPlayerProvider = ({
children: React.ReactNode;
}) => {
const minPxPerSec = 150;
const { EnjoyApp, webApi, learningLanguage } = useContext(
const { EnjoyApp, learningLanguage, recorderConfig } = useContext(
AppSettingsProviderContext
);

Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions enjoy/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,11 @@ type DiskUsageType = {
path: string;
size: number;
}[];

type RecorderConfigType = {
autoGainControl: boolean;
echoCancellation: boolean;
noiseSuppression: boolean;
sampleRate: number;
sampleSize: number;
};
Loading