Files
CamTalk/frontend/src/components/MicManager/index.tsx
2026-06-14 18:10:58 +08:00

77 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// MicManager — 麦克风音频采集
// 职责:获取麦克风 MediaStream供 VAD 和音频录制使用
// ============================================================
import { useCallback, useRef, useState } from "react";
import { useI18n } from "../../lib/i18n";
// Electron API 类型
interface ElectronAPI {
requestMicAccess: () => Promise<boolean>;
isElectron: boolean;
}
declare global {
interface Window {
electronAPI?: ElectronAPI;
}
}
export function useMicrophone() {
const { t } = useI18n();
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 {
// Electron 环境:先通过 IPC 请求系统级权限
if (window.electronAPI?.isElectron) {
const granted = await window.electronAPI.requestMicAccess();
if (!granted) {
const msg = t("error.micAccess");
setError(msg);
console.warn("[Mic] macOS 系统麦克风权限被拒绝");
return null;
}
}
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 : t("error.micAccess");
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 };
}