import { useRef, useCallback, useState, useMemo } from 'react' import { LRUCache } from 'lru-cache' interface UseImageFrameLoaderOptions { maxBufferSize?: number targetPrefetch?: number } interface FrameLoadedCallback { (frameIndex: number, imageBitmap: ImageBitmap): void } export function useImageFrameLoader(options: UseImageFrameLoaderOptions = {}) { const frameCache = useRef>(new LRUCache({ max: options.maxBufferSize || 12, dispose: (bitmap) => { bitmap.close() // 释放 ImageBitmap 内存 } })) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) // 订阅者管理 const frameSubscribers = useRef>>(new Map()) // 初始化 Worker const worker = useMemo(() => { if (typeof window !== 'undefined' && window.Worker) { try { const worker = new Worker( new URL('../workers/image-frame-loader.ts', import.meta.url), { type: 'module' } ) // 监听 Worker 消息 worker.addEventListener('message', (event) => { const { frameIndex, error, imageBitmap, priority } = event.data if (error) { setError(error) } else if (imageBitmap) { debugger frameCache.current.set(frameIndex, imageBitmap) // 如果是高优先级请求,通知订阅者 if (priority === 'high') { const subscribers = frameSubscribers.current.get(frameIndex) if (subscribers) { subscribers.forEach(callback => { try { callback(frameIndex, imageBitmap) } catch (err) { console.error('Frame subscriber callback error:', err) } }) // 清理订阅者 frameSubscribers.current.delete(frameIndex) } } } setIsLoading(false) }) // 设置预取配置 worker.postMessage({ type: 'setBuffer', data: { targetPrefetch: options.targetPrefetch || 4 } }) return worker } catch (err) { console.error('Failed to initialize image frame loader worker:', err) setError('Worker initialization failed') return null } } else { setError('Web Workers not supported') return null } }, [options.maxBufferSize, options.targetPrefetch]) // eslint-disable-line react-hooks/exhaustive-deps // 加载单个帧 const loadFrame = useCallback((url: string, frameIndex: number, priority: 'high' | 'normal' | 'low' = 'high') => { if (!worker) { setError('Worker not available') return } // 如果主线程缓存中已存在,直接返回 if (frameCache.current.has(frameIndex)) { return } setIsLoading(true) setError(null) worker.postMessage({ type: 'load', data: { url, frameIndex, priority } }) }, [worker]) // 订阅帧加载完成通知 const subscribeToFrame = useCallback((frameIndex: number, callback: FrameLoadedCallback) => { if (!frameSubscribers.current.has(frameIndex)) { frameSubscribers.current.set(frameIndex, new Set()) } frameSubscribers.current.get(frameIndex)!.add(callback) // 返回取消订阅函数 return () => { const subscribers = frameSubscribers.current.get(frameIndex) if (subscribers) { subscribers.delete(callback) if (subscribers.size === 0) { frameSubscribers.current.delete(frameIndex) } } } }, []) // 预取多个帧 const prefetchFrames = useCallback((urls: string[], startIndex: number) => { if (!worker) { setError('Worker not available') return } worker.postMessage({ type: 'prefetch', data: { urls, startIndex } }) }, []) // 清理缓存 const clearCache = useCallback(() => { if (!worker) return // LRU 缓存会自动调用 dispose 方法释放 ImageBitmap frameCache.current.clear() worker.postMessage({ type: 'clear' }) }, [worker]) // 获取帧 - 直接从 LRU 缓存获取 const getFrame = useCallback((frameIndex: number): ImageBitmap | null => { console.log(Array.from(frameCache.current.keys())) return frameCache.current.get(frameIndex) || null }, []) // 检查帧是否已缓存 const isFrameCached = useCallback((frameIndex: number): boolean => { return frameCache.current.has(frameIndex) }, []) // 获取缓存统计信息 const getCacheStats = useCallback(() => { const cache = frameCache.current const totalFrames = cache.size const maxFrames = cache.max return { totalFrames, maxFrames, cacheUtilization: maxFrames > 0 ? totalFrames / maxFrames : 0 } }, []) return { loadFrame, prefetchFrames, clearCache, getFrame, isFrameCached, getCacheStats, subscribeToFrame, isLoading, error, isWorkerSupported: typeof window !== 'undefined' && !!window.Worker } }