52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
|
|
// ============================================================
|
|||
|
|
// MicManager — 麦克风音频采集
|
|||
|
|
// 职责:获取麦克风 MediaStream,供 VAD 和音频录制使用
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
import { useCallback, useRef, useState } from "react";
|
|||
|
|
|
|||
|
|
export function useMicrophone() {
|
|||
|
|
const [stream, setStream] = useState<MediaStream | null>(null);
|
|||
|
|
const [error, setError] = useState<string | null>(null);
|
|||
|
|
const audioContextRef = useRef<AudioContext | null>(null);
|
|||
|
|
|
|||
|
|
const startMic = useCallback(async () => {
|
|||
|
|
try {
|
|||
|
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
|||
|
|
audio: {
|
|||
|
|
sampleRate: 16000,
|
|||
|
|
channelCount: 1,
|
|||
|
|
echoCancellation: true,
|
|||
|
|
noiseSuppression: true,
|
|||
|
|
},
|
|||
|
|
video: false,
|
|||
|
|
});
|
|||
|
|
setStream(mediaStream);
|
|||
|
|
setError(null);
|
|||
|
|
return mediaStream;
|
|||
|
|
} catch (err) {
|
|||
|
|
const message = err instanceof Error ? err.message : "无法访问麦克风";
|
|||
|
|
setError(message);
|
|||
|
|
console.error("[Mic] 获取麦克风失败:", err);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const stopMic = useCallback(() => {
|
|||
|
|
stream?.getTracks().forEach((track) => track.stop());
|
|||
|
|
setStream(null);
|
|||
|
|
audioContextRef.current?.close();
|
|||
|
|
audioContextRef.current = null;
|
|||
|
|
}, [stream]);
|
|||
|
|
|
|||
|
|
/** 获取或创建 AudioContext */
|
|||
|
|
const getAudioContext = useCallback((): AudioContext => {
|
|||
|
|
if (!audioContextRef.current) {
|
|||
|
|
audioContextRef.current = new AudioContext({ sampleRate: 16000 });
|
|||
|
|
}
|
|||
|
|
return audioContextRef.current;
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
return { stream, error, startMic, stopMic, getAudioContext };
|
|||
|
|
}
|