Files
CamTalk/frontend/src/components/MicManager/index.tsx

77 lines
2.2 KiB
TypeScript
Raw Normal View History

// ============================================================
// MicManager — 麦克风音频采集
// 职责:获取麦克风 MediaStream供 VAD 和音频录制使用
// ============================================================
import { useCallback, useRef, useState } from "react";
import { useI18n } from "../../lib/i18n";
2026-06-14 18:10:58 +08:00
// 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 {
2026-06-14 18:10:58 +08:00
// 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 };
}