From 49c0975fdc942c242c46c91156c16186ed215e8d Mon Sep 17 00:00:00 2001 From: Pine Date: Tue, 18 Aug 2026 08:06:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20AudioWorklet=20?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20Blob=20URL=20=E5=85=BC=E5=AE=B9=E6=80=A7=E5=92=8C=E5=A4=9A?= =?UTF-8?q?=E5=9F=BA=E5=9D=80=E5=9B=9E=E9=80=80=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/voice/s2s-ws-client.js | 40 ++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) 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", {