import { useCallback, useEffect, useRef, useState } from 'react'; interface AudioActivityConfig { threshold?: number; // 音频阈值,默认 30 sampleRate?: number; // 采样率,默认 100ms smoothingTimeConstant?: number; // 平滑常数,默认 0.8 fftSize?: number; // FFT 大小,默认 1024 } interface UseAudioActivityDetectionReturn { isDetecting: boolean; isSpeaking: boolean; audioLevel: number; startDetection: (stream: MediaStream) => void; stopDetection: () => void; error: string | null; } export function useAudioActivityDetection( config: AudioActivityConfig = {} ): UseAudioActivityDetectionReturn { const { threshold = 30, sampleRate = 100, smoothingTimeConstant = 0.8, fftSize = 1024, } = config; const [isDetecting, setIsDetecting] = useState(false); const [isSpeaking, setIsSpeaking] = useState(false); const [audioLevel, setAudioLevel] = useState(0); const [error, setError] = useState(null); const audioContextRef = useRef(null); const analyserRef = useRef(null); const microphoneRef = useRef(null); const animationFrameRef = useRef(null); const intervalRef = useRef(null); // 音频分析函数 const analyzeAudio = useCallback(() => { if (!analyserRef.current) return; const analyser = analyserRef.current; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); const checkAudioLevel = () => { if (!analyser) return; analyser.getByteFrequencyData(dataArray); // 计算平均音量 let sum = 0; for (let i = 0; i < bufferLength; i++) { sum += dataArray[i]; } const average = sum / bufferLength; setAudioLevel(average); // 判断是否在说话 const currentlySpeaking = average > threshold; setIsSpeaking(currentlySpeaking); // 调试日志 if (process.env.NODE_ENV === 'development') { // console.log('Audio Level:', average, 'Speaking:', currentlySpeaking); } }; // 使用定时器而不是 requestAnimationFrame 以确保稳定的采样率 intervalRef.current = setInterval(checkAudioLevel, sampleRate); }, [threshold, sampleRate]); // 开始检测 const startDetection = useCallback(async (stream: MediaStream) => { try { setError(null); // 创建音频上下文 if (!audioContextRef.current) { audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)(); } const audioContext = audioContextRef.current; // 确保音频上下文处于运行状态 if (audioContext.state === 'suspended') { await audioContext.resume(); } // 创建分析器 const analyser = audioContext.createAnalyser(); analyser.smoothingTimeConstant = smoothingTimeConstant; analyser.fftSize = fftSize; // 创建音频源 const microphone = audioContext.createMediaStreamSource(stream); microphone.connect(analyser); analyserRef.current = analyser; microphoneRef.current = microphone; setIsDetecting(true); analyzeAudio(); console.log('音频活动检测已启动'); } catch (err) { console.error('启动音频检测失败:', err); setError(err instanceof Error ? err.message : '启动音频检测失败'); } }, [analyzeAudio, smoothingTimeConstant, fftSize]); // 停止检测 const stopDetection = useCallback(() => { // 清除定时器 if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } // 清除动画帧 if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = null; } // 断开音频连接 if (microphoneRef.current) { microphoneRef.current.disconnect(); microphoneRef.current = null; } // 清理分析器 analyserRef.current = null; // 关闭音频上下文 if (audioContextRef.current && audioContextRef.current.state !== 'closed') { audioContextRef.current.close(); audioContextRef.current = null; } setIsDetecting(false); setIsSpeaking(false); setAudioLevel(0); setError(null); console.log('音频活动检测已停止'); }, []); // 组件卸载时清理 useEffect(() => { return () => { stopDetection(); }; }, [stopDetection]); return { isDetecting, isSpeaking, audioLevel, startDetection, stopDetection, error, }; }