refactor(CloneModal): 提取录音Hook
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { useState, useRef, useCallback, useEffect } from "react"
|
||||
import { MAX_RECORD_SECONDS } from "../constants"
|
||||
import { formatRecordTime } from "../utils"
|
||||
|
||||
/**
|
||||
* 录音 Hook
|
||||
* 封装浏览器 MediaRecorder 录音逻辑,含计时、自动停止、时长上限
|
||||
*/
|
||||
export const useAudioRecorder = (onError?: (msg: string) => void) => {
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [recordTime, setRecordTime] = useState(0)
|
||||
const [recordedBlob, setRecordedBlob] = useState<Blob | null>(null)
|
||||
|
||||
const recordTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const mediaRecorderRef = useRef<MediaRecorder | null>(null)
|
||||
const audioChunksRef = useRef<Blob[]>([])
|
||||
|
||||
/** 重置录音状态 */
|
||||
const reset = useCallback(() => {
|
||||
setIsRecording(false)
|
||||
setRecordTime(0)
|
||||
setRecordedBlob(null)
|
||||
audioChunksRef.current = []
|
||||
if (recordTimerRef.current) {
|
||||
clearInterval(recordTimerRef.current)
|
||||
recordTimerRef.current = null
|
||||
}
|
||||
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
||||
mediaRecorderRef.current.stop()
|
||||
}
|
||||
mediaRecorderRef.current = null
|
||||
}, [])
|
||||
|
||||
/** 开始/停止录制 */
|
||||
const toggleRecord = useCallback(async () => {
|
||||
if (isRecording) {
|
||||
// 停止录制
|
||||
setIsRecording(false)
|
||||
if (recordTimerRef.current) {
|
||||
clearInterval(recordTimerRef.current)
|
||||
recordTimerRef.current = null
|
||||
}
|
||||
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
||||
mediaRecorderRef.current.stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 开始录制
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
})
|
||||
const mediaRecorder = new MediaRecorder(stream)
|
||||
mediaRecorderRef.current = mediaRecorder
|
||||
audioChunksRef.current = []
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
audioChunksRef.current.push(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
const blob = new Blob(audioChunksRef.current, { type: "audio/webm" })
|
||||
setRecordedBlob(blob)
|
||||
// 停止音轨
|
||||
stream.getTracks().forEach((track) => track.stop())
|
||||
}
|
||||
|
||||
mediaRecorder.start()
|
||||
setIsRecording(true)
|
||||
setRecordTime(0)
|
||||
setRecordedBlob(null)
|
||||
|
||||
recordTimerRef.current = setInterval(() => {
|
||||
setRecordTime((prev) => {
|
||||
const next = prev + 1
|
||||
if (next >= MAX_RECORD_SECONDS) {
|
||||
// 达到时长上限,自动停止
|
||||
setTimeout(() => {
|
||||
setIsRecording(false)
|
||||
if (recordTimerRef.current) {
|
||||
clearInterval(recordTimerRef.current)
|
||||
recordTimerRef.current = null
|
||||
}
|
||||
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
||||
mediaRecorderRef.current.stop()
|
||||
}
|
||||
onError?.("已达最长录制时长(5分钟),已自动停止")
|
||||
}, 0)
|
||||
return MAX_RECORD_SECONDS
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, 1000)
|
||||
} catch {
|
||||
onError?.("无法访问麦克风,请检查浏览器权限设置")
|
||||
}
|
||||
}, [isRecording, onError])
|
||||
|
||||
// 组件卸载时清理
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (recordTimerRef.current) clearInterval(recordTimerRef.current)
|
||||
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
||||
mediaRecorderRef.current.stop()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
isRecording,
|
||||
recordTime,
|
||||
recordedBlob,
|
||||
formattedTime: formatRecordTime(recordTime),
|
||||
toggleRecord,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user