// Web Worker for concurrent image frame downloading and decoding interface FrameRequest { url: string frameIndex: number priority: 'high' | 'normal' | 'low' } interface FrameResponse { frameIndex: number url: string error?: string priority?: 'high' | 'normal' | 'low' } interface WorkerMessage { type: 'load' | 'prefetch' | 'clear' | 'setBuffer' data?: any } class ImageFrameLoader { private loadingQueue = new Set() private maxConcurrent = 4 // 最大并发下载数 private targetPrefetch = 4 // 预取帧数 private currentLoading = 0 constructor() { this.setupMessageHandler() } private setupMessageHandler() { self.addEventListener('message', async (event: MessageEvent) => { const { type, data } = event.data switch (type) { case 'load': await this.loadFrame(data?.url, data?.frameIndex, data?.priority || 'high') break case 'prefetch': await this.prefetchFrames(data?.urls, data?.startIndex) break case 'clear': this.clearCache() break case 'setBuffer': this.targetPrefetch = data?.targetPrefetch || 4 break } }) } private async loadFrame(url: string, frameIndex: number, priority: 'high' | 'normal' | 'low') { // 如果正在加载,跳过 if (this.loadingQueue.has(frameIndex)) { return } // 检查并发限制 if (this.currentLoading >= this.maxConcurrent && priority !== 'high') { // 对于非高优先级请求,延迟处理 setTimeout(() => this.loadFrame(url, frameIndex, priority), 50) return } this.loadingQueue.add(frameIndex) this.currentLoading++ try { const imageBitmap = await this.fetchAndDecodeImage(url) // 直接发送到主线程,不在 worker 中缓存 this.postFrameResponse(frameIndex, imageBitmap, url, priority) } catch (error) { // 区分优先级处理错误 if (priority === 'high') { // 高优先级请求(当前帧)应该报告错误 this.postError(frameIndex, url, (error as Error).message) } else { console.debug(`Prefetch frame ${frameIndex} not available (${url}) - this is normal for future timestamps`) } } finally { this.loadingQueue.delete(frameIndex) this.currentLoading-- } } private async prefetchFrames(urls: string[], startIndex: number) { const prefetchPromises: Promise[] = [] // 并发预取接下来的 targetPrefetch 帧 for (let i = 0; i < this.targetPrefetch && i < urls.length; i++) { const frameIndex = startIndex + i const url = urls[i] if (!this.loadingQueue.has(frameIndex)) { prefetchPromises.push(this.loadFrame(url, frameIndex, 'normal')) } } await Promise.allSettled(prefetchPromises) } private async fetchAndDecodeImage(url: string): Promise { const response = await fetch(url) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const blob = await response.blob() // 使用 createImageBitmap 进行优化解码 const imageBitmap = await createImageBitmap(blob, { colorSpaceConversion: 'none', // 不进行颜色空间转换 premultiplyAlpha: 'none', // 不预乘 alpha resizeQuality: 'high' // 高质量缩放(如果需要) }) return imageBitmap } private postFrameResponse(frameIndex: number, imageBitmap: ImageBitmap, url: string, priority?: 'high' | 'normal' | 'low') { postMessage({ frameIndex, imageBitmap, url, priority }, { transfer: [imageBitmap] }) } private postError(frameIndex: number, url: string, error: string) { postMessage({ frameIndex, url, error }) } private clearCache() { // 清理加载队列 this.loadingQueue.clear() } } // 初始化 worker new ImageFrameLoader() export type { FrameRequest, FrameResponse, WorkerMessage }