|
3 | 3 |
|
4 | 4 | import type { OpenCVTypes } from '../opencv/interfaces'; |
5 | 5 |
|
| 6 | +const READY_CHECK_INTERVAL_MS = 100; |
| 7 | +const OPENCV_LOAD_TIMEOUT_MS = 30_000; |
| 8 | + |
6 | 9 | let opencv: OpenCVTypes | null = null; |
| 10 | +let loadingPromise: Promise<OpenCVTypes> | null = null; |
| 11 | + |
| 12 | +function delay(ms: number): Promise<void> { |
| 13 | + return new Promise((resolve) => setTimeout(resolve, ms)); |
| 14 | +} |
| 15 | + |
| 16 | +/** |
| 17 | + * Wait for cv.ready to be available with polling |
| 18 | + * Some OpenCV builds delay initialization of cv.ready |
| 19 | + */ |
| 20 | +const waitForOpenCVReady = async (cv: OpenCVTypes): Promise<void> => { |
| 21 | + const startTime = Date.now(); |
| 22 | + |
| 23 | + while (Date.now() - startTime < OPENCV_LOAD_TIMEOUT_MS) { |
| 24 | + // Check if cv.ready exists and is a Promise-like object |
| 25 | + if (cv && typeof cv.ready === 'object' && 'then' in cv.ready) { |
| 26 | + try { |
| 27 | + await cv.ready; |
| 28 | + return; // Success |
| 29 | + } catch (error) { |
| 30 | + console.error('Error waiting for cv.ready:', error); |
| 31 | + throw error; |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + // Check if cv.ready is already resolved (some builds may have it pre-resolved) |
| 36 | + if (cv && cv.onload && typeof cv.onload === 'function') { |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + // cv.ready not available yet, wait and retry |
| 41 | + await delay(READY_CHECK_INTERVAL_MS); |
| 42 | + } |
| 43 | + |
| 44 | + throw new Error( |
| 45 | + `Timeout waiting for cv.ready (${OPENCV_LOAD_TIMEOUT_MS}ms). ` + |
| 46 | + 'OpenCV may not be properly built or the file is corrupted.' |
| 47 | + ); |
| 48 | +}; |
7 | 49 |
|
8 | 50 | export const OpenCVLoader = async (): Promise<OpenCVTypes> => { |
9 | 51 | if (opencv) return opencv; |
| 52 | + if (loadingPromise) return loadingPromise; |
10 | 53 |
|
11 | | - const cv: OpenCVTypes = await import('../opencv/4.9.0/opencv.js'); |
| 54 | + loadingPromise = Promise.race([ |
| 55 | + (async () => { |
| 56 | + try { |
| 57 | + const cv: OpenCVTypes = await import('../opencv/4.9.0/opencv.js'); |
12 | 58 |
|
13 | | - if ('ready' in cv) await cv.ready; |
| 59 | + // Wait for cv.ready with polling and timeout |
| 60 | + await waitForOpenCVReady(cv); |
14 | 61 |
|
15 | | - opencv = cv; |
| 62 | + if (!cv.Mat) { |
| 63 | + throw new Error('OpenCV missing essential methods'); |
| 64 | + } |
| 65 | + opencv = cv; |
| 66 | + return opencv; |
| 67 | + } catch (error) { |
| 68 | + loadingPromise = null; |
| 69 | + throw error; |
| 70 | + } |
| 71 | + })(), |
| 72 | + new Promise<never>((_, reject) => |
| 73 | + setTimeout( |
| 74 | + () => reject(new Error(`OpenCV loading timeout (${OPENCV_LOAD_TIMEOUT_MS}ms)`)), |
| 75 | + OPENCV_LOAD_TIMEOUT_MS |
| 76 | + ) |
| 77 | + ), |
| 78 | + ]); |
16 | 79 |
|
17 | | - return opencv; |
| 80 | + return loadingPromise; |
18 | 81 | }; |
0 commit comments