From a2d1a8cb111a810eabd7077c7cd8c25fc5ea84c5 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 30 Jul 2026 10:32:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BD=95=E9=9F=B3=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E7=AB=9E=E6=80=81=E9=98=B2=E6=8A=A4+onstop=E8=AE=A1=E6=97=B6?= =?UTF-8?q?=E5=99=A8=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/CloneModal/hooks/useAudioRecorder.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/web/src/components/voice/CloneModal/hooks/useAudioRecorder.ts b/apps/web/src/components/voice/CloneModal/hooks/useAudioRecorder.ts index 37e82f3f4..ca4c8e047 100644 --- a/apps/web/src/components/voice/CloneModal/hooks/useAudioRecorder.ts +++ b/apps/web/src/components/voice/CloneModal/hooks/useAudioRecorder.ts @@ -15,6 +15,7 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { const mediaRecorderRef = useRef(null) const audioChunksRef = useRef([]) const isMountedRef = useRef(true) + const isStartingRef = useRef(false) /** 重置录音状态 */ const reset = useCallback(() => { @@ -23,6 +24,7 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { setRecordTime(0) setRecordedBlob(null) audioChunksRef.current = [] + isStartingRef.current = false if (recordTimerRef.current) { clearInterval(recordTimerRef.current) recordTimerRef.current = null @@ -48,11 +50,20 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { return } + // 防止快速双击导致重复启动 + if (isStartingRef.current) return + isStartingRef.current = true + // 开始录制 try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, }) + if (!isMountedRef.current) { + stream.getTracks().forEach((track) => track.stop()) + isStartingRef.current = false + return + } const mediaRecorder = new MediaRecorder(stream) mediaRecorderRef.current = mediaRecorder audioChunksRef.current = [] @@ -64,6 +75,10 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { } mediaRecorder.onstop = () => { + if (recordTimerRef.current) { + clearInterval(recordTimerRef.current) + recordTimerRef.current = null + } if (!isMountedRef.current) return // 使用浏览器实际生成的 MIME 类型,避免跨浏览器兼容性问题 const mimeType = mediaRecorder.mimeType || "audio/webm" @@ -71,6 +86,7 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { setRecordedBlob(blob) // 停止音轨 stream.getTracks().forEach((track) => track.stop()) + isStartingRef.current = false } mediaRecorder.start() @@ -101,6 +117,7 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { }) }, 1000) } catch { + isStartingRef.current = false onError?.("无法访问麦克风,请检查浏览器权限设置") } }, [isRecording, onError]) @@ -110,6 +127,7 @@ export const useAudioRecorder = (onError?: (msg: string) => void) => { isMountedRef.current = true return () => { isMountedRef.current = false + isStartingRef.current = false if (recordTimerRef.current) clearInterval(recordTimerRef.current) if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { mediaRecorderRef.current.stop()