fix(desktop): 麦克风优雅降级 + Info.plist 加 NSMicrophoneUsageDescription

- voice.ts: startMic 前探测 navigator.mediaDevices?.getUserMedia——WKWebView(原生壳)非安全
  上下文里它可能未暴露,直接调会抛 "undefined is not an object" 崩掉;改为给可读提示
- build/darwin/Info.plist + Info.dev.plist: 加 NSMicrophoneUsageDescription,否则 macOS
  不会授权麦克风(package 后的 .app 才能弹权限)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-22 14:44:27 +08:00
parent ef2b815bd7
commit 92341be32c
3 changed files with 9 additions and 0 deletions
@@ -27,6 +27,8 @@
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
<true/> <true/>
</dict> </dict>
<key>NSMicrophoneUsageDescription</key>
<string>JARVIS 语音助手需要麦克风来听你说话。</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<string>true</string> <string>true</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
+2
View File
@@ -22,6 +22,8 @@
<string>0.1.1</string> <string>0.1.1</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0.0</string> <string>12.0.0</string>
<key>NSMicrophoneUsageDescription</key>
<string>JARVIS 语音助手需要麦克风来听你说话。</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<string>true</string> <string>true</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
@@ -196,6 +196,11 @@ export class VoiceClient {
private async startMic(): Promise<void> { private async startMic(): Promise<void> {
if (this.micNode) return; if (this.micNode) return;
// WKWebViewWails 原生壳)等非安全上下文里 navigator.mediaDevices 可能未暴露——
// 直接调用会抛 "undefined is not an object" 崩掉。先探测,给出可读提示而非崩溃。
if (!navigator.mediaDevices?.getUserMedia) {
throw new Error("此窗口暂不支持麦克风(原生壳需授权/安全上下文)。用浏览器打开 localhost:5173 可直接说话。");
}
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true }, audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
}); });