fix frontend voice preview fallback
This commit is contained in:
@@ -37,22 +37,30 @@ function generatePcId(): string {
|
||||
);
|
||||
}
|
||||
|
||||
type UseVoicePreviewOptions = {
|
||||
/** 取麦克风失败(权限/无设备)时回调,供 UI 提示。 */
|
||||
onMicError?: () => void;
|
||||
};
|
||||
|
||||
function errorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof Error && error.message) return error.message;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function useVoicePreview(
|
||||
assistantId: string | null,
|
||||
{ onMicError }: UseVoicePreviewOptions = {},
|
||||
) {
|
||||
function microphoneErrorMessage(error: unknown): string {
|
||||
if (error instanceof DOMException) {
|
||||
if (error.name === "NotAllowedError") {
|
||||
return "麦克风权限被拒绝,请在浏览器网站设置中允许 localhost 使用麦克风。";
|
||||
}
|
||||
if (error.name === "NotFoundError") {
|
||||
return "未检测到可用麦克风,请连接或启用麦克风后重试。";
|
||||
}
|
||||
if (error.name === "NotReadableError") {
|
||||
return "麦克风正被其他应用占用,或系统未允许浏览器访问麦克风。";
|
||||
}
|
||||
}
|
||||
return errorMessage(error, "无法访问麦克风。");
|
||||
}
|
||||
|
||||
export function useVoicePreview(assistantId: string | null) {
|
||||
const [status, setStatus] = useState<VoicePreviewStatus>("idle");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [micWarning, setMicWarning] = useState<string | null>(null);
|
||||
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const pcRef = useRef<RTCPeerConnection | null>(null);
|
||||
@@ -90,6 +98,7 @@ export function useVoicePreview(
|
||||
releaseResources();
|
||||
setLocalStream(null);
|
||||
setError(null);
|
||||
setMicWarning(null);
|
||||
setStatus("idle");
|
||||
}, [releaseResources]);
|
||||
|
||||
@@ -110,11 +119,33 @@ export function useVoicePreview(
|
||||
setStatus("failed");
|
||||
return;
|
||||
}
|
||||
|
||||
startingRef.current = true;
|
||||
setError(null);
|
||||
setMicWarning(null);
|
||||
setStatus("connecting");
|
||||
|
||||
// 麦克风是可选的:获取失败时继续建立仅接收后端音频的 WebRTC 会话。
|
||||
let stream: MediaStream | null = null;
|
||||
if (window.isSecureContext && navigator.mediaDevices?.getUserMedia) {
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true,
|
||||
},
|
||||
});
|
||||
} catch (mediaError) {
|
||||
setMicWarning(microphoneErrorMessage(mediaError));
|
||||
}
|
||||
} else {
|
||||
setMicWarning("当前页面无法访问麦克风,已进入仅收听模式。");
|
||||
}
|
||||
if (stream) {
|
||||
localStreamRef.current = stream;
|
||||
setLocalStream(stream);
|
||||
}
|
||||
|
||||
const pcId = generatePcId();
|
||||
const ws = new WebSocket(`${wsBaseUrl()}/ws/voice`);
|
||||
wsRef.current = ws;
|
||||
@@ -205,24 +236,12 @@ export function useVoicePreview(
|
||||
else if (st === "disconnected") fail("WebRTC 音频连接已断开。");
|
||||
};
|
||||
|
||||
// 3) 取麦克风 → 加入连接
|
||||
let stream: MediaStream;
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true,
|
||||
},
|
||||
});
|
||||
} catch (mediaError) {
|
||||
onMicError?.();
|
||||
fail(errorMessage(mediaError, "无法访问麦克风。"));
|
||||
return;
|
||||
// 3) 有麦克风时双向音频;否则明确声明只接收后端音频。
|
||||
if (stream) {
|
||||
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
|
||||
} else {
|
||||
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||
}
|
||||
localStreamRef.current = stream;
|
||||
setLocalStream(stream);
|
||||
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
|
||||
|
||||
// 4) 生成 offer 并发给后端(assistant_id 在 payload 顶层)
|
||||
const offer = await pc.createOffer();
|
||||
@@ -247,10 +266,18 @@ export function useVoicePreview(
|
||||
} finally {
|
||||
startingRef.current = false;
|
||||
}
|
||||
}, [assistantId, fail, onMicError]);
|
||||
}, [assistantId, fail]);
|
||||
|
||||
// 卸载时收尾
|
||||
useEffect(() => releaseResources, [releaseResources]);
|
||||
|
||||
return { status, error, localStream, connect, disconnect, audioRef };
|
||||
return {
|
||||
status,
|
||||
error,
|
||||
micWarning,
|
||||
localStream,
|
||||
connect,
|
||||
disconnect,
|
||||
audioRef,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user