From 7fe0f6bf7049316556e2af5392900c376810f9d8 Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 23 Feb 2025 16:41:01 +0800 Subject: [PATCH 1/3] feat: add record audio --- scripts/build.sh | 0 web/package.json | 5 +- .../ActionButton/RecordAudioButton.tsx | 79 +++++++++++++++++++ web/src/components/MemoEditor/index.tsx | 2 + web/src/components/MemoResource.tsx | 7 +- web/src/locales/en.json | 5 +- web/src/locales/zh-Hans.json | 3 +- 7 files changed, 95 insertions(+), 6 deletions(-) mode change 100644 => 100755 scripts/build.sh create mode 100644 web/src/components/MemoEditor/ActionButton/RecordAudioButton.tsx diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 diff --git a/web/package.json b/web/package.json index 472e225a..6a369eed 100644 --- a/web/package.json +++ b/web/package.json @@ -81,5 +81,6 @@ "protobufjs": "^7.4.0", "typescript": "^5.7.3", "vite": "^6.0.6" - } -} \ No newline at end of file + }, + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" +} diff --git a/web/src/components/MemoEditor/ActionButton/RecordAudioButton.tsx b/web/src/components/MemoEditor/ActionButton/RecordAudioButton.tsx new file mode 100644 index 00000000..62d4e7ec --- /dev/null +++ b/web/src/components/MemoEditor/ActionButton/RecordAudioButton.tsx @@ -0,0 +1,79 @@ +import { Button } from "@usememos/mui"; +import { MicIcon, StopCircleIcon } from "lucide-react"; +import { useCallback, useContext, useState } from "react"; +import toast from "react-hot-toast"; +import { useTranslate } from "@/utils/i18n"; +import { MemoEditorContext } from "../types"; +import { Resource } from "@/types/proto/api/v1/resource_service"; +import { useResourceStore } from "@/store/v1"; + +const RecordAudioButton = () => { + const t = useTranslate(); + const context = useContext(MemoEditorContext); + const resourceStore = useResourceStore(); + const [isRecording, setIsRecording] = useState(false); + const [mediaRecorder, setMediaRecorder] = useState(null); + + const startRecording = useCallback(async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + const recorder = new MediaRecorder(stream); + const chunks: BlobPart[] = []; + + recorder.ondataavailable = (e) => chunks.push(e.data); + recorder.onstop = async () => { + const blob = new Blob(chunks, { type: "audio/webm" }); + const buffer = new Uint8Array(await blob.arrayBuffer()); + + try { + const resource = await resourceStore.createResource({ + resource: Resource.fromPartial({ + filename: `recording-${new Date().getTime()}.webm`, + type: "audio/webm", + size: buffer.length, + content: buffer + }), + }); + context.setResourceList([...context.resourceList, resource]); + } catch (error: any) { + console.error(error); + toast.error(error.details); + } + + stream.getTracks().forEach(track => track.stop()); + }; + + recorder.start(); + setMediaRecorder(recorder); + setIsRecording(true); + } catch (error) { + console.error(error); + toast.error("无法访问麦克风"); + } + }, [context, resourceStore]); + + const stopRecording = useCallback(() => { + if (mediaRecorder) { + mediaRecorder.stop(); + setMediaRecorder(null); + setIsRecording(false); + } + }, [mediaRecorder]); + + return ( + + ); +}; + +export default RecordAudioButton; \ No newline at end of file diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 2e0bd384..067f2723 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -33,6 +33,7 @@ import RelationListView from "./RelationListView"; import ResourceListView from "./ResourceListView"; import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers"; import { MemoEditorContext } from "./types"; +import RecordAudioButton from "./ActionButton/RecordAudioButton"; export interface Props { className?: string; @@ -466,6 +467,7 @@ const MemoEditor = observer((props: Props) => { + {workspaceMemoRelatedSetting.enableLocation && ( = (props: Props) => { return (
{resource.type.startsWith("audio") ? ( - +