M25: WS FX realtime sang Web Worker + MessageChannel worker->worklet (het underrun u1/u10 cold-start + stall main)

This commit is contained in:
2026-09-07 15:11:23 +07:00
parent a88fc5b3f1
commit 9307ecccec
5 changed files with 208 additions and 34 deletions
+52 -4
View File
@@ -2029,6 +2029,48 @@ function fxRtWsOnMessage(ev) {
for (let i = 0; i < half; i++) { l[i] = f[i * 2]; r[i] = f[i * 2 + 1]; }
fxRt.node.port.postMessage({ type: 'out', l, r });
}
// M25: shim giu nguyen API WebSocket cu (readyState/send/onopen/onmessage/
// onclose/onerror) nhung WS + binary deinterleave chay tren Web Worker; output
// di THANG worker->worklet qua MessageChannel (directPort). ~20 call-site
// fxRt.ws giu nguyen - khong sua. Main chi nhan text (latency/flush_done).
function fxRtWsConnect(wsUrl, directPort) {
const _fireClose = (sh) => {
if (sh._closed) return;
sh._closed = true;
sh.readyState = 3; // CLOSED
try { if (sh._worker) sh._worker.terminate(); } catch (e) {}
if (sh.onclose) { try { sh.onclose(); } catch (e) {} }
};
const shim = {
readyState: 0,
onopen: null,
onmessage: null,
onclose: null,
onerror: null,
_worker: null,
_closed: false,
send(data) {
if (this._closed || !this._worker || this.readyState !== 1) return;
try {
if (typeof data === 'string') this._worker.postMessage({ kind: 'send', text: data });
// KHONG transfer list - fxRtChunks c.buffer reused (ws.send cu khong neuter)
else if (data instanceof ArrayBuffer) this._worker.postMessage({ kind: 'send', buf: data });
} catch (e) {}
},
close() { _fireClose(this); }
};
try { shim._worker = new Worker(`${API_BASE_URL}/static/js/fx-rt-worker.js?v=202608291749`); } catch (e) { throw e; } // giong ws constructor that -> caller catch cu giu nguyen
shim._worker.onmessage = (ev) => {
const d = ev.data;
if (!d || shim._closed) return;
if (d.kind === 'open') { shim.readyState = 1; if (shim.onopen) { try { shim.onopen(); } catch (e) {} } return; }
if (d.kind === 'close') { _fireClose(shim); return; }
if (d.kind === 'text') { if (shim.onmessage) { try { shim.onmessage({ data: d.text }); } catch (e) {} } }
};
shim._worker.onerror = () => _fireClose(shim);
try { shim._worker.postMessage({ kind: 'init', wsUrl, port: directPort || undefined }, directPort ? [directPort] : []); } catch (e) {}
return shim;
}
async function fxRtStart() {
// V21: mọi play/chain-change gọi đây — hủy idle timer của lần stop trước
// (không để session warm bị teardown giữa lúc đang chơi).
@@ -2177,9 +2219,14 @@ async function fxRtStart() {
: u.host;
const tok = localStorage.getItem('sonic_token') || '';
const wsUrl = `${wsProto}${wsHost}${st.ws_url}${tok ? '?token=' + encodeURIComponent(tok) : ''}`;
const ws = new WebSocket(wsUrl);
// M25: WS chay tren Web Worker - binary output/input di THANG
// worker<->worklet qua MessageChannel (directPort). Main-thread stall (rAF/
// React/render luc cold start) khong con tre frame -> het underrun u1/u10.
// Text (latency/flush_done) van forward main nhu cu.
const _chan = new MessageChannel();
try { fxRt.node.port.postMessage({ type: '__direct', port: _chan.port2 }, [_chan.port2]); } catch (e) {}
const ws = fxRtWsConnect(wsUrl, _chan.port1);
fxRt.ws = ws;
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
if (token !== fxRt.startToken) { try { ws.close(); } catch (e) {} fxRt.starting = false; return; }
fxRt.active = true;
@@ -2468,9 +2515,11 @@ async function fxRtWarmEnsure(chain) {
: u.host;
const tok = localStorage.getItem('sonic_token') || '';
const wsUrl = `${wsProto}${wsHost}${st.ws_url}${tok ? '?token=' + encodeURIComponent(tok) : ''}`;
// M25: WS chay tren Web Worker - warm chua co node (khong directPort);
// worker van giu WS, binary drop trong worker nhu cu main drop (!fxRt.node).
let ws;
try {
ws = new WebSocket(wsUrl);
ws = fxRtWsConnect(wsUrl, null);
} catch (e) {
try { window.SonicAPI.fxRealtimeStop({ session_id: st.session_id }).catch(() => {}); } catch (e2) {}
fxRt.sessionId = null;
@@ -2479,7 +2528,6 @@ async function fxRtWarmEnsure(chain) {
return null;
}
fxRt.ws = ws;
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
if (token !== fxRt.startToken) {
// Bị thay bằng session mới hơn — dọn session mình vừa tạo (nếu vẫn là