148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
// 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<number>()
|
|
private maxConcurrent = 4 // 最大并发下载数
|
|
private targetPrefetch = 4 // 预取帧数
|
|
private currentLoading = 0
|
|
|
|
constructor() {
|
|
this.setupMessageHandler()
|
|
}
|
|
|
|
private setupMessageHandler() {
|
|
self.addEventListener('message', async (event: MessageEvent<WorkerMessage>) => {
|
|
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<void>[] = []
|
|
|
|
// 并发预取接下来的 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<ImageBitmap> {
|
|
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 } |