import { useState, useEffect, useRef, useCallback } from "react"; interface VoiceAssistantProps { greeting: string; } // 性能优化配置 const ANALYSER_FFT_SIZE = 128; // 降低FFT大小提升性能 const VOLUME_SENSITIVITY = 1.5; const SMOOTHING_FACTOR = 0.7; // 增加平滑系数减少突变 const BAR_COUNT = 12; // 固定柱状图数量 const VoiceAssistant = ({ greeting }: VoiceAssistantProps) => { // 状态管理 const [isListening, setIsListening] = useState(false); const [error, setError] = useState(null); // DOM元素引用 const barsRef = useRef(null); // 音频处理相关引用 const mediaStreamRef = useRef(null); const audioContextRef = useRef(null); const analyserRef = useRef(null); const animationFrameRef = useRef(null); // 重用数据数组减少内存分配 const dataArrayRef = useRef(null); const lastValuesRef = useRef(new Array(BAR_COUNT).fill(10)); // 初始化音频处理(使用useCallback避免重复创建) const initAudioPipeline = useCallback(async () => { try { mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({ audio: { noiseSuppression: true, echoCancellation: true, autoGainControl: false, }, }); const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext; audioContextRef.current = new AudioContextClass({ latencyHint: "balanced", // 平衡延迟和性能 }); analyserRef.current = audioContextRef.current.createAnalyser(); analyserRef.current.fftSize = ANALYSER_FFT_SIZE; analyserRef.current.smoothingTimeConstant = SMOOTHING_FACTOR; // 初始化数据数组 dataArrayRef.current = new Uint8Array( analyserRef.current.frequencyBinCount ); const source = audioContextRef.current.createMediaStreamSource( mediaStreamRef.current ); source.connect(analyserRef.current); startVisualization(); } catch (err) { console.error("音频初始化失败:", err); setIsListening(false); } }, []); // 优化后的可视化逻辑(直接操作DOM) const startVisualization = useCallback(() => { if (!analyserRef.current || !dataArrayRef.current) { console.error('Audio analyzer not initialized'); return; } // 获取频率数据缓冲区长度 const bufferLength = analyserRef.current.frequencyBinCount; // 初始化时创建数据数组(安全校验) if (!dataArrayRef.current || dataArrayRef.current.length !== bufferLength) { dataArrayRef.current = new Uint8Array(bufferLength); } // 定义动画帧回调 const updateBars = () => { // 1. 获取最新频率数据 analyserRef.current!.getByteFrequencyData(dataArrayRef.current!); // 2. 性能优化:批量DOM操作 const bars = barsRef.current?.children; if (!bars) return; // 3. 使用现代循环代替Array.from提升性能 for (let i = 0; i < bars.length; i++) { const bar = bars[i] as HTMLElement; // 4. 优化数据采样策略(前1/2频谱) const dataIndex = Math.floor((i / BAR_COUNT) * (bufferLength / 2)); const rawValue = (dataArrayRef.current![dataIndex] / 255) * 100 * VOLUME_SENSITIVITY; // 5. 应用指数平滑滤波 const smoothValue = Math.min(100, Math.max(10, rawValue * 0.6 + lastValuesRef.current[i] * 0.4 )); lastValuesRef.current[i] = smoothValue; // 6. 复合样式更新(减少重排) bar.style.cssText = ` height: ${smoothValue}%; transform: scaleY(${0.8 + (smoothValue / 100) * 0.6}); animation-delay: ${i * 0.1}s; `; } // 7. 使用绑定this的requestAnimationFrame animationFrameRef.current = requestAnimationFrame(updateBars.bind(this)); }; // 8. 启动动画前取消已有帧 if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } animationFrameRef.current = requestAnimationFrame(updateBars); // 9. 返回清理函数 return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, []); // 10. 移除不必要的依赖项 // 切换监听状态 const toggleListening = useCallback(async () => { if (isListening) { mediaStreamRef.current?.getTracks().forEach((track) => track.stop()); audioContextRef.current?.close(); if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } } else { await initAudioPipeline(); } setIsListening((prev) => !prev); }, [isListening, initAudioPipeline]); // 清理资源 useEffect(() => { return () => { if (isListening) { mediaStreamRef.current?.getTracks().forEach((track) => track.stop()); audioContextRef.current?.close(); if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } } }; }, [isListening]); return (
{/* 问候语 */}

{greeting}

{/* 优化后的音频波形可视化 */}
{/* 底部状态信息 */}

支持唤醒词:"魔镜魔镜"

{/* 呼吸圆点指示器 */}
{/* 扩散波纹效果 */} {isListening && (
)}
音频状态: {isListening ? "监听中" : "待机"}
{/* 错误提示 */} {error && (
{error}
)}
); }; export default VoiceAssistant;