fix(voice): 麦克风打不开时给准确提示——多约束尝试 + 无设备预检

- 无麦克风设备(如 Mac Studio/Mini 无内置麦)时,getUserMedia 会以 OverconstrainedError
  "Invalid constraint" 误导报错;先 enumerateDevices 预检 audioinput,没有就直说"没检测到麦克风"
- getUserMedia 依次试 {audio:true}/{audio:{}}/带回声消除,全败则暴露真实错误名;
  NotAllowedError 单独提示去系统设置授权

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-22 14:56:08 +08:00
parent fe5c4215f7
commit a4b9897eaf
+34 -7
View File
@@ -201,15 +201,42 @@ export class VoiceClient {
if (!navigator.mediaDevices?.getUserMedia) {
throw new Error("此窗口暂不支持麦克风(原生壳需授权/安全上下文)。用浏览器打开 localhost:5173 可直接说话。");
}
// 先试带回声消除等约束(浏览器更好),WKWebView(原生壳)不认高级约束会抛 "Invalid constraint"
// 退回最简 {audio:true}(最兼容;多数实现默认已开回声消除)
let stream: MediaStream;
// 先看有没有麦克风设备:Mac Studio/Mini 等无内置麦克风的机器上,getUserMedia 会以
// OverconstrainedError "Invalid constraint" 报错(误导),提前给准确提示
let devs: MediaDeviceInfo[] = [];
try {
stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 },
});
devs = await navigator.mediaDevices.enumerateDevices();
} catch {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
/* 拿不到设备列表就跳过预检,直接试 getUserMedia */
}
if (devs.length > 0 && !devs.some((d) => d.kind === "audioinput")) {
throw new Error("没检测到麦克风设备。这台机器(如 Mac Studio/Mini)可能无内置麦克风——插个麦克风(带麦耳机 / USB 麦 / AirPods)再试。");
}
// 依次尝试多种约束形式:WKWebView(原生壳)对约束挑剔,不同实现接受的形式不同。
// 全失败则把**真实错误名**抛出来(区分是"约束不认"Overconstrained 还是"权限被拒"NotAllowed)。
const tries: MediaStreamConstraints[] = [
{ audio: true },
{ audio: {} },
{ audio: { echoCancellation: true, noiseSuppression: true } },
];
let stream: MediaStream | null = null;
let lastErr: unknown;
for (const c of tries) {
try {
stream = await navigator.mediaDevices.getUserMedia(c);
break;
} catch (e) {
lastErr = e;
}
}
if (!stream) {
const err = lastErr as { name?: string; message?: string };
const detail = `${err?.name ?? "Error"}: ${err?.message ?? String(lastErr)}`;
if (err?.name === "NotAllowedError" || err?.name === "SecurityError") {
throw new Error("麦克风权限被拒。请到 系统设置 → 隐私与安全性 → 麦克风 里允许本应用。");
}
throw new Error("麦克风打开失败:" + detail);
}
this.micStream = stream;
const ctx = new AudioContext();