v3: hardened opencv.js loader — fetch with download progress, robust runtime detection, 45s failover, retryable
This commit is contained in:
+55
-21
@@ -26,32 +26,66 @@ export function isCvReady() { return !!(_cv && _cv.Mat); }
|
|||||||
export function getCv() { return _cv; }
|
export function getCv() { return _cv; }
|
||||||
export function _injectCv(m) { _cv = m; } // test hook (node)
|
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 (isCvReady()) return Promise.resolve(_cv);
|
||||||
if (_loading) return _loading;
|
if (_loading) return _loading;
|
||||||
_loading = new Promise((resolve, reject) => {
|
_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');
|
const s = document.createElement('script');
|
||||||
s.src = url;
|
s.src = src;
|
||||||
s.async = true;
|
s.onload = res;
|
||||||
s.onerror = () => reject(new Error('opencv.js failed to load'));
|
s.onerror = () => rej(new Error('opencv.js eval failed'));
|
||||||
s.onload = () => {
|
|
||||||
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);
|
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;
|
||||||
|
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;
|
return _loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user