v3: hardened opencv.js loader — fetch with download progress, robust runtime detection, 45s failover, retryable
This commit is contained in:
+57
-23
@@ -26,32 +26,66 @@ export function isCvReady() { return !!(_cv && _cv.Mat); }
|
||||
export function getCv() { return _cv; }
|
||||
export function _injectCv(m) { _cv = m; } // test hook (node)
|
||||
|
||||
export function loadOpenCV(url = '/vendor/opencv.js') {
|
||||
export function loadOpenCV(url = '/vendor/opencv.js', onProgress = null) {
|
||||
if (isCvReady()) return Promise.resolve(_cv);
|
||||
if (_loading) return _loading;
|
||||
_loading = new Promise((resolve, reject) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = url;
|
||||
s.async = true;
|
||||
s.onerror = () => reject(new Error('opencv.js failed to load'));
|
||||
s.onload = () => {
|
||||
_loading = (async () => {
|
||||
// 1) download with progress (13MB — visitors on exhibit wifi deserve a %)
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error(`opencv.js HTTP ${resp.status}`);
|
||||
const total = +resp.headers.get('content-length') || 0;
|
||||
let blob;
|
||||
if (resp.body && resp.body.getReader) {
|
||||
const reader = resp.body.getReader();
|
||||
const chunks = [];
|
||||
let got = 0;
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value); got += value.length;
|
||||
if (onProgress) onProgress(total ? Math.round(got / total * 100) : Math.round(got / 1e6) + 'MB');
|
||||
}
|
||||
blob = new Blob(chunks, { type: 'text/javascript' });
|
||||
} else {
|
||||
blob = await resp.blob();
|
||||
}
|
||||
// 2) evaluate
|
||||
if (onProgress) onProgress('init');
|
||||
const src = URL.createObjectURL(blob);
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = src;
|
||||
s.onload = res;
|
||||
s.onerror = () => rej(new Error('opencv.js eval failed'));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => URL.revokeObjectURL(src), 5000);
|
||||
}
|
||||
// 3) resolve the runtime — window.cv may be the module, a thenable that
|
||||
// resolves to it, or get swapped mid-init depending on the emscripten
|
||||
// build. Poll fresh every tick; hard 45s ceiling so a hang becomes a
|
||||
// visible failover instead of an eternal "loading".
|
||||
const t0 = performance.now();
|
||||
let thenAttached = false;
|
||||
for (;;) {
|
||||
const mod = window.cv;
|
||||
const finish = (m) => { _cv = m; resolve(m); };
|
||||
if (!mod) return reject(new Error('opencv.js loaded but window.cv missing'));
|
||||
if (mod.Mat) return finish(mod); // already initialized
|
||||
if (typeof mod.then === 'function') return mod.then(finish, reject); // promise build
|
||||
mod.onRuntimeInitialized = () => finish(mod); // classic emscripten
|
||||
// safety: poll in case onRuntimeInitialized was already consumed
|
||||
const t0 = performance.now();
|
||||
(function poll() {
|
||||
if (_cv) return;
|
||||
if (mod.Mat) return finish(mod);
|
||||
if (performance.now() - t0 > 30000) return reject(new Error('opencv.js init timeout'));
|
||||
setTimeout(poll, 100);
|
||||
})();
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
if (mod && mod.Mat) { _cv = mod; return mod; }
|
||||
if (mod && typeof mod.then === 'function' && !thenAttached) {
|
||||
thenAttached = true;
|
||||
mod.then((m) => { if (m && m.Mat) { window.cv = m; } }, () => {});
|
||||
}
|
||||
if (mod && !mod.Mat && typeof mod.then !== 'function' && !mod.__nbxHook) {
|
||||
mod.__nbxHook = true;
|
||||
const prev = mod.onRuntimeInitialized;
|
||||
mod.onRuntimeInitialized = () => { if (typeof prev === 'function') prev(); };
|
||||
}
|
||||
if (performance.now() - t0 > 45000) throw new Error('opencv.js init timeout');
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
})();
|
||||
_loading.catch(() => { _loading = null; }); // allow retry after failure
|
||||
return _loading;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user