@meframe/core 0.0.30-beta → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Meframe.d.ts +0 -17
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +1 -18
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +7 -49
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +3 -56
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/resource/ResourceCache.d.ts +2 -2
- package/dist/cache/resource/ResourceCache.d.ts.map +1 -1
- package/dist/cache/resource/ResourceCache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +1 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +2 -3
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/model/types.d.ts +0 -1
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +3 -2
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +18 -13
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +15 -17
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +6 -13
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +37 -64
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/index.d.ts +0 -1
- package/dist/stages/load/index.d.ts.map +1 -1
- package/dist/stages/load/types.d.ts +3 -10
- package/dist/stages/load/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cache/l2/L2Cache.js +0 -329
- package/dist/cache/l2/L2Cache.js.map +0 -1
- package/dist/cache/l2/L2OPFSStore.js +0 -89
- package/dist/cache/l2/L2OPFSStore.js.map +0 -1
- package/dist/cache/storage/indexeddb/ChunkRecordStore.js +0 -180
- package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +0 -1
- package/dist/stages/load/EventHandlers.d.ts +0 -26
- package/dist/stages/load/EventHandlers.d.ts.map +0 -1
- package/dist/stages/load/EventHandlers.js +0 -42
- package/dist/stages/load/EventHandlers.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import { type Resource, type CompositionModel, hasResourceId } from '../../model';\nimport type { Orchestrator, ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventHandlers } from './EventHandlers';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\nimport { createImageBitmapFromBlob } from '../../utils/image-utils';\nimport { MP4IndexParser, type MP4ParseResult } from '../demux/MP4IndexParser';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private orchestrator?: Orchestrator;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventHandlers?: EventHandlers;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n private blobCache = new Map<string, Blob>();\n private pendingTransfers = new Map<string, string[]>();\n private parsingIndexes = new Set<string>(); // Track in-progress index parsing\n\n // Preloading state\n private isPreloadingEnabled = true;\n private preloadQueue: string[] = [];\n private activePreloads = new Set<string>();\n private readonly IDLE_PRELOAD_CONCURRENCY = 2;\n\n constructor(options?: ResourceLoaderOptions) {\n const maxConcurrent = options?.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options?.onProgress, options?.config);\n this.eventBus = options?.eventBus;\n this.onStateChange = options?.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n\n if (options?.orchestrator) {\n this.bind(options.orchestrator);\n }\n }\n\n /**\n * Bind to Orchestrator event system\n */\n bind(orchestrator: Orchestrator): void {\n this.unbind();\n this.orchestrator = orchestrator;\n\n this.eventHandlers = new EventHandlers(\n orchestrator,\n (resourceId) => this.cancel(resourceId),\n (model) => this.handleModelSet(model)\n );\n }\n\n /**\n * Unbind from Orchestrator\n */\n unbind(): void {\n this.eventHandlers?.dispose();\n this.eventHandlers = undefined;\n this.orchestrator = undefined;\n }\n\n private handleModelSet(model: CompositionModel): void {\n this.model = model;\n if (this.isPreloadingEnabled) {\n this.startPreloading();\n }\n }\n\n setPreloadingEnabled(enabled: boolean): void {\n this.isPreloadingEnabled = enabled;\n if (enabled) {\n this.startPreloading();\n } else {\n // Clear queue if disabled, but let active preloads finish (they are low priority)\n this.preloadQueue = [];\n }\n }\n\n startPreloading(): void {\n if (!this.model || !this.isPreloadingEnabled) return;\n\n const mainTrack = this.model.tracks.find(\n (track) => track.id === (this.model?.mainTrackId || 'main')\n );\n if (!mainTrack) return;\n const newQueue: string[] = [];\n for (const clip of mainTrack.clips) {\n if (!hasResourceId(clip)) continue;\n const resource = this.model.getResource(clip.resourceId);\n if (!resource) continue;\n\n // Skip if already ready, loading, or error\n if (\n !resource ||\n resource.state === 'ready' ||\n resource.state === 'loading' ||\n resource.state === 'error'\n ) {\n continue;\n }\n\n // Also skip if currently in activePreloads or activeTasks\n if (this.activePreloads.has(resource.id) || this.taskManager.hasActiveTask(resource.id)) {\n continue;\n }\n\n newQueue.push(resource.id);\n }\n\n this.preloadQueue = newQueue;\n this.processPreloadQueue();\n }\n\n private processPreloadQueue(): void {\n if (!this.isPreloadingEnabled || this.preloadQueue.length === 0) return;\n\n while (\n this.activePreloads.size < this.IDLE_PRELOAD_CONCURRENCY &&\n this.preloadQueue.length > 0\n ) {\n const resourceId = this.preloadQueue.shift();\n if (!resourceId) break;\n\n this.activePreloads.add(resourceId);\n\n // Use low priority for preloading\n this.fetch(resourceId, { priority: 'low' }).finally(() => {\n this.activePreloads.delete(resourceId);\n // Continue processing queue\n this.processPreloadQueue();\n });\n }\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n clipId?: string,\n trackId?: string,\n isMainTrack = false\n ): void {\n if (this.taskManager.hasActiveTask(resource.id)) {\n if (priority === 'high') {\n // Preview channel: high priority can preempt (existing logic preserved)\n } else if (isMainTrack) {\n // Main track resource conflict: throw error for PreRenderService to detect\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n } else {\n // Attachment resource conflict: check cache\n if (this.blobCache.has(resource.id)) {\n // Already cached: immediately create ImageBitmap and transfer\n void this.transferCachedImage(resource.id, sessionId);\n return;\n } else {\n // Loading in progress: register to pending queue, will transfer after load complete\n this.registerPendingTransfer(resource.id, sessionId);\n console.debug(\n `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`\n );\n return;\n }\n }\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);\n this.processQueue();\n }\n\n private registerPendingTransfer(resourceId: string, sessionId?: string): void {\n if (!sessionId) return;\n\n const pending = this.pendingTransfers.get(resourceId) || [];\n if (!pending.includes(sessionId)) {\n pending.push(sessionId);\n this.pendingTransfers.set(resourceId, pending);\n }\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n let loadError: Error | undefined;\n\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video') {\n // Video: OPFS cache + MP4 index parsing (for window cache architecture)\n const cached = await this.orchestrator?.cacheManager.hasResourceInCache(task.resourceId);\n if (cached) {\n // Resource already in OPFS - ensure index is parsed\n await this.ensureIndexParsed(task.resourceId);\n\n // If sessionId is present (Worker Pipeline active), transfer OPFS stream to worker\n if (task.sessionId) {\n const stream = await this.createOPFSReadStream(task.resourceId);\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n }\n } else {\n // Not cached - download and cache to OPFS\n await this.loadWithOPFSCache(task);\n }\n } else if (task.resource.type === 'audio') {\n // Audio (MP3/WAV): Traditional pipeline (demux worker)\n // No OPFS cache or index parsing needed\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n loadError = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId, loadError);\n this.processQueue();\n }\n }\n\n /**\n * Ensure MP4 index is parsed for a cached resource\n */\n async ensureIndexParsed(resourceId: string): Promise<void> {\n if (!this.orchestrator) return;\n\n // Check if index already exists\n if (this.orchestrator.cacheManager.mp4IndexCache.has(resourceId)) {\n return;\n }\n\n // Check if already parsing (avoid duplicate parsing for same resource)\n if (this.parsingIndexes.has(resourceId)) {\n // Wait for the in-progress parsing to complete\n while (this.parsingIndexes.has(resourceId)) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n return;\n }\n\n // Mark as parsing\n this.parsingIndexes.add(resourceId);\n\n try {\n // Parse from OPFS file\n const stream = await this.createOPFSReadStream(resourceId);\n // Create minimal task for parsing (no clipId since this is a background index parse)\n const parseTask: LoadTask = {\n resourceId,\n resource: { id: resourceId, type: 'video', uri: '' },\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.parseIndexFromStream(parseTask, stream);\n } finally {\n // Remove from parsing set\n this.parsingIndexes.delete(resourceId);\n }\n }\n\n /**\n * Create ReadableStream from OPFS file\n * Reuses OPFSManager's underlying file access\n */\n private async createOPFSReadStream(resourceId: string): Promise<ReadableStream<Uint8Array>> {\n if (!this.orchestrator) {\n throw new Error('Orchestrator not bound');\n }\n\n const opfsManager = (this.orchestrator.cacheManager.resourceCache as any).opfsManager;\n const projectId = (this.orchestrator.cacheManager.resourceCache as any).projectId;\n\n // Get file handle from OPFS\n const dir = await opfsManager.getProjectDir(projectId, 'resource');\n const fileName = `${resourceId}.mp4`;\n const fileHandle = await dir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n\n // Return native stream\n return file.stream();\n }\n\n /**\n * Load resource and cache to OPFS + parse moov + extract first frame\n *\n * Strategy: Parallel tee() approach for fast first frame\n * - One stream writes to OPFS (background)\n * - Another stream parses moov and extracts first GOP\n * - First frame is decoded and cached immediately\n */\n private async loadWithOPFSCache(task: LoadTask): Promise<void> {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n\n // Prepare streams: 1 for OPFS, 1 for parsing, optional 1 for Worker\n let opfsStream: ReadableStream<Uint8Array>;\n let parseStream: ReadableStream<Uint8Array>;\n let workerStream: ReadableStream<Uint8Array> | undefined;\n\n if (task.sessionId) {\n // If worker needs it, split into 3 ways\n const [branch1, branch2] = stream.tee();\n const [branch2a, branch2b] = branch2.tee();\n\n opfsStream = branch1;\n parseStream = branch2a;\n workerStream = branch2b;\n } else {\n // Just 2 ways\n const [s1, s2] = stream.tee();\n opfsStream = s1;\n parseStream = s2;\n }\n\n const promises: Promise<void>[] = [\n this.writeToOPFS(task.resourceId, opfsStream),\n this.parseIndexFromStream(task, parseStream),\n ];\n\n if (workerStream && task.sessionId) {\n // Assign stream to task so transferToDemuxWorker uses it\n // Note: we need to clone the task or modify it carefully, but here it's safe\n // We create a temp task object or just modify current (since stream is consumed)\n // Actually transferToDemuxWorker uses task.stream.\n // We can't modify task.stream in place easily if type is readonly-ish, but it's not.\n const workerTask = { ...task, stream: workerStream };\n promises.push(this.transferToDemuxWorker(workerTask));\n }\n\n // Parallel execution\n await Promise.all(promises);\n }\n\n /**\n * Write resource stream to OPFS\n */\n private async writeToOPFS(resourceId: string, stream: ReadableStream<Uint8Array>): Promise<void> {\n if (!this.orchestrator) return;\n\n await this.orchestrator.cacheManager.resourceCache.writeResource(resourceId, stream);\n }\n\n /**\n * Parse moov from stream and cache index + audio samples + decode first frame\n */\n private async parseIndexFromStream(\n task: LoadTask,\n stream: ReadableStream<Uint8Array>\n ): Promise<void> {\n if (!this.orchestrator) return;\n\n const { resourceId, clipId } = task;\n\n try {\n const parser = new MP4IndexParser();\n\n const result: MP4ParseResult = await parser.parseFromStream(stream, {\n onFirstFrameReady: async (index, chunks) => {\n // Set resourceId on index\n index.resourceId = resourceId;\n\n // Cache index immediately\n this.orchestrator!.cacheManager.mp4IndexCache.set(resourceId, index);\n\n // Emit event with chunks for Orchestrator to handle\n // Only if clipId is provided (indicates this is a preview/cover request)\n if (clipId) {\n this.eventBus?.emit(MeframeEvent.ResourceFirstFrameReady, {\n resourceId,\n clipId,\n index,\n chunks,\n });\n }\n },\n });\n\n result.index.resourceId = resourceId;\n\n // Cache index (if not already cached by onFirstFrameReady)\n if (!this.orchestrator.cacheManager.mp4IndexCache.has(resourceId)) {\n this.orchestrator.cacheManager.mp4IndexCache.set(resourceId, result.index);\n }\n\n // Cache audio samples if present\n if (result.audioSamples && result.audioMetadata) {\n this.orchestrator.cacheManager.audioSampleCache.set(\n resourceId,\n result.audioSamples,\n result.audioMetadata\n );\n } else {\n // Ensure cache knows this resource has NO audio\n // This prevents GlobalAudioSession from waiting for it\n // AudioSampleCache should ideally support storing \"empty\" state or we just rely on has() returning false\n // But has() returning false triggers fetch.\n // We need a way to say \"fetched, but no audio\".\n // Currently AudioSampleCache.set requires samples.\n // We might need to update AudioSampleCache to support explicit \"no audio\" record\n // Or we rely on MP4Index having audio track info.\n }\n } catch (error) {\n console.error(`[ResourceLoader] Failed to parse MP4 index for ${resourceId}:`, error);\n // Rethrow error to ensure resource is marked as failed\n // In the new architecture (Window Cache + AudioSampleCache), index parsing is critical.\n throw error;\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → cache in CacheManager\n * Note: Images don't need streaming (typically < 5MB)\n *\n * Strategy for window cache architecture:\n * - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)\n * - OnDemandComposeSession retrieves from cache for composition\n * - No longer transfer to Worker (composition is in main thread)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n // Check cache\n let blob = this.blobCache.get(task.resourceId);\n\n if (!blob) {\n // Not cached: download and cache\n blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n this.blobCache.set(task.resourceId, blob);\n }\n\n // Create ImageBitmap from Blob (with special handling for SVG)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Store in CacheManager for main-thread access (OnDemandComposeSession)\n if (this.orchestrator) {\n this.orchestrator.cacheManager.imageBitmapCache.set(task.resourceId, imageBitmap);\n }\n\n // Legacy: Transfer to Worker if sessionId is provided (for old pipeline compatibility)\n // if (task.sessionId) {\n // await this.transferImageToWorker(task, imageBitmap);\n\n // // Process pending transfer queue\n // const pending = this.pendingTransfers.get(task.resourceId);\n // if (pending && pending.length > 0) {\n // for (const sessionId of pending) {\n // await this.transferCachedImage(task.resourceId, sessionId);\n // }\n // this.pendingTransfers.delete(task.resourceId);\n // }\n // }\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n * Legacy: Not used in window cache architecture (images accessed via CacheManager)\n */\n // private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n // if (!this.orchestrator) return;\n\n // if (!task.sessionId) {\n // throw new Error(\n // `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n // `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n // );\n // }\n\n // const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);\n // await composeWorker?.send?.(\n // 'receive_image',\n // {\n // resourceId: task.resourceId,\n // sessionId: task.sessionId,\n // imageBitmap,\n // },\n // { transfer: [imageBitmap] }\n // );\n // }\n\n /**\n * Transfer cached image to a session\n * Creates new ImageBitmap from cached Blob and transfers to worker\n */\n private async transferCachedImage(resourceId: string, sessionId?: string): Promise<void> {\n const blob = this.blobCache.get(resourceId);\n if (!blob || !sessionId) return;\n\n // Create new ImageBitmap from cached Blob (with SVG support)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Transfer to Worker\n if (!this.orchestrator) return;\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', sessionId);\n\n await composeWorker?.send?.(\n 'receive_image',\n {\n resourceId,\n sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n /**\n * Transfer stream to demux worker (for audio files)\n */\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream || !this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.orchestrator.workers.get(workerType, task.sessionId);\n if (demuxWorker) {\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n } else {\n // Demux worker not ready - cancel stream\n task.stream.cancel();\n }\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n if (this.orchestrator) {\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n const transferResourceToWorker = async (\n rId: string,\n sId: string,\n type: string\n ): Promise<void> => {\n if (type === 'video') {\n const stream = await this.createOPFSReadStream(rId);\n // Mock a task to reuse transfer logic\n const task: LoadTask = {\n resourceId: rId,\n sessionId: sId,\n resource: this.model?.resources.get(rId)!,\n stream,\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.transferToDemuxWorker(task);\n } else if (type === 'image') {\n await this.transferCachedImage(rId, sId);\n }\n };\n\n // If resource is already ready, check if we need to transfer stream (Worker Pipeline)\n if (resource.state === 'ready') {\n if (options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n return;\n }\n\n // Check if task already exists\n let taskPromise = this.taskManager.getTaskPromise(resourceId);\n let isCoveredByTask = false;\n\n if (taskPromise) {\n // Check if existing task covers this session\n // Use activeTasks map + taskQueue search instead of getTaskFromQueue\n const activeTask =\n this.taskManager.activeTasks.get(resourceId) ||\n this.taskManager.taskQueue.find((t) => t.resourceId === resourceId);\n\n if (activeTask && activeTask.sessionId === options?.sessionId) {\n isCoveredByTask = true;\n }\n } else {\n // No task, we create it.\n // enqueue() will create the task with promise\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId,\n options?.isMainTrack ?? false\n );\n // Get the newly created promise\n taskPromise = this.taskManager.getTaskPromise(resourceId);\n isCoveredByTask = true;\n }\n\n // Wait for task to complete\n await taskPromise;\n\n // If we weren't the primary session for the task, transfer now from cache/OPFS\n // We must re-check resource.state here because it might have failed\n const updatedResource = this.model?.resources.get(resourceId);\n if (!isCoveredByTask && updatedResource?.state === 'ready' && options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.blobCache.clear();\n this.pendingTransfers.clear();\n this.unbind();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import { type Resource, type CompositionModel, hasResourceId } from '../../model';\nimport type { ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\nimport { createImageBitmapFromBlob } from '../../utils/image-utils';\nimport { MP4IndexParser, type MP4ParseResult } from '../demux/MP4IndexParser';\nimport type { CacheManager } from '../../cache/CacheManager';\nimport type { WorkerPool } from '../../worker/WorkerPool';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private cacheManager: CacheManager;\n private workerPool: WorkerPool;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n private blobCache = new Map<string, Blob>();\n private pendingTransfers = new Map<string, string[]>();\n private parsingIndexes = new Set<string>(); // Track in-progress index parsing\n\n // Preloading state\n private isPreloadingEnabled = true;\n private preloadQueue: string[] = [];\n private activePreloads = new Set<string>();\n // TODO: make this configurable\n private readonly IDLE_PRELOAD_CONCURRENCY = 2;\n\n constructor(options: ResourceLoaderOptions) {\n const maxConcurrent = options.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options.onProgress, options.config);\n this.eventBus = options.eventBus;\n this.onStateChange = options.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n this.cacheManager = options.cacheManager;\n this.workerPool = options.workerPool;\n }\n\n async setModel(model: CompositionModel): Promise<void> {\n this.model = model;\n const mainTrack = model.tracks.find((track) => track.id === (model.mainTrackId || 'main'));\n if (mainTrack?.clips?.[0] && hasResourceId(mainTrack.clips[0])) {\n await this.fetch(mainTrack.clips[0].resourceId, {\n priority: 'high',\n clipId: mainTrack.clips[0].id,\n trackId: mainTrack.id,\n });\n }\n this.startPreloading();\n }\n\n setPreloadingEnabled(enabled: boolean): void {\n this.isPreloadingEnabled = enabled;\n if (enabled) {\n this.startPreloading();\n } else {\n // Clear queue if disabled, but let active preloads finish (they are low priority)\n this.preloadQueue = [];\n }\n }\n\n startPreloading(): void {\n if (!this.model || !this.isPreloadingEnabled) return;\n\n const mainTrack = this.model.tracks.find(\n (track) => track.id === (this.model?.mainTrackId || 'main')\n );\n if (!mainTrack) return;\n const newQueue: string[] = [];\n for (const clip of mainTrack.clips) {\n if (!hasResourceId(clip)) continue;\n const resource = this.model.getResource(clip.resourceId);\n if (!resource) continue;\n\n // Skip if already ready, loading, or error\n if (\n !resource ||\n resource.state === 'ready' ||\n resource.state === 'loading' ||\n resource.state === 'error'\n ) {\n continue;\n }\n\n // Also skip if currently in activePreloads or activeTasks\n if (this.activePreloads.has(resource.id) || this.taskManager.hasActiveTask(resource.id)) {\n continue;\n }\n\n newQueue.push(resource.id);\n }\n\n this.preloadQueue = newQueue;\n this.processPreloadQueue();\n }\n\n private processPreloadQueue(): void {\n if (!this.isPreloadingEnabled || this.preloadQueue.length === 0) return;\n\n while (\n this.activePreloads.size < this.IDLE_PRELOAD_CONCURRENCY &&\n this.preloadQueue.length > 0\n ) {\n const resourceId = this.preloadQueue.shift();\n if (!resourceId) break;\n\n this.activePreloads.add(resourceId);\n\n // Use low priority for preloading\n this.fetch(resourceId, { priority: 'low' }).finally(() => {\n this.activePreloads.delete(resourceId);\n // Continue processing queue\n this.processPreloadQueue();\n });\n }\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n clipId?: string,\n trackId?: string,\n isMainTrack = false\n ): void {\n if (this.taskManager.hasActiveTask(resource.id)) {\n if (priority === 'high') {\n // Preview channel: high priority can preempt (existing logic preserved)\n } else if (isMainTrack) {\n // Main track resource conflict: throw error for PreRenderService to detect\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n } else {\n // Attachment resource conflict: check cache\n if (this.blobCache.has(resource.id)) {\n // Already cached: immediately create ImageBitmap and transfer\n void this.transferCachedImage(resource.id, sessionId);\n return;\n } else {\n // Loading in progress: register to pending queue, will transfer after load complete\n this.registerPendingTransfer(resource.id, sessionId);\n console.debug(\n `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`\n );\n return;\n }\n }\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);\n this.processQueue();\n }\n\n private registerPendingTransfer(resourceId: string, sessionId?: string): void {\n if (!sessionId) return;\n\n const pending = this.pendingTransfers.get(resourceId) || [];\n if (!pending.includes(sessionId)) {\n pending.push(sessionId);\n this.pendingTransfers.set(resourceId, pending);\n }\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n let loadError: Error | undefined;\n\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video') {\n // Video: OPFS cache + MP4 index parsing (for window cache architecture)\n const cached = await this.cacheManager.hasResourceInCache(task.resourceId);\n if (cached) {\n // Resource already in OPFS - ensure index is parsed\n await this.ensureIndexParsed(task.resourceId);\n\n // If sessionId is present (Worker Pipeline active), transfer OPFS stream to worker\n if (task.sessionId) {\n const stream = await this.createOPFSReadStream(task.resourceId);\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n }\n } else {\n // Not cached - download and cache to OPFS\n await this.loadWithOPFSCache(task);\n }\n } else if (task.resource.type === 'audio') {\n // Audio (MP3/WAV): Traditional pipeline (demux worker)\n // No OPFS cache or index parsing needed\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n loadError = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId, loadError);\n this.processQueue();\n }\n }\n\n /**\n * Ensure MP4 index is parsed for a cached resource\n */\n async ensureIndexParsed(resourceId: string): Promise<void> {\n // Check if index already exists\n if (this.cacheManager.mp4IndexCache.has(resourceId)) {\n return;\n }\n\n // Check if already parsing (avoid duplicate parsing for same resource)\n if (this.parsingIndexes.has(resourceId)) {\n // Wait for the in-progress parsing to complete\n while (this.parsingIndexes.has(resourceId)) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n return;\n }\n\n // Mark as parsing\n this.parsingIndexes.add(resourceId);\n\n try {\n // Parse from OPFS file\n const stream = await this.createOPFSReadStream(resourceId);\n // Create minimal task for parsing (no clipId since this is a background index parse)\n const parseTask: LoadTask = {\n resourceId,\n resource: { id: resourceId, type: 'video', uri: '' },\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.parseIndexFromStream(parseTask, stream);\n } finally {\n // Remove from parsing set\n this.parsingIndexes.delete(resourceId);\n }\n }\n\n /**\n * Create ReadableStream from OPFS file\n * Reuses OPFSManager's underlying file access\n */\n private async createOPFSReadStream(resourceId: string): Promise<ReadableStream<Uint8Array>> {\n const opfsManager = this.cacheManager.resourceCache.opfsManager;\n const projectId = this.cacheManager.resourceCache.projectId;\n\n // Get file handle from OPFS\n const dir = await opfsManager.getProjectDir(projectId, 'resource');\n const fileName = `${resourceId}.mp4`;\n const fileHandle = await dir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n\n // Return native stream\n return file.stream();\n }\n\n /**\n * Load resource and cache to OPFS + parse moov + extract first frame\n *\n * Strategy: Parallel tee() approach for fast first frame\n * - One stream writes to OPFS (background)\n * - Another stream parses moov and extracts first GOP\n * - First frame is decoded and cached immediately\n */\n private async loadWithOPFSCache(task: LoadTask): Promise<void> {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n\n // Prepare streams: 1 for OPFS, 1 for parsing, optional 1 for Worker\n let opfsStream: ReadableStream<Uint8Array>;\n let parseStream: ReadableStream<Uint8Array>;\n let workerStream: ReadableStream<Uint8Array> | undefined;\n\n if (task.sessionId) {\n // If worker needs it, split into 3 ways\n const [branch1, branch2] = stream.tee();\n const [branch2a, branch2b] = branch2.tee();\n\n opfsStream = branch1;\n parseStream = branch2a;\n workerStream = branch2b;\n } else {\n // Just 2 ways\n const [s1, s2] = stream.tee();\n opfsStream = s1;\n parseStream = s2;\n }\n\n const promises: Promise<void>[] = [\n this.writeToOPFS(task.resourceId, opfsStream),\n this.parseIndexFromStream(task, parseStream),\n ];\n\n if (workerStream && task.sessionId) {\n // Assign stream to task so transferToDemuxWorker uses it\n // Note: we need to clone the task or modify it carefully, but here it's safe\n // We create a temp task object or just modify current (since stream is consumed)\n // Actually transferToDemuxWorker uses task.stream.\n // We can't modify task.stream in place easily if type is readonly-ish, but it's not.\n const workerTask = { ...task, stream: workerStream };\n promises.push(this.transferToDemuxWorker(workerTask));\n }\n\n // Parallel execution\n await Promise.all(promises);\n }\n\n /**\n * Write resource stream to OPFS\n */\n private async writeToOPFS(resourceId: string, stream: ReadableStream<Uint8Array>): Promise<void> {\n await this.cacheManager.resourceCache.writeResource(resourceId, stream);\n }\n\n /**\n * Parse moov from stream and cache index + audio samples + decode first frame\n */\n private async parseIndexFromStream(\n task: LoadTask,\n stream: ReadableStream<Uint8Array>\n ): Promise<void> {\n const { resourceId, clipId } = task;\n\n try {\n const parser = new MP4IndexParser();\n\n const result: MP4ParseResult = await parser.parseFromStream(stream, {\n onFirstFrameReady: async (index, chunks) => {\n // Set resourceId on index\n index.resourceId = resourceId;\n\n // Cache index immediately\n this.cacheManager.mp4IndexCache.set(resourceId, index);\n\n // Emit event with chunks for Orchestrator to handle\n // Only if clipId is provided (indicates this is a preview/cover request)\n if (clipId) {\n this.eventBus?.emit(MeframeEvent.ResourceFirstFrameReady, {\n resourceId,\n clipId,\n index,\n chunks,\n });\n }\n },\n });\n\n result.index.resourceId = resourceId;\n\n // Cache index (if not already cached by onFirstFrameReady)\n if (!this.cacheManager.mp4IndexCache.has(resourceId)) {\n this.cacheManager.mp4IndexCache.set(resourceId, result.index);\n }\n\n // Cache audio samples if present\n if (result.audioSamples && result.audioMetadata) {\n this.cacheManager.audioSampleCache.set(\n resourceId,\n result.audioSamples,\n result.audioMetadata\n );\n } else {\n // Ensure cache knows this resource has NO audio\n // This prevents GlobalAudioSession from waiting for it\n // AudioSampleCache should ideally support storing \"empty\" state or we just rely on has() returning false\n // But has() returning false triggers fetch.\n // We need a way to say \"fetched, but no audio\".\n // Currently AudioSampleCache.set requires samples.\n // We might need to update AudioSampleCache to support explicit \"no audio\" record\n // Or we rely on MP4Index having audio track info.\n }\n } catch (error) {\n console.error(`[ResourceLoader] Failed to parse MP4 index for ${resourceId}:`, error);\n // Rethrow error to ensure resource is marked as failed\n // In the new architecture (Window Cache + AudioSampleCache), index parsing is critical.\n throw error;\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → cache in CacheManager\n * Note: Images don't need streaming (typically < 5MB)\n *\n * Strategy for window cache architecture:\n * - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)\n * - OnDemandComposeSession retrieves from cache for composition\n * - No longer transfer to Worker (composition is in main thread)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n // Check cache\n let blob = this.blobCache.get(task.resourceId);\n\n if (!blob) {\n // Not cached: download and cache\n blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n this.blobCache.set(task.resourceId, blob);\n }\n\n // Create ImageBitmap from Blob (with special handling for SVG)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Store in CacheManager for main-thread access (OnDemandComposeSession)\n this.cacheManager.imageBitmapCache.set(task.resourceId, imageBitmap);\n\n // Legacy: Transfer to Worker if sessionId is provided (for old pipeline compatibility)\n // if (task.sessionId) {\n // await this.transferImageToWorker(task, imageBitmap);\n\n // // Process pending transfer queue\n // const pending = this.pendingTransfers.get(task.resourceId);\n // if (pending && pending.length > 0) {\n // for (const sessionId of pending) {\n // await this.transferCachedImage(task.resourceId, sessionId);\n // }\n // this.pendingTransfers.delete(task.resourceId);\n // }\n // }\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n * Legacy: Not used in window cache architecture (images accessed via CacheManager)\n */\n // private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n // if (!this.orchestrator) return;\n\n // if (!task.sessionId) {\n // throw new Error(\n // `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n // `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n // );\n // }\n\n // const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);\n // await composeWorker?.send?.(\n // 'receive_image',\n // {\n // resourceId: task.resourceId,\n // sessionId: task.sessionId,\n // imageBitmap,\n // },\n // { transfer: [imageBitmap] }\n // );\n // }\n\n /**\n * Transfer cached image to a session\n * Creates new ImageBitmap from cached Blob and transfers to worker\n */\n private async transferCachedImage(resourceId: string, sessionId?: string): Promise<void> {\n const blob = this.blobCache.get(resourceId);\n if (!blob || !sessionId) return;\n\n // Create new ImageBitmap from cached Blob (with SVG support)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Transfer to Worker\n const composeWorker = await this.workerPool.get('videoCompose', sessionId);\n\n await composeWorker?.send?.(\n 'receive_image',\n {\n resourceId,\n sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n /**\n * Transfer stream to demux worker (for audio files)\n */\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.workerPool.get(workerType, task.sessionId);\n if (demuxWorker) {\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n } else {\n // Demux worker not ready - cancel stream\n task.stream.cancel();\n }\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n const transferResourceToWorker = async (\n rId: string,\n sId: string,\n type: string\n ): Promise<void> => {\n if (type === 'video') {\n const stream = await this.createOPFSReadStream(rId);\n // Mock a task to reuse transfer logic\n const task: LoadTask = {\n resourceId: rId,\n sessionId: sId,\n resource: this.model?.resources.get(rId)!,\n stream,\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.transferToDemuxWorker(task);\n } else if (type === 'image') {\n await this.transferCachedImage(rId, sId);\n }\n };\n\n // If resource is already ready, check if we need to transfer stream (Worker Pipeline)\n if (resource.state === 'ready') {\n if (options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n return;\n }\n\n // Check if task already exists\n let taskPromise = this.taskManager.getTaskPromise(resourceId);\n let isCoveredByTask = false;\n\n if (taskPromise) {\n // Check if existing task covers this session\n // Use activeTasks map + taskQueue search instead of getTaskFromQueue\n const activeTask =\n this.taskManager.activeTasks.get(resourceId) ||\n this.taskManager.taskQueue.find((t) => t.resourceId === resourceId);\n\n if (activeTask && activeTask.sessionId === options?.sessionId) {\n isCoveredByTask = true;\n }\n } else {\n // No task, we create it.\n // enqueue() will create the task with promise\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId,\n options?.isMainTrack ?? false\n );\n // Get the newly created promise\n taskPromise = this.taskManager.getTaskPromise(resourceId);\n isCoveredByTask = true;\n }\n\n // Wait for task to complete\n await taskPromise;\n\n // If we weren't the primary session for the task, transfer now from cache/OPFS\n // We must re-check resource.state here because it might have failed\n const updatedResource = this.model?.resources.get(resourceId);\n if (!isCoveredByTask && updatedResource?.state === 'ready' && options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.blobCache.clear();\n this.pendingTransfers.clear();\n }\n}\n"],"names":[],"mappings":";;;;;;;AAYO,MAAM,8BAA8B,MAAM;AAAA,EAC/C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,eAAe;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gCAAgB,IAAA;AAAA,EAChB,uCAAuB,IAAA;AAAA,EACvB,qCAAqB,IAAA;AAAA;AAAA;AAAA,EAGrB,sBAAsB;AAAA,EACtB,eAAyB,CAAA;AAAA,EACzB,qCAAqB,IAAA;AAAA;AAAA,EAEZ,2BAA2B;AAAA,EAE5C,YAAY,SAAgC;AAC1C,UAAM,gBAAgB,QAAQ,QAAQ,iBAAiB;AACvD,SAAK,cAAc,IAAI,YAAY,aAAa;AAChD,SAAK,gBAAgB,IAAI,cAAc,QAAQ,YAAY,QAAQ,MAAM;AACzE,SAAK,WAAW,QAAQ;AACxB,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,oBAAoB,IAAI,wBAAA;AAC7B,SAAK,eAAe,QAAQ;AAC5B,SAAK,aAAa,QAAQ;AAAA,EAC5B;AAAA,EAEA,MAAM,SAAS,OAAwC;AACrD,SAAK,QAAQ;AACb,UAAM,YAAY,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,QAAQ,MAAM,eAAe,OAAO;AACzF,QAAI,WAAW,QAAQ,CAAC,KAAK,cAAc,UAAU,MAAM,CAAC,CAAC,GAAG;AAC9D,YAAM,KAAK,MAAM,UAAU,MAAM,CAAC,EAAE,YAAY;AAAA,QAC9C,UAAU;AAAA,QACV,QAAQ,UAAU,MAAM,CAAC,EAAE;AAAA,QAC3B,SAAS,UAAU;AAAA,MAAA,CACpB;AAAA,IACH;AACA,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,qBAAqB,SAAwB;AAC3C,SAAK,sBAAsB;AAC3B,QAAI,SAAS;AACX,WAAK,gBAAA;AAAA,IACP,OAAO;AAEL,WAAK,eAAe,CAAA;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,kBAAwB;AACtB,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,oBAAqB;AAE9C,UAAM,YAAY,KAAK,MAAM,OAAO;AAAA,MAClC,CAAC,UAAU,MAAM,QAAQ,KAAK,OAAO,eAAe;AAAA,IAAA;AAEtD,QAAI,CAAC,UAAW;AAChB,UAAM,WAAqB,CAAA;AAC3B,eAAW,QAAQ,UAAU,OAAO;AAClC,UAAI,CAAC,cAAc,IAAI,EAAG;AAC1B,YAAM,WAAW,KAAK,MAAM,YAAY,KAAK,UAAU;AACvD,UAAI,CAAC,SAAU;AAGf,UACE,CAAC,YACD,SAAS,UAAU,WACnB,SAAS,UAAU,aACnB,SAAS,UAAU,SACnB;AACA;AAAA,MACF;AAGA,UAAI,KAAK,eAAe,IAAI,SAAS,EAAE,KAAK,KAAK,YAAY,cAAc,SAAS,EAAE,GAAG;AACvF;AAAA,MACF;AAEA,eAAS,KAAK,SAAS,EAAE;AAAA,IAC3B;AAEA,SAAK,eAAe;AACpB,SAAK,oBAAA;AAAA,EACP;AAAA,EAEQ,sBAA4B;AAClC,QAAI,CAAC,KAAK,uBAAuB,KAAK,aAAa,WAAW,EAAG;AAEjE,WACE,KAAK,eAAe,OAAO,KAAK,4BAChC,KAAK,aAAa,SAAS,GAC3B;AACA,YAAM,aAAa,KAAK,aAAa,MAAA;AACrC,UAAI,CAAC,WAAY;AAEjB,WAAK,eAAe,IAAI,UAAU;AAGlC,WAAK,MAAM,YAAY,EAAE,UAAU,OAAO,EAAE,QAAQ,MAAM;AACxD,aAAK,eAAe,OAAO,UAAU;AAErC,aAAK,oBAAA;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,YACN,UACA,WAAsC,UACtC,WACA,QACA,SACA,cAAc,OACR;AACN,QAAI,KAAK,YAAY,cAAc,SAAS,EAAE,GAAG;AAC/C,UAAI,aAAa,OAAQ;AAAA,eAEd,aAAa;AAEtB,cAAM,IAAI;AAAA,UACR,YAAY,SAAS,EAAE;AAAA,QAAA;AAAA,MAE3B,OAAO;AAEL,YAAI,KAAK,UAAU,IAAI,SAAS,EAAE,GAAG;AAEnC,eAAK,KAAK,oBAAoB,SAAS,IAAI,SAAS;AACpD;AAAA,QACF,OAAO;AAEL,eAAK,wBAAwB,SAAS,IAAI,SAAS;AACnD,kBAAQ;AAAA,YACN,wCAAwC,SAAS,EAAE;AAAA,UAAA;AAErD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,YAAY,QAAQ,UAAU,UAAU,WAAW,QAAQ,OAAO;AACvE,SAAK,aAAA;AAAA,EACP;AAAA,EAEQ,wBAAwB,YAAoB,WAA0B;AAC5E,QAAI,CAAC,UAAW;AAEhB,UAAM,UAAU,KAAK,iBAAiB,IAAI,UAAU,KAAK,CAAA;AACzD,QAAI,CAAC,QAAQ,SAAS,SAAS,GAAG;AAChC,cAAQ,KAAK,SAAS;AACtB,WAAK,iBAAiB,IAAI,YAAY,OAAO;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,WAAO,KAAK,YAAY,YAAY;AAClC,YAAM,OAAO,KAAK,YAAY,YAAA;AAC9B,UAAI,CAAC,KAAM;AACX,WAAK,UAAU,IAAI;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,UAAU,MAA+B;AACrD,SAAK,YAAY,aAAa,IAAI;AAClC,QAAI;AAEJ,QAAI;AACF,WAAK,oBAAoB,KAAK,YAAY,SAAS;AACnD,WAAK,aAAa,IAAI,gBAAA;AAGtB,UAAI,KAAK,SAAS,SAAS,SAAS;AAClC,cAAM,KAAK,gBAAgB,IAAI;AAAA,MACjC,WAAW,KAAK,SAAS,SAAS,SAAS;AAEzC,cAAM,SAAS,MAAM,KAAK,aAAa,mBAAmB,KAAK,UAAU;AACzE,YAAI,QAAQ;AAEV,gBAAM,KAAK,kBAAkB,KAAK,UAAU;AAG5C,cAAI,KAAK,WAAW;AAClB,kBAAM,SAAS,MAAM,KAAK,qBAAqB,KAAK,UAAU;AAC9D,iBAAK,SAAS;AACd,kBAAM,KAAK,sBAAsB,IAAI;AAAA,UACvC;AAAA,QACF,OAAO;AAEL,gBAAM,KAAK,kBAAkB,IAAI;AAAA,QACnC;AAAA,MACF,WAAW,KAAK,SAAS,SAAS,SAAS;AAGzC,cAAM,SAAS,MAAM,KAAK,cAAc,oBAAoB,IAAI;AAChE,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,MAAM,+BAA+B,KAAK,UAAU,EAAE;AAAA,QAClE;AACA,aAAK,SAAS;AACd,cAAM,KAAK,sBAAsB,IAAI;AAAA,MACvC,WAAW,KAAK,SAAS,SAAS,UAAU,KAAK,SAAS,SAAS,QAAQ;AACzE,cAAM,KAAK,iBAAiB,IAAI;AAAA,MAClC;AAGA,WAAK,oBAAoB,KAAK,YAAY,OAAO;AAAA,IACnD,SAAS,OAAO;AACd,WAAK,QAAQ;AACb,kBAAY;AACZ,WAAK,oBAAoB,KAAK,YAAY,OAAO;AAAA,IACnD,UAAA;AACE,WAAK,YAAY,aAAa,KAAK,YAAY,SAAS;AACxD,WAAK,aAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,YAAmC;AAEzD,QAAI,KAAK,aAAa,cAAc,IAAI,UAAU,GAAG;AACnD;AAAA,IACF;AAGA,QAAI,KAAK,eAAe,IAAI,UAAU,GAAG;AAEvC,aAAO,KAAK,eAAe,IAAI,UAAU,GAAG;AAC1C,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,MACxD;AACA;AAAA,IACF;AAGA,SAAK,eAAe,IAAI,UAAU;AAElC,QAAI;AAEF,YAAM,SAAS,MAAM,KAAK,qBAAqB,UAAU;AAEzD,YAAM,YAAsB;AAAA,QAC1B;AAAA,QACA,UAAU,EAAE,IAAI,YAAY,MAAM,SAAS,KAAK,GAAA;AAAA,QAChD,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,WAAW,KAAK,IAAA;AAAA,QAChB,UAAU;AAAA,MAAA;AAEZ,YAAM,KAAK,qBAAqB,WAAW,MAAM;AAAA,IACnD,UAAA;AAEE,WAAK,eAAe,OAAO,UAAU;AAAA,IACvC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,qBAAqB,YAAyD;AAC1F,UAAM,cAAc,KAAK,aAAa,cAAc;AACpD,UAAM,YAAY,KAAK,aAAa,cAAc;AAGlD,UAAM,MAAM,MAAM,YAAY,cAAc,WAAW,UAAU;AACjE,UAAM,WAAW,GAAG,UAAU;AAC9B,UAAM,aAAa,MAAM,IAAI,cAAc,QAAQ;AACnD,UAAM,OAAO,MAAM,WAAW,QAAA;AAG9B,WAAO,KAAK,OAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,kBAAkB,MAA+B;AAC7D,UAAM,SAAS,MAAM,KAAK,cAAc,oBAAoB,IAAI;AAChE,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,+BAA+B,KAAK,UAAU,EAAE;AAAA,IAClE;AAGA,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,KAAK,WAAW;AAElB,YAAM,CAAC,SAAS,OAAO,IAAI,OAAO,IAAA;AAClC,YAAM,CAAC,UAAU,QAAQ,IAAI,QAAQ,IAAA;AAErC,mBAAa;AACb,oBAAc;AACd,qBAAe;AAAA,IACjB,OAAO;AAEL,YAAM,CAAC,IAAI,EAAE,IAAI,OAAO,IAAA;AACxB,mBAAa;AACb,oBAAc;AAAA,IAChB;AAEA,UAAM,WAA4B;AAAA,MAChC,KAAK,YAAY,KAAK,YAAY,UAAU;AAAA,MAC5C,KAAK,qBAAqB,MAAM,WAAW;AAAA,IAAA;AAG7C,QAAI,gBAAgB,KAAK,WAAW;AAMlC,YAAM,aAAa,EAAE,GAAG,MAAM,QAAQ,aAAA;AACtC,eAAS,KAAK,KAAK,sBAAsB,UAAU,CAAC;AAAA,IACtD;AAGA,UAAM,QAAQ,IAAI,QAAQ;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAAY,YAAoB,QAAmD;AAC/F,UAAM,KAAK,aAAa,cAAc,cAAc,YAAY,MAAM;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBACZ,MACA,QACe;AACf,UAAM,EAAE,YAAY,OAAA,IAAW;AAE/B,QAAI;AACF,YAAM,SAAS,IAAI,eAAA;AAEnB,YAAM,SAAyB,MAAM,OAAO,gBAAgB,QAAQ;AAAA,QAClE,mBAAmB,OAAO,OAAO,WAAW;AAE1C,gBAAM,aAAa;AAGnB,eAAK,aAAa,cAAc,IAAI,YAAY,KAAK;AAIrD,cAAI,QAAQ;AACV,iBAAK,UAAU,KAAK,aAAa,yBAAyB;AAAA,cACxD;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA,CACD;AAAA,UACH;AAAA,QACF;AAAA,MAAA,CACD;AAED,aAAO,MAAM,aAAa;AAG1B,UAAI,CAAC,KAAK,aAAa,cAAc,IAAI,UAAU,GAAG;AACpD,aAAK,aAAa,cAAc,IAAI,YAAY,OAAO,KAAK;AAAA,MAC9D;AAGA,UAAI,OAAO,gBAAgB,OAAO,eAAe;AAC/C,aAAK,aAAa,iBAAiB;AAAA,UACjC;AAAA,UACA,OAAO;AAAA,UACP,OAAO;AAAA,QAAA;AAAA,MAEX,OAAO;AAAA,MASP;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,kDAAkD,UAAU,KAAK,KAAK;AAGpF,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAiB,MAA+B;AAC5D,UAAM,KAAK,UAAU,KAAK,SAAS,KAAK,KAAK,WAAY,MAAM;AAAA,EAGjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,gBAAgB,MAA+B;AAE3D,QAAI,OAAO,KAAK,UAAU,IAAI,KAAK,UAAU;AAE7C,QAAI,CAAC,MAAM;AAET,aAAO,MAAM,KAAK,UAAU,KAAK,SAAS,KAAK,KAAK,WAAY,MAAM;AACtE,WAAK,UAAU,IAAI,KAAK,YAAY,IAAI;AAAA,IAC1C;AAGA,UAAM,cAAc,MAAM,0BAA0B,IAAI;AAGxD,SAAK,aAAa,iBAAiB,IAAI,KAAK,YAAY,WAAW;AAAA,EAerE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,UAAU,KAAa,QAAoC;AACvE,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ;AAE5C,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;AAAA,IACnE;AAEA,WAAO,SAAS,KAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCA,MAAc,oBAAoB,YAAoB,WAAmC;AACvF,UAAM,OAAO,KAAK,UAAU,IAAI,UAAU;AAC1C,QAAI,CAAC,QAAQ,CAAC,UAAW;AAGzB,UAAM,cAAc,MAAM,0BAA0B,IAAI;AAGxD,UAAM,gBAAgB,MAAM,KAAK,WAAW,IAAI,gBAAgB,SAAS;AAEzE,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,EAAE,UAAU,CAAC,WAAW,EAAA;AAAA,IAAE;AAAA,EAE9B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBAAsB,MAA+B;AACjE,QAAI,CAAC,KAAK,OAAQ;AAElB,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI;AAAA,QACR,oDAAoD,KAAK,UAAU;AAAA,MAAA;AAAA,IAGvE;AAEA,UAAM,aAAa,KAAK,SAAS,SAAS,UAAU,eAAe;AACnE,UAAM,cAAc,MAAM,KAAK,WAAW,IAAI,YAAY,KAAK,SAAS;AACxE,QAAI,aAAa;AACf,YAAM,YAAY,WAAW,KAAK,QAAQ;AAAA,QACxC,WAAW,KAAK;AAAA,QAChB,GAAG,KAAK;AAAA,QACR,GAAI,KAAK,SAAS,EAAE,OAAO,KAAK,MAAA;AAAA,QAChC,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,QAAA;AAAA,MAAQ,CAC7C;AAAA,IACH,OAAO;AAEL,WAAK,OAAO,OAAA;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,oBAAoB,YAAoB,OAAgC;AAC9E,UAAM,WAAW,KAAK,OAAO,UAAU,IAAI,UAAU;AACrD,QAAI,UAAU;AACZ,YAAM,WAAW,SAAS;AAC1B,eAAS,QAAQ;AACjB,WAAK,UAAU,KAAK,aAAa,qBAAqB;AAAA,QACpD,MAAM,aAAa;AAAA,QACnB;AAAA,QACA;AAAA,QACA,UAAU;AAAA,MAAA,CACX;AAAA,IACH;AAEA,SAAK,gBAAgB,YAAY,KAAK;AAAA,EACxC;AAAA,EAEA,MAAM,MAAM,YAAqB,SAA8C;AAC7E,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,OAAO,UAAU,IAAI,UAAU;AACrD,QAAI,CAAC,UAAU;AACb,cAAQ,KAAK,YAAY,UAAU,qBAAqB;AACxD;AAAA,IACF;AAEA,UAAM,2BAA2B,OAC/B,KACA,KACA,SACkB;AAClB,UAAI,SAAS,SAAS;AACpB,cAAM,SAAS,MAAM,KAAK,qBAAqB,GAAG;AAElD,cAAM,OAAiB;AAAA,UACrB,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,UAAU,KAAK,OAAO,UAAU,IAAI,GAAG;AAAA,UACvC;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,UACZ,WAAW,KAAK,IAAA;AAAA,UAChB,UAAU;AAAA,QAAA;AAEZ,cAAM,KAAK,sBAAsB,IAAI;AAAA,MACvC,WAAW,SAAS,SAAS;AAC3B,cAAM,KAAK,oBAAoB,KAAK,GAAG;AAAA,MACzC;AAAA,IACF;AAGA,QAAI,SAAS,UAAU,SAAS;AAC9B,UAAI,SAAS,WAAW;AACtB,cAAM,yBAAyB,YAAY,QAAQ,WAAW,SAAS,IAAI;AAAA,MAC7E;AACA;AAAA,IACF;AAGA,QAAI,cAAc,KAAK,YAAY,eAAe,UAAU;AAC5D,QAAI,kBAAkB;AAEtB,QAAI,aAAa;AAGf,YAAM,aACJ,KAAK,YAAY,YAAY,IAAI,UAAU,KAC3C,KAAK,YAAY,UAAU,KAAK,CAAC,MAAM,EAAE,eAAe,UAAU;AAEpE,UAAI,cAAc,WAAW,cAAc,SAAS,WAAW;AAC7D,0BAAkB;AAAA,MACpB;AAAA,IACF,OAAO;AAGL,WAAK;AAAA,QACH;AAAA,QACA,SAAS,YAAY;AAAA,QACrB,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS,eAAe;AAAA,MAAA;AAG1B,oBAAc,KAAK,YAAY,eAAe,UAAU;AACxD,wBAAkB;AAAA,IACpB;AAGA,UAAM;AAIN,UAAM,kBAAkB,KAAK,OAAO,UAAU,IAAI,UAAU;AAC5D,QAAI,CAAC,mBAAmB,iBAAiB,UAAU,WAAW,SAAS,WAAW;AAChF,YAAM,yBAAyB,YAAY,QAAQ,WAAW,SAAS,IAAI;AAAA,IAC7E;AAAA,EACF;AAAA,EAEA,OAAO,YAA0B;AAC/B,SAAK,YAAY,WAAW,UAAU;AACtC,SAAK,aAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,YAA6B;AAC7C,WAAO,KAAK,YAAY,cAAc,UAAU;AAAA,EAClD;AAAA,EAEA,MAAM,YAA0B;AAC9B,UAAM,OAAO,KAAK,YAAY,cAAc,UAAU;AACtD,QAAI,MAAM;AACR,WAAK,YAAY,MAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,YAAoB,SAA8C;AAC7E,UAAM,WAAW,KAAK,OAAO,YAAY,UAAU;AACnD,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,YAAY,UAAU,YAAY;AAAA,IACpD;AAEA,UAAM,aAAa,KAAK,YAAY,cAAc,UAAU;AAE5D,QAAI,YAAY,aAAa,QAAW;AACtC,WAAK;AAAA,QACH;AAAA,QACA,SAAS,YAAY;AAAA,QACrB,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IAEb,OAAO;AACL,YAAM,KAAK,MAAM,YAAY,OAAO;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,IAAI,cAAqC;AACvC,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,IAAI,YAAwB;AAC1B,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,UAAgB;AACd,SAAK,YAAY,MAAA;AACjB,SAAK,kBAAkB,QAAA;AACvB,SAAK,UAAU,MAAA;AACf,SAAK,iBAAiB,MAAA;AAAA,EACxB;AACF;"}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
export { ResourceLoader, ResourceConflictError } from './ResourceLoader';
|
|
6
6
|
export { TaskManager } from './TaskManager';
|
|
7
7
|
export { StreamFactory } from './StreamFactory';
|
|
8
|
-
export { EventHandlers } from './EventHandlers';
|
|
9
8
|
export { WindowByteRangeResolver } from './WindowByteRangeResolver';
|
|
10
9
|
export * from './types';
|
|
11
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stages/load/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stages/load/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,cAAc,SAAS,CAAC"}
|
|
@@ -3,16 +3,8 @@ import { Clip } from '../../model/types';
|
|
|
3
3
|
import { EventPayloadMap } from '../../event/events';
|
|
4
4
|
import { EventBus } from '../../event/EventBus';
|
|
5
5
|
import { CacheManager } from '../../cache/CacheManager';
|
|
6
|
+
import { WorkerPool } from '../../worker/WorkerPool';
|
|
6
7
|
|
|
7
|
-
export interface Orchestrator {
|
|
8
|
-
on(event: string, handler: (data: any) => void): void;
|
|
9
|
-
off(event: string, handler: (data: any) => void): void;
|
|
10
|
-
emit(event: string, data: any): void;
|
|
11
|
-
workers: {
|
|
12
|
-
get(type: string, id?: string, options?: Record<string, any>): Promise<any>;
|
|
13
|
-
};
|
|
14
|
-
cacheManager: CacheManager;
|
|
15
|
-
}
|
|
16
8
|
export interface LoaderConfig {
|
|
17
9
|
highWaterMark?: number;
|
|
18
10
|
maxConcurrent?: number;
|
|
@@ -80,7 +72,8 @@ export interface LoadTask {
|
|
|
80
72
|
reject?: (error: Error) => void;
|
|
81
73
|
}
|
|
82
74
|
export interface ResourceLoaderOptions {
|
|
83
|
-
|
|
75
|
+
cacheManager: CacheManager;
|
|
76
|
+
workerPool: WorkerPool;
|
|
84
77
|
eventBus?: EventBus<EventPayloadMap>;
|
|
85
78
|
config?: LoaderConfig;
|
|
86
79
|
onProgress?: (progress: LoadProgress) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/load/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/load/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/dist/cache/l2/L2Cache.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { binarySearchRange } from "../../utils/binary-search.js";
|
|
2
|
-
import { L2OPFSStore } from "./L2OPFSStore.js";
|
|
3
|
-
import { ChunkRecordStore } from "../storage/indexeddb/ChunkRecordStore.js";
|
|
4
|
-
class L2Cache {
|
|
5
|
-
opfsStore;
|
|
6
|
-
dbStore;
|
|
7
|
-
maxSize;
|
|
8
|
-
projectId;
|
|
9
|
-
initPromise = null;
|
|
10
|
-
constructor(config) {
|
|
11
|
-
this.maxSize = config.maxSizeMB * 1024 * 1024;
|
|
12
|
-
this.projectId = config.projectId;
|
|
13
|
-
this.opfsStore = new L2OPFSStore();
|
|
14
|
-
this.dbStore = new ChunkRecordStore();
|
|
15
|
-
}
|
|
16
|
-
async init() {
|
|
17
|
-
if (this.initPromise) return this.initPromise;
|
|
18
|
-
this.initPromise = (async () => {
|
|
19
|
-
await Promise.all([this.opfsStore.init(), this.dbStore.init()]);
|
|
20
|
-
})();
|
|
21
|
-
return this.initPromise;
|
|
22
|
-
}
|
|
23
|
-
async get(timeUs, clipId) {
|
|
24
|
-
await this.init();
|
|
25
|
-
const records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);
|
|
26
|
-
for (const record of records) {
|
|
27
|
-
const batch = binarySearchRange(record.batches, timeUs, (b, _index) => ({
|
|
28
|
-
start: b.startUs,
|
|
29
|
-
end: b.startUs + b.durationUs
|
|
30
|
-
}));
|
|
31
|
-
if (!batch) {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const chunkData = await this.opfsStore.read(record.fileName, batch, this.projectId);
|
|
35
|
-
if (!chunkData) continue;
|
|
36
|
-
await this.dbStore.updateLastAccess(this.projectId, record.clipId, record.track);
|
|
37
|
-
return this.createChunk(chunkData, timeUs, record.track, batch.type, batch.durationUs);
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
async put(clipId, chunks, track, options) {
|
|
42
|
-
await this.init();
|
|
43
|
-
if (chunks.length === 0) return;
|
|
44
|
-
const fileName = `clip-${clipId}-${track[0]}1.${track === "video" ? "webm" : "m4a"}`;
|
|
45
|
-
let existingRecord = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
46
|
-
if (existingRecord) {
|
|
47
|
-
const fileExists = await this.opfsStore.fileExists(existingRecord.fileName, this.projectId);
|
|
48
|
-
if (!fileExists) {
|
|
49
|
-
await this.deleteEntry(clipId, track);
|
|
50
|
-
existingRecord = null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
let chunksToWrite = chunks;
|
|
54
|
-
if (existingRecord && existingRecord.batches.length > 0) {
|
|
55
|
-
const lastBatch = existingRecord.batches[existingRecord.batches.length - 1];
|
|
56
|
-
if (lastBatch) {
|
|
57
|
-
const lastTimestamp = lastBatch.startUs;
|
|
58
|
-
chunksToWrite = chunks.filter((chunk) => chunk.timestamp > lastTimestamp);
|
|
59
|
-
if (chunksToWrite.length === 0) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const newBatches = await this.opfsStore.append(
|
|
65
|
-
fileName,
|
|
66
|
-
chunksToWrite,
|
|
67
|
-
existingRecord?.batches,
|
|
68
|
-
this.projectId
|
|
69
|
-
);
|
|
70
|
-
const record = {
|
|
71
|
-
projectId: this.projectId,
|
|
72
|
-
clipId,
|
|
73
|
-
track,
|
|
74
|
-
fileName,
|
|
75
|
-
batches: existingRecord?.batches ? [...existingRecord.batches, ...newBatches] : newBatches,
|
|
76
|
-
lastAccess: Date.now(),
|
|
77
|
-
totalBytes: (existingRecord?.totalBytes || 0) + newBatches.reduce((sum, b) => sum + b.byteLength, 0),
|
|
78
|
-
isComplete: options?.isComplete ?? existingRecord?.isComplete ?? false,
|
|
79
|
-
expectedDurationUs: options?.expectedDurationUs ?? existingRecord?.expectedDurationUs,
|
|
80
|
-
metadata: options?.metadata ?? existingRecord?.metadata
|
|
81
|
-
};
|
|
82
|
-
await this.dbStore.putRecord(record);
|
|
83
|
-
await this.enforceQuota();
|
|
84
|
-
}
|
|
85
|
-
async invalidateRange(startUs, endUs, clipId) {
|
|
86
|
-
await this.init();
|
|
87
|
-
let records;
|
|
88
|
-
if (clipId) {
|
|
89
|
-
records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);
|
|
90
|
-
} else {
|
|
91
|
-
const allRecords = await this.dbStore.getAllRecords();
|
|
92
|
-
records = allRecords.filter((r) => r.projectId === this.projectId);
|
|
93
|
-
}
|
|
94
|
-
for (const record of records) {
|
|
95
|
-
const hasOverlap = record.batches.some((batch) => {
|
|
96
|
-
const batchEnd = batch.startUs + batch.durationUs;
|
|
97
|
-
return batch.startUs < endUs && batchEnd > startUs;
|
|
98
|
-
});
|
|
99
|
-
if (hasOverlap) {
|
|
100
|
-
await this.deleteEntry(record.clipId, record.track);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Check if clip has cached data in L2
|
|
106
|
-
*/
|
|
107
|
-
async hasClip(clipId, track) {
|
|
108
|
-
await this.init();
|
|
109
|
-
const record = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
110
|
-
return record !== null && record.batches && record.batches.length > 0;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Check if clip has complete cached data in L2
|
|
114
|
-
*/
|
|
115
|
-
async hasCompleteClip(clipId, track) {
|
|
116
|
-
await this.init();
|
|
117
|
-
const record = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
118
|
-
return record?.isComplete === true;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Mark clip as complete in L2 cache
|
|
122
|
-
*/
|
|
123
|
-
async markComplete(clipId, track) {
|
|
124
|
-
await this.init();
|
|
125
|
-
const record = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
126
|
-
if (record) {
|
|
127
|
-
record.isComplete = true;
|
|
128
|
-
record.lastAccess = Date.now();
|
|
129
|
-
await this.dbStore.putRecord(record);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async invalidateClip(clipId) {
|
|
133
|
-
await this.init();
|
|
134
|
-
const records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);
|
|
135
|
-
for (const record of records) {
|
|
136
|
-
await this.deleteEntry(record.clipId, record.track);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Create a readable stream of encoded chunks for export
|
|
141
|
-
* Reads chunks in timestamp order from OPFS
|
|
142
|
-
*/
|
|
143
|
-
async createReadStream(clipId, track) {
|
|
144
|
-
await this.init();
|
|
145
|
-
const record = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
146
|
-
if (!record || record.batches.length === 0) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
const batches = [...record.batches];
|
|
150
|
-
let batchIndex = 0;
|
|
151
|
-
return new ReadableStream({
|
|
152
|
-
pull: async (controller) => {
|
|
153
|
-
if (batchIndex >= batches.length) {
|
|
154
|
-
controller.close();
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const batch = batches[batchIndex];
|
|
158
|
-
if (!batch) {
|
|
159
|
-
controller.close();
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const chunkData = await this.opfsStore.read(record.fileName, batch, this.projectId);
|
|
164
|
-
if (!chunkData) {
|
|
165
|
-
controller.close();
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const chunk = this.createChunk(
|
|
169
|
-
chunkData,
|
|
170
|
-
batch.startUs,
|
|
171
|
-
track,
|
|
172
|
-
batch.type,
|
|
173
|
-
batch.durationUs
|
|
174
|
-
);
|
|
175
|
-
controller.enqueue(chunk);
|
|
176
|
-
batchIndex++;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if (error instanceof DOMException && error.name === "NotFoundError") {
|
|
179
|
-
controller.close();
|
|
180
|
-
} else {
|
|
181
|
-
controller.error(error);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
async clear() {
|
|
188
|
-
await this.init();
|
|
189
|
-
try {
|
|
190
|
-
await this.dbStore.clear();
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error("[L2Cache] Failed to clear IndexedDB:", error);
|
|
193
|
-
throw error;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
await this.opfsStore.clear(this.projectId);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
if (error?.name !== "NotFoundError") {
|
|
199
|
-
console.warn("[L2Cache] Failed to clear OPFS:", error);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
createChunk(data, timeUs, track, chunkType = "key", durationUs = 0) {
|
|
204
|
-
if (track === "video") {
|
|
205
|
-
return new EncodedVideoChunk({
|
|
206
|
-
type: chunkType,
|
|
207
|
-
timestamp: timeUs,
|
|
208
|
-
duration: durationUs,
|
|
209
|
-
data
|
|
210
|
-
});
|
|
211
|
-
} else {
|
|
212
|
-
return new EncodedAudioChunk({
|
|
213
|
-
type: chunkType,
|
|
214
|
-
timestamp: timeUs,
|
|
215
|
-
duration: durationUs,
|
|
216
|
-
data
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async deleteEntry(clipId, track, projectId) {
|
|
221
|
-
const targetProjectId = projectId ?? this.projectId;
|
|
222
|
-
const record = await this.dbStore.getRecord(targetProjectId, clipId, track);
|
|
223
|
-
if (record) {
|
|
224
|
-
await this.opfsStore.deleteFile(record.fileName, targetProjectId);
|
|
225
|
-
}
|
|
226
|
-
await this.dbStore.deleteRecord(targetProjectId, clipId, track);
|
|
227
|
-
}
|
|
228
|
-
async enforceQuota() {
|
|
229
|
-
const estimate = await navigator.storage.estimate();
|
|
230
|
-
const usage = estimate.usage || 0;
|
|
231
|
-
if (usage <= this.maxSize) return;
|
|
232
|
-
console.warn(
|
|
233
|
-
`[L2Cache] Quota exceeded! Deleting oldest entries: usage=${usage}, maxSize=${this.maxSize}`
|
|
234
|
-
);
|
|
235
|
-
const toDelete = usage - this.maxSize;
|
|
236
|
-
let bytesDeleted = 0;
|
|
237
|
-
const records = await this.dbStore.getAllRecordsSortedByAccess();
|
|
238
|
-
for (const record of records) {
|
|
239
|
-
if (bytesDeleted >= toDelete) break;
|
|
240
|
-
if (record.projectId !== this.projectId) {
|
|
241
|
-
await this.deleteEntry(record.clipId, record.track, record.projectId);
|
|
242
|
-
bytesDeleted += record.totalBytes;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (bytesDeleted < toDelete) {
|
|
246
|
-
for (const record of records) {
|
|
247
|
-
if (bytesDeleted >= toDelete) break;
|
|
248
|
-
if (record.projectId === this.projectId) {
|
|
249
|
-
await this.deleteEntry(record.clipId, record.track);
|
|
250
|
-
bytesDeleted += record.totalBytes;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
getMetadata() {
|
|
256
|
-
return {
|
|
257
|
-
maxSizeMB: this.maxSize / (1024 * 1024),
|
|
258
|
-
usedSizeMB: 0,
|
|
259
|
-
// Would need to track actual usage
|
|
260
|
-
entries: 0,
|
|
261
|
-
// Would need to track actual entries
|
|
262
|
-
hitRate: 0
|
|
263
|
-
// Would need to track hits and misses
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
async hasAvailableQuota(sizeMB) {
|
|
267
|
-
if (typeof navigator === "undefined" || !navigator.storage?.estimate) {
|
|
268
|
-
throw new Error("Storage API not available");
|
|
269
|
-
}
|
|
270
|
-
const estimate = await navigator.storage.estimate();
|
|
271
|
-
const availableMB = ((estimate.quota || 0) - (estimate.usage || 0)) / (1024 * 1024);
|
|
272
|
-
return availableMB >= sizeMB;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Get chunk metadata (decoderConfig) for a specific clip
|
|
276
|
-
*/
|
|
277
|
-
async getClipMetadata(clipId, track) {
|
|
278
|
-
await this.init();
|
|
279
|
-
const record = await this.dbStore.getRecord(this.projectId, clipId, track);
|
|
280
|
-
return record?.metadata || null;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* List all cached projects
|
|
284
|
-
*/
|
|
285
|
-
async listProjects() {
|
|
286
|
-
await this.init();
|
|
287
|
-
const records = await this.dbStore.getAllRecords();
|
|
288
|
-
const projects = /* @__PURE__ */ new Map();
|
|
289
|
-
for (const record of records) {
|
|
290
|
-
const existing = projects.get(record.projectId) || {
|
|
291
|
-
totalBytes: 0,
|
|
292
|
-
clipCount: 0,
|
|
293
|
-
lastAccess: 0
|
|
294
|
-
};
|
|
295
|
-
projects.set(record.projectId, {
|
|
296
|
-
totalBytes: existing.totalBytes + record.totalBytes,
|
|
297
|
-
clipCount: existing.clipCount + 1,
|
|
298
|
-
lastAccess: Math.max(existing.lastAccess, record.lastAccess)
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
return Array.from(projects.entries()).map(([projectId, stats]) => ({
|
|
302
|
-
projectId,
|
|
303
|
-
...stats
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Clear all cache data for a specific project
|
|
308
|
-
*/
|
|
309
|
-
async clearProject(targetProjectId) {
|
|
310
|
-
await this.init();
|
|
311
|
-
const records = await this.dbStore.getRecordsByProjectId(targetProjectId);
|
|
312
|
-
for (const record of records) {
|
|
313
|
-
await this.deleteEntry(record.clipId, record.track, targetProjectId);
|
|
314
|
-
}
|
|
315
|
-
await this.opfsStore.deleteProjectDirectory(targetProjectId);
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Get cache size for a specific project
|
|
319
|
-
*/
|
|
320
|
-
async getProjectSize(projectId) {
|
|
321
|
-
await this.init();
|
|
322
|
-
const records = await this.dbStore.getRecordsByProjectId(projectId);
|
|
323
|
-
return records.reduce((sum, r) => sum + r.totalBytes, 0);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
export {
|
|
327
|
-
L2Cache
|
|
328
|
-
};
|
|
329
|
-
//# sourceMappingURL=L2Cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"L2Cache.js","sources":["../../../src/cache/l2/L2Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { binarySearchRange } from '../../utils/binary-search';\nimport { L2OPFSStore } from './L2OPFSStore';\nimport { ChunkRecordStore, type ChunkRecord } from '../storage/indexeddb/ChunkRecordStore';\n\ninterface L2Config {\n maxSizeMB: number;\n projectId: string;\n}\n\n/**\n * L2 Cache - High-level cache coordinator\n * Uses OPFSStore for file storage and IndexedDBStore for metadata\n */\nexport class L2Cache {\n private readonly opfsStore: L2OPFSStore;\n private readonly dbStore: ChunkRecordStore;\n readonly maxSize: number;\n readonly projectId: string;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: L2Config) {\n this.maxSize = config.maxSizeMB * 1024 * 1024;\n this.projectId = config.projectId;\n this.opfsStore = new L2OPFSStore();\n this.dbStore = new ChunkRecordStore();\n }\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = (async () => {\n await Promise.all([this.opfsStore.init(), this.dbStore.init()]);\n })();\n\n return this.initPromise;\n }\n\n async get(timeUs: TimeUs, clipId: string): Promise<EncodedVideoChunk | EncodedAudioChunk | null> {\n await this.init();\n\n // Query IndexedDB for chunk metadata\n const records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);\n\n for (const record of records) {\n const batch = binarySearchRange(record.batches, timeUs, (b, _index) => ({\n start: b.startUs,\n end: b.startUs + b.durationUs,\n }));\n\n if (!batch) {\n continue;\n }\n\n const chunkData = await this.opfsStore.read(record.fileName, batch, this.projectId);\n if (!chunkData) continue;\n\n await this.dbStore.updateLastAccess(this.projectId, record.clipId, record.track);\n\n return this.createChunk(chunkData, timeUs, record.track, batch.type, batch.durationUs);\n }\n\n return null;\n }\n\n async put(\n clipId: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n track: 'video' | 'audio',\n options?: {\n isComplete?: boolean;\n expectedDurationUs?: number;\n metadata?: any;\n }\n ): Promise<void> {\n await this.init();\n\n if (chunks.length === 0) return;\n\n const fileName = `clip-${clipId}-${track[0]}1.${track === 'video' ? 'webm' : 'm4a'}`;\n\n // Step 1: Read existing record\n let existingRecord = await this.dbStore.getRecord(this.projectId, clipId, track);\n\n // Step 2: Validate consistency - if IndexedDB has record but OPFS file missing, delete the record\n if (existingRecord) {\n const fileExists = await this.opfsStore.fileExists(existingRecord.fileName, this.projectId);\n if (!fileExists) {\n await this.deleteEntry(clipId, track);\n existingRecord = null;\n }\n }\n\n // Step 3: Deduplicate based on timestamp\n let chunksToWrite = chunks;\n if (existingRecord && existingRecord.batches.length > 0) {\n const lastBatch = existingRecord.batches[existingRecord.batches.length - 1];\n if (lastBatch) {\n const lastTimestamp = lastBatch.startUs;\n // Filter out chunks with timestamp <= lastTimestamp\n chunksToWrite = chunks.filter((chunk) => chunk.timestamp > lastTimestamp);\n\n if (chunksToWrite.length === 0) {\n return;\n }\n }\n }\n\n // Step 4: Write to OPFS\n const newBatches = await this.opfsStore.append(\n fileName,\n chunksToWrite,\n existingRecord?.batches,\n this.projectId\n );\n\n // Step 5: Update IndexedDB\n const record: ChunkRecord = {\n projectId: this.projectId,\n clipId,\n track,\n fileName,\n batches: existingRecord?.batches ? [...existingRecord.batches, ...newBatches] : newBatches,\n lastAccess: Date.now(),\n totalBytes:\n (existingRecord?.totalBytes || 0) + newBatches.reduce((sum, b) => sum + b.byteLength, 0),\n isComplete: options?.isComplete ?? existingRecord?.isComplete ?? false,\n expectedDurationUs: options?.expectedDurationUs ?? existingRecord?.expectedDurationUs,\n metadata: options?.metadata ?? existingRecord?.metadata,\n };\n\n await this.dbStore.putRecord(record);\n\n // Check and enforce quota\n await this.enforceQuota();\n }\n\n async invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): Promise<void> {\n await this.init();\n\n // Get records to check\n let records: ChunkRecord[];\n if (clipId) {\n records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);\n } else {\n // Need all records for current project\n const allRecords = await this.dbStore.getAllRecords();\n records = allRecords.filter((r) => r.projectId === this.projectId);\n }\n\n // Check overlaps and delete\n for (const record of records) {\n const hasOverlap = record.batches.some((batch) => {\n const batchEnd = batch.startUs + batch.durationUs;\n return batch.startUs < endUs && batchEnd > startUs;\n });\n\n if (hasOverlap) {\n await this.deleteEntry(record.clipId, record.track);\n }\n }\n }\n\n /**\n * Check if clip has cached data in L2\n */\n async hasClip(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n await this.init();\n\n const record = await this.dbStore.getRecord(this.projectId, clipId, track);\n return record !== null && record.batches && record.batches.length > 0;\n }\n\n /**\n * Check if clip has complete cached data in L2\n */\n async hasCompleteClip(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n await this.init();\n\n const record = await this.dbStore.getRecord(this.projectId, clipId, track);\n return record?.isComplete === true;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.init();\n\n const record = await this.dbStore.getRecord(this.projectId, clipId, track);\n if (record) {\n record.isComplete = true;\n record.lastAccess = Date.now();\n await this.dbStore.putRecord(record);\n }\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n await this.init();\n\n // Collect records to delete\n const records = await this.dbStore.collectRecordsByClipId(this.projectId, clipId);\n\n // Delete each record\n for (const record of records) {\n await this.deleteEntry(record.clipId, record.track);\n }\n }\n\n /**\n * Create a readable stream of encoded chunks for export\n * Reads chunks in timestamp order from OPFS\n */\n async createReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n await this.init();\n\n // Get chunk record\n const record = await this.dbStore.getRecord(this.projectId, clipId, track);\n\n if (!record || record.batches.length === 0) {\n return null;\n }\n\n // Clone batches array for stream iteration\n const batches = [...record.batches];\n let batchIndex = 0;\n\n return new ReadableStream<EncodedVideoChunk | EncodedAudioChunk>({\n pull: async (controller) => {\n if (batchIndex >= batches.length) {\n controller.close();\n return;\n }\n\n const batch = batches[batchIndex];\n if (!batch) {\n controller.close();\n return;\n }\n\n try {\n // Read chunk data from OPFS\n const chunkData = await this.opfsStore.read(record.fileName, batch, this.projectId);\n if (!chunkData) {\n controller.close();\n return;\n }\n\n // Create encoded chunk with correct type and duration\n const chunk = this.createChunk(\n chunkData,\n batch.startUs,\n track,\n batch.type,\n batch.durationUs\n );\n controller.enqueue(chunk);\n\n batchIndex++;\n } catch (error) {\n // File not found or read error - close stream gracefully\n if (error instanceof DOMException && error.name === 'NotFoundError') {\n controller.close();\n } else {\n controller.error(error);\n }\n }\n },\n });\n }\n\n async clear(): Promise<void> {\n await this.init();\n\n // Clear IndexedDB\n try {\n await this.dbStore.clear();\n } catch (error) {\n console.error('[L2Cache] Failed to clear IndexedDB:', error);\n throw error;\n }\n\n // Clear OPFS files\n try {\n await this.opfsStore.clear(this.projectId);\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn('[L2Cache] Failed to clear OPFS:', error);\n }\n }\n }\n\n private createChunk(\n data: ArrayBuffer,\n timeUs: TimeUs,\n track: 'video' | 'audio',\n chunkType: 'key' | 'delta' = 'key',\n durationUs: TimeUs = 0\n ): EncodedVideoChunk | EncodedAudioChunk {\n if (track === 'video') {\n return new EncodedVideoChunk({\n type: chunkType,\n timestamp: timeUs,\n duration: durationUs,\n data,\n });\n } else {\n return new EncodedAudioChunk({\n type: chunkType,\n timestamp: timeUs,\n duration: durationUs,\n data,\n });\n }\n }\n\n private async deleteEntry(clipId: string, track: string, projectId?: string): Promise<void> {\n const targetProjectId = projectId ?? this.projectId;\n\n // Step 1: Get record info\n const record = await this.dbStore.getRecord(targetProjectId, clipId, track);\n\n // Step 2: Delete OPFS file\n if (record) {\n await this.opfsStore.deleteFile(record.fileName, targetProjectId);\n }\n\n // Step 3: Delete IndexedDB record\n await this.dbStore.deleteRecord(targetProjectId, clipId, track);\n }\n\n private async enforceQuota(): Promise<void> {\n const estimate = await navigator.storage.estimate();\n const usage = estimate.usage || 0;\n\n if (usage <= this.maxSize) return;\n\n console.warn(\n `[L2Cache] Quota exceeded! Deleting oldest entries: usage=${usage}, maxSize=${this.maxSize}`\n );\n\n const toDelete = usage - this.maxSize;\n let bytesDeleted = 0;\n\n // Get all records sorted by lastAccess\n const records = await this.dbStore.getAllRecordsSortedByAccess();\n\n // Step 1: Delete oldest entries from OTHER projects (protect current project)\n for (const record of records) {\n if (bytesDeleted >= toDelete) break;\n\n if (record.projectId !== this.projectId) {\n await this.deleteEntry(record.clipId, record.track, record.projectId);\n bytesDeleted += record.totalBytes;\n }\n }\n\n // Step 2: If still over quota, delete oldest from current project\n if (bytesDeleted < toDelete) {\n for (const record of records) {\n if (bytesDeleted >= toDelete) break;\n\n if (record.projectId === this.projectId) {\n await this.deleteEntry(record.clipId, record.track);\n bytesDeleted += record.totalBytes;\n }\n }\n }\n }\n\n getMetadata(): {\n maxSizeMB: number;\n usedSizeMB: number;\n entries: number;\n hitRate: number;\n } {\n // This is a simplified implementation\n // In a real implementation, we would track actual usage\n return {\n maxSizeMB: this.maxSize / (1024 * 1024),\n usedSizeMB: 0, // Would need to track actual usage\n entries: 0, // Would need to track actual entries\n hitRate: 0, // Would need to track hits and misses\n };\n }\n\n async hasAvailableQuota(sizeMB: number): Promise<boolean> {\n if (typeof navigator === 'undefined' || !navigator.storage?.estimate) {\n // L2Cache requires storage API to function\n throw new Error('Storage API not available');\n }\n\n const estimate = await navigator.storage.estimate();\n const availableMB = ((estimate.quota || 0) - (estimate.usage || 0)) / (1024 * 1024);\n return availableMB >= sizeMB;\n }\n\n /**\n * Get chunk metadata (decoderConfig) for a specific clip\n */\n async getClipMetadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n await this.init();\n\n const record = await this.dbStore.getRecord(this.projectId, clipId, track);\n return record?.metadata || null;\n }\n\n /**\n * List all cached projects\n */\n async listProjects(): Promise<\n Array<{ projectId: string; totalBytes: number; clipCount: number; lastAccess: number }>\n > {\n await this.init();\n\n const records = await this.dbStore.getAllRecords();\n const projects = new Map<\n string,\n { totalBytes: number; clipCount: number; lastAccess: number }\n >();\n\n // Aggregate stats per project\n for (const record of records) {\n const existing = projects.get(record.projectId) || {\n totalBytes: 0,\n clipCount: 0,\n lastAccess: 0,\n };\n\n projects.set(record.projectId, {\n totalBytes: existing.totalBytes + record.totalBytes,\n clipCount: existing.clipCount + 1,\n lastAccess: Math.max(existing.lastAccess, record.lastAccess),\n });\n }\n\n return Array.from(projects.entries()).map(([projectId, stats]) => ({\n projectId,\n ...stats,\n }));\n }\n\n /**\n * Clear all cache data for a specific project\n */\n async clearProject(targetProjectId: string): Promise<void> {\n await this.init();\n\n // 1. Collect all records for this project\n const records = await this.dbStore.getRecordsByProjectId(targetProjectId);\n\n // 2. Delete OPFS files and IndexedDB records\n for (const record of records) {\n await this.deleteEntry(record.clipId, record.track, targetProjectId);\n }\n\n // 3. Delete project directory (recursive, even if not empty)\n await this.opfsStore.deleteProjectDirectory(targetProjectId);\n }\n\n /**\n * Get cache size for a specific project\n */\n async getProjectSize(projectId: string): Promise<number> {\n await this.init();\n\n const records = await this.dbStore.getRecordsByProjectId(projectId);\n return records.reduce((sum, r) => sum + r.totalBytes, 0);\n }\n}\n"],"names":[],"mappings":"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"}
|