diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index eec6a44..00cdc34 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -25,6 +25,7 @@ use std::thread; /// with ERROR_INVALID_STATE when the option sets differ). const WEBVIEW2_BROWSER_ARGS: &str = "--ignore-certificate-errors --auto-accept-camera-and-microphone-capture \ + --use-fake-ui-for-media-stream \ --disable-backgrounding-occluded-windows --disable-renderer-backgrounding \ --disable-background-timer-throttling"; diff --git a/client/src/services/audio.ts b/client/src/services/audio.ts index 6550a19..70fe6ce 100644 --- a/client/src/services/audio.ts +++ b/client/src/services/audio.ts @@ -134,19 +134,23 @@ registerProcessor('pcm-processor', PCMProcessor); export async function listMicrophones(): Promise { const audioInputs = (list: MediaDeviceInfo[]) => list.filter((d) => d.kind === 'audioinput') - // 新版 WebView2 / Chromium 收紧了设备枚举:只有在麦克风流“活跃”时, - // enumerateDevices() 才会返回设备的真实名字(label),否则只有一个无名通用项。 - // 因此这里始终先开一个临时音频流,趁流活跃时枚举,拿到完整列表后立刻关闭。 - let stream: MediaStream | null = null - try { - stream = await navigator.mediaDevices.getUserMedia({ audio: true }) - return audioInputs(await navigator.mediaDevices.enumerateDevices()) - } catch { - // 拿不到权限或没有可用麦克风:退回普通枚举(可能只有通用项) - return audioInputs(await navigator.mediaDevices.enumerateDevices()) - } finally { - if (stream) stream.getTracks().forEach((t) => t.stop()) + let devices = audioInputs(await navigator.mediaDevices.enumerateDevices()) + + // 没有持久麦克风权限时,enumerateDevices() 只返回一个无名字的通用项。 + // 此时临时打开一路音频流触发授权,趁流活跃时重新枚举,拿到真实名称。 + if (devices.length <= 1 || devices.some((d) => !d.label)) { + let stream: MediaStream | null = null + try { + stream = await navigator.mediaDevices.getUserMedia({ audio: true }) + devices = audioInputs(await navigator.mediaDevices.enumerateDevices()) + } catch { + // 权限被拒或无可用麦克风:保留已有结果 + } finally { + if (stream) stream.getTracks().forEach((t) => t.stop()) + } } + + return devices } function createAudioContext() {