Files
Ein Anderssono c9b14e0726
Build and publish frontend / publish (push) Successful in 49s
Fix Piper memory pressure on iOS
2026-10-17 16:38:16 +02:00

68 lines
2.4 KiB
JavaScript

const version = document.querySelector('#frontend-version')?.textContent || 'dev';
const workerUrl = `/tts/piper-wasm-v1/piper-worker.js?v=${encodeURIComponent(version)}`;
let worker;
let requestId = 0;
const pending = new Map();
function getWorker() {
if (worker) return worker;
// ONNX Runtime resolves its WASM loader relative to the worker module URL.
// Keep the worker as ESM so import.meta.url remains available at runtime.
worker = new Worker(workerUrl, {type: 'module'});
worker.onmessage = event => {
const message = event.data || {};
const request = pending.get(message.id);
if (!request) return;
if (message.type === 'progress') {
request.onProgress?.(message.progress);
} else {
pending.delete(message.id);
if (message.type === 'audio') {
request.resolve(new Blob([message.audio], {type: 'audio/x-wav'}));
} else {
const error = new Error(message.error || 'Piper-arbetaren misslyckades.');
request.reject(error);
// TtsSession keeps a singleton even when ONNX initialization fails.
// Discard the whole worker so a player retry starts with a clean ONNX
// module and does not reuse a poisoned WASM initialization.
for (const queued of pending.values()) queued.reject(error);
pending.clear();
worker?.terminate();
worker = null;
}
}
};
worker.onerror = event => {
const details = [event.message, event.filename && `${event.filename}:${event.lineno || 0}:${event.colno || 0}`]
.filter(Boolean)
.join(' — ');
const error = new Error(details
? `Piper-arbetaren kunde inte startas: ${details}`
: 'Piper-arbetaren kunde inte startas.');
for (const request of pending.values()) request.reject(error);
pending.clear();
worker?.terminate();
worker = null;
};
worker.onmessageerror = () => {
const error = new Error('Piper-arbetaren skickade ett oläsbart svar.');
for (const request of pending.values()) request.reject(error);
pending.clear();
worker?.terminate();
worker = null;
};
return worker;
}
window.GamePiper = {
voiceId: 'sv_SE-nst-medium',
synthesize(text, onProgress) {
return new Promise((resolve, reject) => {
const id = ++requestId;
pending.set(id, {resolve, reject, onProgress});
try { getWorker().postMessage({id, text: String(text)}); }
catch (error) { pending.delete(id); reject(error); }
});
},
};