From a4b9897eaf70c02083e827ba1e4a037a67849442 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Wed, 22 Jul 2026 14:56:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(voice):=20=E9=BA=A6=E5=85=8B=E9=A3=8E?= =?UTF-8?q?=E6=89=93=E4=B8=8D=E5=BC=80=E6=97=B6=E7=BB=99=E5=87=86=E7=A1=AE?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E2=80=94=E2=80=94=E5=A4=9A=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=E5=B0=9D=E8=AF=95=20+=20=E6=97=A0=E8=AE=BE=E5=A4=87=E9=A2=84?= =?UTF-8?q?=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 无麦克风设备(如 Mac Studio/Mini 无内置麦)时,getUserMedia 会以 OverconstrainedError "Invalid constraint" 误导报错;先 enumerateDevices 预检 audioinput,没有就直说"没检测到麦克风" - getUserMedia 依次试 {audio:true}/{audio:{}}/带回声消除,全败则暴露真实错误名; NotAllowedError 单独提示去系统设置授权 Co-Authored-By: Claude Opus 4.8 --- sundynix-desktop/frontend/src/lib/voice.ts | 41 ++++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/sundynix-desktop/frontend/src/lib/voice.ts b/sundynix-desktop/frontend/src/lib/voice.ts index 7508c18..f97d779 100644 --- a/sundynix-desktop/frontend/src/lib/voice.ts +++ b/sundynix-desktop/frontend/src/lib/voice.ts @@ -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();