diff --git a/src/voice/s2s-ws-client.js b/src/voice/s2s-ws-client.js index d254c32..17be6a2 100644 --- a/src/voice/s2s-ws-client.js +++ b/src/voice/s2s-ws-client.js @@ -478,11 +478,11 @@ export class S2sWsRealtimeClient extends EventTarget { } } - // The worklets live at the repo root, one level up from this module. - // ?v=2 busts the browser cache so the fixed (unmuted) playback worklet - // always loads instead of a stale muted copy. - // 多基址回退:打包环境优先后端 http(tauri.localhost 无法加载 worklet 模块), - // 浏览器开发回退页面源 / 相对路径。 + // AudioWorklet 加载(WebView2/打包兼容): + // 1) 先直接 addModule(http 地址) —— 浏览器/常规环境快路径 + // 2) 失败则 fetch 源码 → Blob URL → addModule —— 打包 WebView2 下 + // tauri.localhost 自定义协议与跨域均受限,Blob 同源最稳 + // 3) 多个候选基址(后端 http / 页面源 / 相对路径) const candidates = [ window.VOICE_WORKLETS_BASE, `${window.location.origin}/voice-worklets/`, @@ -490,6 +490,27 @@ export class S2sWsRealtimeClient extends EventTarget { ].filter(Boolean); let workletErr = null; let workletOk = false; + + const addViaBlob = async (name) => { + for (const base of candidates) { + try { + const res = await fetch(base + name, { cache: "no-store" }); + if (!res.ok) continue; + const src = await res.text(); + const url = URL.createObjectURL(new Blob([src], { type: "application/javascript" })); + try { + await ctx.audioWorklet.addModule(url); + return true; + } finally { + URL.revokeObjectURL(url); + } + } catch (err) { + workletErr = err; + } + } + return false; + }; + for (const base of candidates) { try { await ctx.audioWorklet.addModule(base + "mic-capture.js?v=3"); @@ -498,9 +519,16 @@ export class S2sWsRealtimeClient extends EventTarget { break; } catch (err) { workletErr = err; - console.warn("[ws] worklet 加载失败,尝试备用地址:", base, err); + console.warn("[ws] worklet 直接加载失败,改走 Blob 方案:", base, err); } } + if (!workletOk) { + // 回退:fetch → Blob → addModule(WebView2 打包环境最稳) + const micOk = await addViaBlob("mic-capture.js?v=3"); + const playOk = await addViaBlob("audio-playback.js?v=3"); + workletOk = micOk && playOk; + if (workletOk) console.info("[ws] AudioWorklet 已通过 Blob URL 加载(WebView2 兼容模式)"); + } if (!workletOk) throw workletErr || new Error("AudioWorklet 模块加载失败"); const captureNode = new AudioWorkletNode(ctx, "mic-capture", {