@meframe/core 0.3.3 → 0.3.5

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.
Files changed (67) hide show
  1. package/README.md +4 -6
  2. package/dist/event/events.d.ts +4 -2
  3. package/dist/event/events.d.ts.map +1 -1
  4. package/dist/event/events.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
  9. package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +2 -2
  10. package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
  11. package/dist/orchestrator/ExportScheduler.d.ts +2 -3
  12. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  13. package/dist/orchestrator/ExportScheduler.js +2 -3
  14. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  15. package/dist/orchestrator/Orchestrator.d.ts +2 -1
  16. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  17. package/dist/orchestrator/Orchestrator.js +26 -16
  18. package/dist/orchestrator/Orchestrator.js.map +1 -1
  19. package/dist/orchestrator/VideoClipSession.d.ts +1 -1
  20. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  21. package/dist/orchestrator/VideoClipSession.js +2 -2
  22. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  23. package/dist/orchestrator/{OnDemandVideoSession.d.ts → VideoWindowDecodeSession.d.ts} +4 -4
  24. package/dist/orchestrator/VideoWindowDecodeSession.d.ts.map +1 -0
  25. package/dist/orchestrator/{OnDemandVideoSession.js → VideoWindowDecodeSession.js} +10 -10
  26. package/dist/orchestrator/VideoWindowDecodeSession.js.map +1 -0
  27. package/dist/stages/decode/index.d.ts +0 -1
  28. package/dist/stages/decode/index.d.ts.map +1 -1
  29. package/dist/{utils/video-decoder-helpers.d.ts → stages/decode/video-decoder.d.ts} +1 -1
  30. package/dist/stages/decode/video-decoder.d.ts.map +1 -0
  31. package/dist/{utils/video-decoder-helpers.js → stages/decode/video-decoder.js} +2 -2
  32. package/dist/stages/decode/video-decoder.js.map +1 -0
  33. package/dist/stages/mux/MP4Muxer.js +1 -1
  34. package/dist/utils/mp4box.js +1 -1
  35. package/dist/worker/WorkerPool.d.ts.map +1 -1
  36. package/dist/worker/WorkerPool.js +1 -5
  37. package/dist/worker/WorkerPool.js.map +1 -1
  38. package/dist/worker/types.d.ts +1 -1
  39. package/dist/worker/types.d.ts.map +1 -1
  40. package/dist/worker/types.js.map +1 -1
  41. package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
  42. package/dist/worker/worker-manifest.d.ts +2 -2
  43. package/dist/worker/worker-manifest.js.map +1 -1
  44. package/dist/workers/WorkerChannel.DQK8rAab.js.map +1 -1
  45. package/dist/workers/worker-manifest.json +0 -4
  46. package/package.json +1 -1
  47. package/dist/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
  48. package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
  49. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +0 -1
  50. package/dist/orchestrator/OnDemandVideoSession.js.map +0 -1
  51. package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -49
  52. package/dist/stages/decode/VideoChunkDecoder.d.ts.map +0 -1
  53. package/dist/utils/video-decoder-helpers.d.ts.map +0 -1
  54. package/dist/utils/video-decoder-helpers.js.map +0 -1
  55. package/dist/workers/BaseDecoder.CB5XmTpS.js +0 -137
  56. package/dist/workers/BaseDecoder.CB5XmTpS.js.map +0 -1
  57. package/dist/workers/MP4Demuxer.DfWiwyjB.js +0 -7396
  58. package/dist/workers/MP4Demuxer.DfWiwyjB.js.map +0 -1
  59. package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js +0 -320
  60. package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js.map +0 -1
  61. package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js +0 -334
  62. package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js.map +0 -1
  63. package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js +0 -502
  64. package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js.map +0 -1
  65. package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js +0 -210
  66. package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js.map +0 -1
  67. /package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { hasResourceId } from '../model/types';\nimport { OnDemandVideoSession } from './OnDemandVideoSession';\n\ninterface VideoClipSessionCallbacks {\n onEncodedStreamReady(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n track: 'video' | 'audio'\n ): Promise<void>;\n onAudioStreamReady?(\n stream: ReadableStream<AudioData>,\n metadata: {\n sessionId: string;\n clipStartUs: number;\n clipDurationUs: number;\n }\n ): void;\n onStreamDisposed?(): void;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n sessionId?: string;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n resourceLoader?: ResourceLoader;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\n/**\n * VideoClipSession - Export-only session for rendering video clips\n *\n * Pipeline: OnDemandVideoSession (decode) -> ComposeWorker -> EncodeWorker\n * All sessions now use this architecture for export processing\n */\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly sessionId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n private readonly resourceLoader?: ResourceLoader;\n\n private instructionContext: InstructionContext | null = null;\n private composeWorker: BaseWorker | null = null;\n private videoEncodeWorker: BaseWorker | null = null;\n private onDemandSession: OnDemandVideoSession | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private composeToEncodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.sessionId = config.sessionId ?? config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n this.resourceLoader = config.resourceLoader;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n // Prepare instructions (but don't send yet - will send with stream)\n await this.ensureInstructions();\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n console.warn('[VideoClipSession] activate: clip or resource not found', this.sessionId);\n return;\n }\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n } else {\n console.warn(\n '[VideoClipSession] Unknown resource type:',\n resource.type,\n 'for',\n this.sessionId\n );\n }\n\n this.isActive = true;\n\n // Note: Attachment resources are loaded in connectVideoPipeline/connectImagePipeline\n // before sending video stream, ensuring watermarks appear from the first frame\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n await this.releasePipeline();\n this.isActive = false;\n }\n await this.activate();\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip || !hasResourceId(clip)) return null;\n return this.compositionModel.getResource(clip.resourceId) ?? null;\n }\n\n private extractAttachmentImageResources(): string[] {\n if (!this.instructionContext?.instructions) return [];\n\n const resourceIdSet = new Set<string>();\n for (const layer of this.instructionContext.instructions.layers) {\n // Only process attachment layers (with attachmentId)\n if (!layer.payload.attachmentId) continue;\n\n if (layer.type === 'image') {\n const imagePayload = layer.payload;\n if (imagePayload.oldResourceId) {\n resourceIdSet.add(imagePayload.oldResourceId);\n }\n const resourceId = imagePayload.resourceId;\n if (resourceId) {\n resourceIdSet.add(resourceId);\n }\n }\n }\n return Array.from(resourceIdSet);\n }\n\n /**\n * Load and transfer attachment images to ComposeWorker\n * Must be called after workers are created and before sending video stream\n */\n private async loadAndTransferAttachments(sessionId: string, clipId: string): Promise<void> {\n const attachmentResources = this.extractAttachmentImageResources();\n if (attachmentResources.length === 0 || !this.resourceLoader) {\n return;\n }\n\n // Parallel load all attachments\n await Promise.all(\n attachmentResources.map(async (resourceId) => {\n const resource = this.compositionModel.getResource(resourceId);\n if (!resource) {\n console.warn(`[VideoClipSession] Resource not found: ${resourceId}`);\n return;\n }\n const imageBitmap = await this.resourceLoader!.loadImage(resource);\n if (!imageBitmap) {\n console.warn(`[VideoClipSession] Failed to load attachment: ${resourceId}`);\n return;\n }\n\n await this.composeWorker!.send(\n 'receive_image',\n { clipId, resourceId, sessionId, imageBitmap },\n { transfer: [imageBitmap] }\n );\n })\n );\n }\n\n private async ensureInstructions(): Promise<void> {\n // Always get fresh clip from model to ensure latest attachments\n const clip = this.getClip();\n if (!clip) {\n throw new Error(`Clip ${this.clipId} not found`);\n }\n\n // For export, always build fresh instructions (no cache) to ensure latest attachments\n const plan = this.planner.buildClipPlan(clip, { cache: false });\n await this.installInstructions(plan.instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip);\n await this.connectImagePipeline();\n }\n\n private async connectImagePipeline(): Promise<void> {\n if (!this.composeWorker || !this.videoEncodeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n if (!this.resourceLoader) {\n throw new Error('[VideoClipSession] ResourceLoader not available for image pipeline');\n }\n\n const sessionId = this.sessionId;\n const clip = this.getClip();\n if (!clip) {\n throw new Error('[VideoClipSession] Clip not found for pipeline connection');\n }\n\n if (!this.instructionContext) {\n throw new Error('[VideoClipSession] Instructions not installed before connecting pipeline');\n }\n\n // STEP 1: Connect ComposeWorker -> EncodeWorker\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n\n // STEP 2: Setup EncodeWorker stream receiver\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n\n // STEP 3: Load and transfer attachment images (before sending main track image)\n await this.loadAndTransferAttachments(sessionId, clip.id);\n\n // STEP 4: Load main track image and send to ComposeWorker with instructions\n if (hasResourceId(clip)) {\n const resource = this.getResource();\n if (!resource) {\n throw new Error('[VideoClipSession] Resource not found for image pipeline');\n }\n\n // Load image directly\n const imageBitmap = await this.resourceLoader.loadImage(resource);\n\n // Send to ComposeWorker with instructions (same as video pipeline metadata)\n await this.composeWorker.send(\n 'receive_image',\n {\n resourceId: resource.id,\n sessionId,\n imageBitmap,\n instructions: this.instructionContext.instructions,\n },\n { transfer: [imageBitmap] }\n );\n }\n }\n\n private async setupVideoPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip);\n\n // Create OnDemandVideoSession for main-thread decoding\n this.onDemandSession = await OnDemandVideoSession.create({\n clipId: this.clipId,\n resourceId: resource.id,\n targetTimeUs: clip.trimStartUs ?? 0,\n globalTimeUs: clip.startUs,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n resourceLoader: this.resourceLoader!,\n fps: this.compositionModel.fps ?? 30,\n });\n\n await this.connectVideoPipeline();\n }\n\n private async acquireWorkers(clip: Clip): Promise<void> {\n // VideoComposeWorker\n this.composeWorker = await this.workerPool.getOrCreate('videoCompose', this.sessionId, {\n lazy: true,\n });\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.composeWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n\n // VideoEncodeWorker (always needed for export)\n this.videoEncodeWorker = await this.workerPool.getOrCreate('videoEncode', this.sessionId, {\n lazy: true,\n });\n const encodeConfig = this.workerConfigs.videoEncode ?? {};\n const encoderConfig = { ...encodeConfig };\n if (this.compositionModel.renderConfig?.width) {\n encoderConfig.width = this.compositionModel.renderConfig.width;\n }\n if (this.compositionModel.renderConfig?.height) {\n encoderConfig.height = this.compositionModel.renderConfig.height;\n }\n await this.videoEncodeWorker.send('configure', {\n initial: true,\n config: encoderConfig,\n });\n }\n\n private async connectVideoPipeline(): Promise<void> {\n if (!this.composeWorker || !this.videoEncodeWorker || !this.onDemandSession) {\n throw new Error('Pipeline workers not ready');\n }\n\n const sessionId = this.sessionId;\n const clip = this.getClip();\n if (!clip) {\n throw new Error('[VideoClipSession] Clip not found for pipeline connection');\n }\n\n if (!this.instructionContext) {\n throw new Error('[VideoClipSession] Instructions not installed before connecting pipeline');\n }\n\n // STEP 1: Connect ComposeWorker -> EncodeWorker\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n\n // STEP 2: Setup EncodeWorker stream receiver\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n\n // STEP 3: Load and transfer attachment images (before sending video stream)\n await this.loadAndTransferAttachments(sessionId, clip.id);\n\n // STEP 4: Start decoding and send frames to ComposeWorker\n const trimStartUs = clip.trimStartUs ?? 0;\n const frameStream = await this.onDemandSession.decodeRangeToStream(\n trimStartUs,\n trimStartUs + clip.durationUs\n );\n\n // Send VideoFrame stream to ComposeWorker (main thread → worker)\n // Include instructions in metadata to avoid race conditions\n await this.composeWorker.sendStream(frameStream, {\n sessionId,\n streamType: 'video',\n instructions: this.instructionContext.instructions,\n });\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.composeWorker && this.instructionContext) {\n await this.composeWorker.notify('dispose_clip', {\n sessionId: this.sessionId,\n revision: this.instructionContext.revision,\n });\n }\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed();\n }\n this.visualStream = null;\n\n // Cleanup OnDemandVideoSession\n if (this.onDemandSession) {\n await this.onDemandSession.dispose();\n this.onDemandSession = null;\n }\n\n // Terminate workers\n if (this.composeWorker) {\n this.workerPool.terminate('videoCompose', this.sessionId);\n this.composeWorker = null;\n }\n\n if (this.videoEncodeWorker) {\n this.workerPool.terminate('videoEncode', this.sessionId);\n this.videoEncodeWorker = null;\n }\n\n this.composeToEncodeChannel?.port1.close();\n this.composeToEncodeChannel?.port2.close();\n this.composeToEncodeChannel = null;\n }\n\n async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { hasResourceId } from '../model/types';\nimport { VideoWindowDecodeSession } from './VideoWindowDecodeSession';\n\ninterface VideoClipSessionCallbacks {\n onEncodedStreamReady(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n track: 'video' | 'audio'\n ): Promise<void>;\n onAudioStreamReady?(\n stream: ReadableStream<AudioData>,\n metadata: {\n sessionId: string;\n clipStartUs: number;\n clipDurationUs: number;\n }\n ): void;\n onStreamDisposed?(): void;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n sessionId?: string;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n resourceLoader?: ResourceLoader;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\n/**\n * VideoClipSession - Export-only session for rendering video clips\n *\n * Pipeline: VideoWindowDecodeSession (decode) -> ComposeWorker -> EncodeWorker\n * All sessions now use this architecture for export processing\n */\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly sessionId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n private readonly resourceLoader?: ResourceLoader;\n\n private instructionContext: InstructionContext | null = null;\n private composeWorker: BaseWorker | null = null;\n private videoEncodeWorker: BaseWorker | null = null;\n private onDemandSession: VideoWindowDecodeSession | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private composeToEncodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.sessionId = config.sessionId ?? config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n this.resourceLoader = config.resourceLoader;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n // Prepare instructions (but don't send yet - will send with stream)\n await this.ensureInstructions();\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n console.warn('[VideoClipSession] activate: clip or resource not found', this.sessionId);\n return;\n }\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n } else {\n console.warn(\n '[VideoClipSession] Unknown resource type:',\n resource.type,\n 'for',\n this.sessionId\n );\n }\n\n this.isActive = true;\n\n // Note: Attachment resources are loaded in connectVideoPipeline/connectImagePipeline\n // before sending video stream, ensuring watermarks appear from the first frame\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n await this.releasePipeline();\n this.isActive = false;\n }\n await this.activate();\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip || !hasResourceId(clip)) return null;\n return this.compositionModel.getResource(clip.resourceId) ?? null;\n }\n\n private extractAttachmentImageResources(): string[] {\n if (!this.instructionContext?.instructions) return [];\n\n const resourceIdSet = new Set<string>();\n for (const layer of this.instructionContext.instructions.layers) {\n // Only process attachment layers (with attachmentId)\n if (!layer.payload.attachmentId) continue;\n\n if (layer.type === 'image') {\n const imagePayload = layer.payload;\n if (imagePayload.oldResourceId) {\n resourceIdSet.add(imagePayload.oldResourceId);\n }\n const resourceId = imagePayload.resourceId;\n if (resourceId) {\n resourceIdSet.add(resourceId);\n }\n }\n }\n return Array.from(resourceIdSet);\n }\n\n /**\n * Load and transfer attachment images to ComposeWorker\n * Must be called after workers are created and before sending video stream\n */\n private async loadAndTransferAttachments(sessionId: string, clipId: string): Promise<void> {\n const attachmentResources = this.extractAttachmentImageResources();\n if (attachmentResources.length === 0 || !this.resourceLoader) {\n return;\n }\n\n // Parallel load all attachments\n await Promise.all(\n attachmentResources.map(async (resourceId) => {\n const resource = this.compositionModel.getResource(resourceId);\n if (!resource) {\n console.warn(`[VideoClipSession] Resource not found: ${resourceId}`);\n return;\n }\n const imageBitmap = await this.resourceLoader!.loadImage(resource);\n if (!imageBitmap) {\n console.warn(`[VideoClipSession] Failed to load attachment: ${resourceId}`);\n return;\n }\n\n await this.composeWorker!.send(\n 'receive_image',\n { clipId, resourceId, sessionId, imageBitmap },\n { transfer: [imageBitmap] }\n );\n })\n );\n }\n\n private async ensureInstructions(): Promise<void> {\n // Always get fresh clip from model to ensure latest attachments\n const clip = this.getClip();\n if (!clip) {\n throw new Error(`Clip ${this.clipId} not found`);\n }\n\n // For export, always build fresh instructions (no cache) to ensure latest attachments\n const plan = this.planner.buildClipPlan(clip, { cache: false });\n await this.installInstructions(plan.instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip);\n await this.connectImagePipeline();\n }\n\n private async connectImagePipeline(): Promise<void> {\n if (!this.composeWorker || !this.videoEncodeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n if (!this.resourceLoader) {\n throw new Error('[VideoClipSession] ResourceLoader not available for image pipeline');\n }\n\n const sessionId = this.sessionId;\n const clip = this.getClip();\n if (!clip) {\n throw new Error('[VideoClipSession] Clip not found for pipeline connection');\n }\n\n if (!this.instructionContext) {\n throw new Error('[VideoClipSession] Instructions not installed before connecting pipeline');\n }\n\n // STEP 1: Connect ComposeWorker -> EncodeWorker\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n\n // STEP 2: Setup EncodeWorker stream receiver\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n\n // STEP 3: Load and transfer attachment images (before sending main track image)\n await this.loadAndTransferAttachments(sessionId, clip.id);\n\n // STEP 4: Load main track image and send to ComposeWorker with instructions\n if (hasResourceId(clip)) {\n const resource = this.getResource();\n if (!resource) {\n throw new Error('[VideoClipSession] Resource not found for image pipeline');\n }\n\n // Load image directly\n const imageBitmap = await this.resourceLoader.loadImage(resource);\n\n // Send to ComposeWorker with instructions (same as video pipeline metadata)\n await this.composeWorker.send(\n 'receive_image',\n {\n resourceId: resource.id,\n sessionId,\n imageBitmap,\n instructions: this.instructionContext.instructions,\n },\n { transfer: [imageBitmap] }\n );\n }\n }\n\n private async setupVideoPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip);\n\n // Create VideoWindowDecodeSession for main-thread decoding\n this.onDemandSession = await VideoWindowDecodeSession.create({\n clipId: this.clipId,\n resourceId: resource.id,\n targetTimeUs: clip.trimStartUs ?? 0,\n globalTimeUs: clip.startUs,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n resourceLoader: this.resourceLoader!,\n fps: this.compositionModel.fps ?? 30,\n });\n\n await this.connectVideoPipeline();\n }\n\n private async acquireWorkers(clip: Clip): Promise<void> {\n // VideoComposeWorker\n this.composeWorker = await this.workerPool.getOrCreate('videoCompose', this.sessionId, {\n lazy: true,\n });\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.composeWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n\n // VideoEncodeWorker (always needed for export)\n this.videoEncodeWorker = await this.workerPool.getOrCreate('videoEncode', this.sessionId, {\n lazy: true,\n });\n const encodeConfig = this.workerConfigs.videoEncode ?? {};\n const encoderConfig = { ...encodeConfig };\n if (this.compositionModel.renderConfig?.width) {\n encoderConfig.width = this.compositionModel.renderConfig.width;\n }\n if (this.compositionModel.renderConfig?.height) {\n encoderConfig.height = this.compositionModel.renderConfig.height;\n }\n await this.videoEncodeWorker.send('configure', {\n initial: true,\n config: encoderConfig,\n });\n }\n\n private async connectVideoPipeline(): Promise<void> {\n if (!this.composeWorker || !this.videoEncodeWorker || !this.onDemandSession) {\n throw new Error('Pipeline workers not ready');\n }\n\n const sessionId = this.sessionId;\n const clip = this.getClip();\n if (!clip) {\n throw new Error('[VideoClipSession] Clip not found for pipeline connection');\n }\n\n if (!this.instructionContext) {\n throw new Error('[VideoClipSession] Instructions not installed before connecting pipeline');\n }\n\n // STEP 1: Connect ComposeWorker -> EncodeWorker\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n\n // STEP 2: Setup EncodeWorker stream receiver\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n\n // STEP 3: Load and transfer attachment images (before sending video stream)\n await this.loadAndTransferAttachments(sessionId, clip.id);\n\n // STEP 4: Start decoding and send frames to ComposeWorker\n const trimStartUs = clip.trimStartUs ?? 0;\n const frameStream = await this.onDemandSession.decodeRangeToStream(\n trimStartUs,\n trimStartUs + clip.durationUs\n );\n\n // Send VideoFrame stream to ComposeWorker (main thread → worker)\n // Include instructions in metadata to avoid race conditions\n await this.composeWorker.sendStream(frameStream, {\n sessionId,\n streamType: 'video',\n instructions: this.instructionContext.instructions,\n });\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.composeWorker && this.instructionContext) {\n await this.composeWorker.notify('dispose_clip', {\n sessionId: this.sessionId,\n revision: this.instructionContext.revision,\n });\n }\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed();\n }\n this.visualStream = null;\n\n // Cleanup VideoWindowDecodeSession\n if (this.onDemandSession) {\n await this.onDemandSession.dispose();\n this.onDemandSession = null;\n }\n\n // Terminate workers\n if (this.composeWorker) {\n this.workerPool.terminate('videoCompose', this.sessionId);\n this.composeWorker = null;\n }\n\n if (this.videoEncodeWorker) {\n this.workerPool.terminate('videoEncode', this.sessionId);\n this.videoEncodeWorker = null;\n }\n\n this.composeToEncodeChannel?.port1.close();\n this.composeToEncodeChannel?.port2.close();\n this.composeToEncodeChannel = null;\n }\n\n async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ import { TimeUs } from '../model/types';
5
5
  import { CompositionModel, Clip } from '../model';
6
6
  import { ResourceLoader } from '../stages/load/ResourceLoader';
7
7
 
8
- interface OnDemandVideoSessionConfig {
8
+ interface VideoWindowDecodeSessionConfig {
9
9
  clipId: string;
10
10
  resourceId: string;
11
11
  targetTimeUs: TimeUs;
@@ -29,7 +29,7 @@ interface OnDemandVideoSessionConfig {
29
29
  * - Worker overhead (10-50ms) is significant for small workloads
30
30
  * - Decode is fast enough
31
31
  */
32
- export declare class OnDemandVideoSession {
32
+ export declare class VideoWindowDecodeSession {
33
33
  /**
34
34
  * Static method to decode and cache first frame from extracted GOP chunks
35
35
  * Used by ResourceLoader during streaming download for fast cover rendering
@@ -48,7 +48,7 @@ export declare class OnDemandVideoSession {
48
48
  private aborted;
49
49
  private decodedFrames;
50
50
  private constructor();
51
- static create(config: OnDemandVideoSessionConfig): Promise<OnDemandVideoSession>;
51
+ static create(config: VideoWindowDecodeSessionConfig): Promise<VideoWindowDecodeSession>;
52
52
  private init;
53
53
  /**
54
54
  * Decode a window range, write raw frames to L1 cache
@@ -98,4 +98,4 @@ export declare class OnDemandVideoSession {
98
98
  dispose(): Promise<void>;
99
99
  }
100
100
  export {};
101
- //# sourceMappingURL=OnDemandVideoSession.d.ts.map
101
+ //# sourceMappingURL=VideoWindowDecodeSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VideoWindowDecodeSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoWindowDecodeSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAUpE,UAAU,8BAA8B;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,wBAAwB;IACnC;;;OAGG;WACU,wBAAwB,CACnC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAoChB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,UAAU,UAAS;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAoB;IAEzC,OAAO;WAYM,MAAM,CAAC,MAAM,EAAE,8BAA8B,GAAG,OAAO,CAAC,wBAAwB,CAAC;YAMhF,IAAI;IAIlB;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BjE;;;;OAIG;IACG,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBtD;;OAEG;YACW,mBAAmB;IAsBjC;;OAEG;YACW,mBAAmB;YAqCnB,wBAAwB;IAiFtC,OAAO,CAAC,6BAA6B;IAiCrC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,2BAA2B;IA8CnC;;;;;;OAMG;YACW,YAAY;YA0DZ,YAAY;IAsB1B;;OAEG;IACH,OAAO,CAAC,UAAU;YAcJ,kBAAkB;IA8BhC;;;OAGG;IACG,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;YA2FhF,6BAA6B;IAkBrC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAU/B"}
@@ -1,7 +1,7 @@
1
1
  import { binarySearchRange, binarySearchOverlapping } from "../utils/binary-search.js";
2
- import { decodeChunksWithoutFlush, decodeChunksForScrub } from "../utils/video-decoder-helpers.js";
2
+ import { decodeChunksWithoutFlush, decodeChunksForScrub } from "../stages/decode/video-decoder.js";
3
3
  import { ResourceCorruptedError } from "../utils/errors.js";
4
- class OnDemandVideoSession {
4
+ class VideoWindowDecodeSession {
5
5
  /**
6
6
  * Static method to decode and cache first frame from extracted GOP chunks
7
7
  * Used by ResourceLoader during streaming download for fast cover rendering
@@ -59,7 +59,7 @@ class OnDemandVideoSession {
59
59
  this.targetTimeUs = config.targetTimeUs;
60
60
  }
61
61
  static async create(config) {
62
- const session = new OnDemandVideoSession(config);
62
+ const session = new VideoWindowDecodeSession(config);
63
63
  await session.init();
64
64
  return session;
65
65
  }
@@ -74,7 +74,7 @@ class OnDemandVideoSession {
74
74
  }
75
75
  const resource = this.compositionModel.getResource(this.resourceId);
76
76
  if (!resource) {
77
- console.warn("[OnDemandVideoSession] Resource not found in composition model:", {
77
+ console.warn("[VideoWindowDecodeSession] Resource not found in composition model:", {
78
78
  resourceId: this.resourceId,
79
79
  clipId: this.clipId,
80
80
  startUs,
@@ -312,7 +312,7 @@ class OnDemandVideoSession {
312
312
  if (!sample) continue;
313
313
  const relativeOffset = sample.byteOffset - baseByteOffset;
314
314
  if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {
315
- console.warn("[OnDemandVideoSession] Sample outside buffer:", {
315
+ console.warn("[VideoWindowDecodeSession] Sample outside buffer:", {
316
316
  sampleOffset: sample.byteOffset,
317
317
  sampleLength: sample.byteLength,
318
318
  baseOffset: baseByteOffset,
@@ -394,7 +394,7 @@ class OnDemandVideoSession {
394
394
  async decodeRangeToStream(startUs, endUs) {
395
395
  const index = this.mp4IndexCache.get(this.resourceId);
396
396
  if (!index?.tracks.video) {
397
- throw new Error(`[OnDemandVideoSession] No video track index for ${this.resourceId}`);
397
+ throw new Error(`[VideoWindowDecodeSession] No video track index for ${this.resourceId}`);
398
398
  }
399
399
  const videoTrack = index.tracks.video;
400
400
  const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);
@@ -402,7 +402,7 @@ class OnDemandVideoSession {
402
402
  start: async (controller) => {
403
403
  try {
404
404
  if (gopWindow.gops.length === 0) {
405
- console.warn("[OnDemandVideoSession] No GOPs found for range");
405
+ console.warn("[VideoWindowDecodeSession] No GOPs found for range");
406
406
  controller.close();
407
407
  return;
408
408
  }
@@ -450,7 +450,7 @@ class OnDemandVideoSession {
450
450
  }
451
451
  controller.close();
452
452
  } catch (error) {
453
- console.error("[OnDemandVideoSession] decodeRangeToStream error:", error);
453
+ console.error("[VideoWindowDecodeSession] decodeRangeToStream error:", error);
454
454
  this.releaseDecodedFrames();
455
455
  controller.error(error);
456
456
  }
@@ -480,6 +480,6 @@ class OnDemandVideoSession {
480
480
  }
481
481
  }
482
482
  export {
483
- OnDemandVideoSession
483
+ VideoWindowDecodeSession
484
484
  };
485
- //# sourceMappingURL=OnDemandVideoSession.js.map
485
+ //# sourceMappingURL=VideoWindowDecodeSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VideoWindowDecodeSession.js","sources":["../../src/orchestrator/VideoWindowDecodeSession.ts"],"sourcesContent":["import type { CacheManager } from '../cache/CacheManager';\nimport type { MP4IndexCache } from '../cache/resource/MP4IndexCache';\nimport type { MP4Index, GOP, Sample } from '../stages/demux/types';\nimport type { TimeUs, Resource } from '../model/types';\nimport type { CompositionModel, Clip } from '../model';\nimport { binarySearchOverlapping, binarySearchRange } from '../utils/binary-search';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { decodeChunksForScrub, decodeChunksWithoutFlush } from '../stages/decode/video-decoder';\nimport { ResourceCorruptedError } from '../utils/errors';\n\ninterface GOPWindowResult {\n gops: GOP[];\n byteStart: number;\n byteEnd: number;\n}\n\ninterface VideoWindowDecodeSessionConfig {\n clipId: string;\n resourceId: string;\n targetTimeUs: TimeUs;\n globalTimeUs: TimeUs;\n mp4IndexCache: MP4IndexCache;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n resourceLoader: ResourceLoader;\n fps: number;\n}\n\n/**\n * Strategy:\n * 1. Read GOP range from OPFS\n * 2. Demux using MP4Box (main thread)\n * 3. Decode using VideoDecoder (main thread)\n * 4. Write RAW VideoFrames (YUV) to L1 cache\n * 5. Dispose immediately after window completes\n *\n * Why main thread?\n * - Window is small (±2s, ~60-120 frames)\n * - Worker overhead (10-50ms) is significant for small workloads\n * - Decode is fast enough\n */\nexport class VideoWindowDecodeSession {\n /**\n * Static method to decode and cache first frame from extracted GOP chunks\n * Used by ResourceLoader during streaming download for fast cover rendering\n */\n static async decodeAndCacheFirstFrame(\n _resourceId: string,\n chunks: EncodedVideoChunk[],\n index: MP4Index,\n clip: Clip,\n cacheManager: CacheManager,\n fps: number\n ): Promise<void> {\n if (chunks.length === 0) return;\n\n const videoTrack = index.tracks.video;\n if (!videoTrack) return;\n\n // Verify first chunk is keyframe\n const firstChunk = chunks[0];\n if (!firstChunk || firstChunk.type !== 'key') {\n return;\n }\n\n try {\n const result = await decodeChunksWithoutFlush(chunks, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n });\n\n // Cache all decoded frames\n const frameDuration = Math.round(1_000_000 / fps);\n for (const frame of result.frames) {\n const frameGlobalTime = clip.startUs + frame.timestamp;\n cacheManager.addFrame(\n frame,\n clip.id,\n frameDuration,\n clip.trackId ?? 'main',\n frameGlobalTime\n );\n }\n } catch {\n // Don't throw - this is a best-effort optimization\n }\n }\n private readonly clipId: string;\n private readonly resourceId: string;\n private readonly mp4IndexCache: MP4IndexCache;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly resourceLoader: ResourceLoader;\n private readonly fps: number;\n private readonly globalTimeUs: TimeUs;\n private readonly targetTimeUs: TimeUs;\n\n isDisposed = false;\n private aborted = false;\n private decodedFrames: VideoFrame[] = [];\n\n private constructor(config: VideoWindowDecodeSessionConfig) {\n this.clipId = config.clipId;\n this.resourceId = config.resourceId;\n this.mp4IndexCache = config.mp4IndexCache;\n this.cacheManager = config.cacheManager;\n this.resourceLoader = config.resourceLoader;\n this.compositionModel = config.compositionModel;\n this.fps = config.fps;\n this.globalTimeUs = config.globalTimeUs;\n this.targetTimeUs = config.targetTimeUs;\n }\n\n static async create(config: VideoWindowDecodeSessionConfig): Promise<VideoWindowDecodeSession> {\n const session = new VideoWindowDecodeSession(config);\n await session.init();\n return session;\n }\n\n private async init(): Promise<void> {\n // No special initialization needed for now\n }\n\n /**\n * Decode a window range, write raw frames to L1 cache\n */\n async decodeWindow(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n if (this.isDisposed) {\n throw new Error('Session already disposed');\n }\n\n const resource = this.compositionModel.getResource(this.resourceId);\n if (!resource) {\n console.warn('[VideoWindowDecodeSession] Resource not found in composition model:', {\n resourceId: this.resourceId,\n clipId: this.clipId,\n startUs,\n endUs,\n model: {\n fps: this.compositionModel.fps,\n durationUs: this.compositionModel.durationUs,\n mainTrackId: this.compositionModel.mainTrackId,\n trackCount: this.compositionModel.tracks.length,\n resourcesSize: this.compositionModel.resources.size,\n },\n });\n throw new Error(`Resource not found: ${this.resourceId}`);\n }\n\n if (resource.type === 'image') {\n await this.handleImageResource(resource, startUs, endUs);\n return;\n }\n\n await this.handleVideoResource(startUs, endUs);\n }\n\n /**\n * Scrub decode (timeline dragging):\n * Decode only the minimum frames needed to present a true frame near targetTimeUs.\n * This avoids the heavy 3s window decode used for playback preheating.\n */\n async decodeScrub(targetTimeUs: TimeUs): Promise<void> {\n if (this.isDisposed) {\n throw new Error('Session already disposed');\n }\n\n const resource = this.compositionModel.getResource(this.resourceId);\n if (!resource) {\n throw new Error(`Resource not found: ${this.resourceId}`);\n }\n\n if (resource.type === 'image') {\n const frameDuration = Math.max(1, Math.round(1_000_000 / this.fps));\n await this.handleImageResource(resource, targetTimeUs, targetTimeUs + frameDuration);\n return;\n }\n\n await this.handleVideoResourceScrub(targetTimeUs);\n }\n\n /**\n * Handle image resource by creating a VideoFrame from ImageBitmap\n */\n private async handleImageResource(\n resource: Resource,\n startUs: TimeUs,\n endUs: TimeUs\n ): Promise<void> {\n const image = await this.resourceLoader.loadImage(resource);\n if (!image) return;\n\n const frame = new VideoFrame(image, {\n timestamp: startUs,\n duration: endUs - startUs,\n });\n\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n endUs - startUs,\n this.compositionModel.mainTrackId,\n this.globalTimeUs\n );\n }\n\n /**\n * Handle video resource by decoding from OPFS\n */\n private async handleVideoResource(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index) {\n throw new Error(`No index found for resource ${this.resourceId}`);\n }\n\n // Calculate GOP ranges needed\n const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);\n if (gopWindow.gops.length === 0) {\n return;\n }\n\n // Read GOP data from OPFS\n const gopData = await this.readResourceRangeWithRecovery(\n gopWindow.byteStart,\n gopWindow.byteEnd\n );\n\n if (this.aborted) return;\n\n // Extract chunks from GOP data\n const chunks = await this.demuxGOPData(gopData, index, gopWindow);\n if (this.aborted) return;\n\n // Decode chunks to frames\n await this.decodeChunks(chunks, index);\n\n // Check abort and cleanup if needed\n if (this.aborted) {\n this.releaseDecodedFrames();\n return;\n }\n\n // Write frames to L1 cache\n await this.cacheDecodedFrames(startUs, endUs);\n }\n\n private async handleVideoResourceScrub(targetTimeUs: TimeUs): Promise<void> {\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index?.tracks.video) {\n throw new Error(`No video index found for resource ${this.resourceId}`);\n }\n\n const videoTrack = index.tracks.video;\n const { gopIndex, samples } = videoTrack;\n\n const gop = binarySearchRange(gopIndex, targetTimeUs, (g, idx) => {\n const next = gopIndex[idx + 1];\n return { start: g.startTimeUs, end: next ? next.startTimeUs : Infinity };\n });\n if (!gop) {\n // Fallback: use a tiny window decode.\n const frameDuration = Math.max(1, Math.round(1_000_000 / this.fps));\n await this.handleVideoResource(targetTimeUs, targetTimeUs + frameDuration);\n return;\n }\n\n const startIdx = gop.keyframeSampleIndex;\n const endIdx = Math.min(samples.length, startIdx + gop.sampleCount);\n if (startIdx >= endIdx) return;\n\n // Compute byte range for the GOP (sample offsets are not guaranteed monotonic).\n let byteStart = Infinity;\n let byteEnd = 0;\n for (let i = startIdx; i < endIdx; i++) {\n const s = samples[i];\n if (!s) continue;\n byteStart = Math.min(byteStart, s.byteOffset);\n byteEnd = Math.max(byteEnd, s.byteOffset + s.byteLength);\n }\n if (!Number.isFinite(byteStart) || byteEnd <= byteStart) return;\n\n const gopData = await this.readResourceRangeWithRecovery(byteStart, byteEnd);\n if (this.aborted) return;\n\n const chunks = this.buildEncodedChunksFromSamples(\n gopData,\n byteStart,\n samples,\n startIdx,\n endIdx\n );\n if (chunks.length === 0) return;\n\n const timeoutMs = this.cacheManager.isExporting ? 15_000 : 2_000;\n const { before, after } = await decodeChunksForScrub(\n chunks,\n {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n },\n targetTimeUs,\n {\n timeoutMs,\n maxQueueSize: 2,\n shouldAbort: () => this.aborted,\n }\n );\n\n if (this.aborted) {\n before?.close();\n after?.close();\n return;\n }\n\n // Cache only the closest frames around target.\n for (const frame of [before, after]) {\n if (!frame) continue;\n try {\n this.cacheFrame(frame);\n } catch {\n frame.close();\n }\n }\n }\n\n private buildEncodedChunksFromSamples(\n data: ArrayBuffer,\n baseByteOffset: number,\n samples: Sample[],\n startIdx: number,\n endIdx: number\n ): EncodedVideoChunk[] {\n const chunks: EncodedVideoChunk[] = [];\n const dataView = new Uint8Array(data);\n\n for (let i = startIdx; i < endIdx; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n const relativeOffset = sample.byteOffset - baseByteOffset;\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {\n continue;\n }\n\n const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);\n chunks.push(\n new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n })\n );\n }\n\n return chunks;\n }\n\n /**\n * Release all decoded frames without caching\n */\n private releaseDecodedFrames(): void {\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n }\n\n private calculateGOPRangesForWindow(\n index: MP4Index,\n startUs: TimeUs,\n endUs: TimeUs\n ): GOPWindowResult {\n if (!index.tracks.video) {\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n const { gopIndex, samples } = index.tracks.video;\n\n // Find GOP containing startUs (or the nearest keyframe before it)\n const nearestKeyframe = this.mp4IndexCache.getNearestKeyframe(this.resourceId, startUs);\n const decodeStartUs = nearestKeyframe?.timestamp ?? startUs;\n\n // Use binary search to find all overlapping GOPs\n const overlappingGOPs = binarySearchOverlapping(gopIndex, decodeStartUs, endUs, (gop, idx) => {\n const nextGOP = gopIndex[idx + 1];\n return {\n start: gop.startTimeUs,\n end: nextGOP ? nextGOP.startTimeUs : Infinity,\n };\n });\n\n if (overlappingGOPs.length === 0) {\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n // Calculate merged byte range for OPFS read\n let byteStart = Infinity;\n let byteEnd = 0;\n\n for (const gop of overlappingGOPs) {\n const startSample = samples[gop.keyframeSampleIndex];\n const endSampleIndex = gop.keyframeSampleIndex + gop.sampleCount - 1;\n const endSample = samples[endSampleIndex];\n\n if (startSample && endSample) {\n byteStart = Math.min(byteStart, startSample.byteOffset);\n byteEnd = Math.max(byteEnd, endSample.byteOffset + endSample.byteLength);\n }\n }\n\n return { gops: overlappingGOPs, byteStart, byteEnd };\n }\n\n /**\n * Extract video chunks from GOP data\n *\n * Directly use GOP sample indices to slice the samples array.\n * This is O(k) where k is the number of samples in the window,\n * vs O(n×m) for the old approach (n=all samples, m=GOP count).\n */\n private async demuxGOPData(\n data: ArrayBuffer,\n index: MP4Index,\n gopWindow: GOPWindowResult\n ): Promise<EncodedVideoChunk[]> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n const { samples } = videoTrack;\n const chunks: EncodedVideoChunk[] = [];\n const dataView = new Uint8Array(data);\n const baseByteOffset = gopWindow.byteStart;\n\n // Direct sample index slicing - iterate only samples in the window\n for (const gop of gopWindow.gops) {\n const startIdx = gop.keyframeSampleIndex;\n const endIdx = startIdx + gop.sampleCount;\n\n // Extract samples for this GOP (direct array slice)\n for (let i = startIdx; i < endIdx; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n // Calculate relative offset within the data buffer\n const relativeOffset = sample.byteOffset - baseByteOffset;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {\n console.warn('[VideoWindowDecodeSession] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n baseOffset: baseByteOffset,\n relativeOffset,\n bufferLength: data.byteLength,\n });\n continue;\n }\n\n // Extract sample data\n const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n // Create EncodedVideoChunk\n const chunk = new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n });\n\n chunks.push(chunk);\n }\n }\n\n return chunks;\n }\n\n private async decodeChunks(chunks: EncodedVideoChunk[], index: MP4Index): Promise<void> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n const timeoutMs = this.cacheManager.isExporting ? 15_000 : undefined;\n const result = await decodeChunksWithoutFlush(\n chunks,\n {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n },\n timeoutMs ? { timeoutMs } : undefined\n );\n\n // Store frames for caching\n this.decodedFrames = result.frames;\n }\n\n /**\n * Cache a single frame to L1 with proper timestamp calculations\n */\n private cacheFrame(frame: VideoFrame, globalTimeOffset: TimeUs = 0): void {\n const frameDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n const frameGlobalTime =\n this.globalTimeUs + (frame.timestamp - this.targetTimeUs) + globalTimeOffset;\n\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n frameDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n }\n\n private async cacheDecodedFrames(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const framesToCache: VideoFrame[] = [];\n const framesToDiscard: VideoFrame[] = [];\n\n // Partition frames into cacheable and discardable\n for (const frame of this.decodedFrames) {\n if (frame.timestamp >= startUs && frame.timestamp < endUs) {\n framesToCache.push(frame);\n } else {\n framesToDiscard.push(frame);\n }\n }\n\n // Cache frames within window\n for (const frame of framesToCache) {\n try {\n this.cacheFrame(frame);\n } catch {\n frame.close();\n }\n }\n\n // Release frames outside window\n for (const frame of framesToDiscard) {\n frame.close();\n }\n\n this.decodedFrames = [];\n }\n\n /**\n * Decode entire time range to VideoFrame stream (for export)\n * Does NOT cache to L1 - outputs frames directly for Worker pipeline\n */\n async decodeRangeToStream(startUs: TimeUs, endUs: TimeUs): Promise<ReadableStream<VideoFrame>> {\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index?.tracks.video) {\n throw new Error(`[VideoWindowDecodeSession] No video track index for ${this.resourceId}`);\n }\n\n const videoTrack = index.tracks.video;\n const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);\n\n return new ReadableStream<VideoFrame>({\n start: async (controller) => {\n try {\n if (gopWindow.gops.length === 0) {\n console.warn('[VideoWindowDecodeSession] No GOPs found for range');\n controller.close();\n return;\n }\n\n // Process GOPs in batches (10 GOPs at a time for memory control)\n const batchSize = 10;\n for (let i = 0; i < gopWindow.gops.length; i += batchSize) {\n if (this.aborted) {\n controller.close();\n return;\n }\n\n const batchGOPs = gopWindow.gops.slice(\n i,\n Math.min(i + batchSize, gopWindow.gops.length)\n );\n\n // Calculate byte range for this batch\n let batchByteStart = Infinity;\n let batchByteEnd = 0;\n for (const gop of batchGOPs) {\n const startSample = videoTrack.samples[gop.keyframeSampleIndex];\n const endSampleIdx = gop.keyframeSampleIndex + gop.sampleCount - 1;\n const endSample = videoTrack.samples[endSampleIdx];\n if (startSample && endSample) {\n batchByteStart = Math.min(batchByteStart, startSample.byteOffset);\n batchByteEnd = Math.max(batchByteEnd, endSample.byteOffset + endSample.byteLength);\n }\n }\n\n // Read GOP batch data from OPFS\n const gopData = await this.readResourceRangeWithRecovery(batchByteStart, batchByteEnd);\n\n if (this.aborted) {\n controller.close();\n return;\n }\n\n // Extract chunks from GOP batch\n const batchChunks = await this.demuxGOPData(gopData, index, {\n gops: batchGOPs,\n byteStart: batchByteStart,\n byteEnd: batchByteEnd,\n });\n\n // Decode chunks to frames\n await this.decodeChunks(batchChunks, index);\n\n if (this.aborted) {\n this.releaseDecodedFrames();\n controller.close();\n return;\n }\n\n // Enqueue decoded frames (DO NOT cache to L1)\n for (const frame of this.decodedFrames) {\n controller.enqueue(frame);\n }\n\n // Clear frames array (ownership transferred to stream)\n this.decodedFrames = [];\n }\n\n controller.close();\n } catch (error) {\n console.error('[VideoWindowDecodeSession] decodeRangeToStream error:', error);\n this.releaseDecodedFrames();\n controller.error(error);\n }\n },\n cancel: () => {\n this.aborted = true;\n this.releaseDecodedFrames();\n },\n });\n }\n\n private async readResourceRangeWithRecovery(start: number, end: number): Promise<ArrayBuffer> {\n try {\n return await this.cacheManager.readResourceRange(this.resourceId, start, end);\n } catch (error) {\n if (!(error instanceof ResourceCorruptedError)) throw error;\n\n // Unify recovery behavior:\n // 1) Invalidate OPFS + index (ensure cache consistency)\n // 2) Reload to OPFS + rebuild index\n // 3) Retry the read once\n await this.cacheManager.resourceCache.deleteResource(this.resourceId);\n this.cacheManager.mp4IndexCache.delete(this.resourceId);\n\n await this.resourceLoader.load(this.resourceId, { isPreload: false, clipId: this.clipId });\n return await this.cacheManager.readResourceRange(this.resourceId, start, end);\n }\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n\n this.aborted = true;\n\n // Release all decoded frames\n this.releaseDecodedFrames();\n\n this.isDisposed = true;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  export { BaseDecoder } from './BaseDecoder';
2
- export { VideoChunkDecoder } from './VideoChunkDecoder';
3
2
  export { AudioChunkDecoder } from './AudioChunkDecoder';
4
3
  export type { DecoderConfig, VideoDecoderConfig, AudioDecoderConfig, DecodeRequest, DecodeResult, SeekRequest, GOPInfo, DecoderStats, DecoderPoolConfig, DecoderState, DecoderInstance, DecoderMessage, DecoderResponse, } from './types';
5
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,eAAe,GAChB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,eAAe,GAChB,MAAM,SAAS,CAAC"}
@@ -56,4 +56,4 @@ export declare function decodeChunksForScrub(chunks: EncodedVideoChunk[], config
56
56
  * - Timeout fallback ensures no infinite hang
57
57
  */
58
58
  export declare function decodeChunksWithoutFlush(chunks: EncodedVideoChunk[], config: VideoDecoderConfig, options?: DecodeOptions): Promise<DecodeResult>;
59
- //# sourceMappingURL=video-decoder-helpers.d.ts.map
59
+ //# sourceMappingURL=video-decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-decoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/video-decoder.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwJ5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAiFvB"}
@@ -1,4 +1,4 @@
1
- import { getRecommendedHardwareAcceleration } from "./platform-utils.js";
1
+ import { getRecommendedHardwareAcceleration } from "../../utils/platform-utils.js";
2
2
  async function decodeChunksForScrub(chunks, config, targetTimeUs, options = {}) {
3
3
  const { timeoutMs = 2e3, maxQueueSize = 2, shouldAbort } = options;
4
4
  const startTime = performance.now();
@@ -191,4 +191,4 @@ export {
191
191
  decodeChunksForScrub,
192
192
  decodeChunksWithoutFlush
193
193
  };
194
- //# sourceMappingURL=video-decoder-helpers.js.map
194
+ //# sourceMappingURL=video-decoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-decoder.js","sources":["../../../src/stages/decode/video-decoder.ts"],"sourcesContent":["import { getRecommendedHardwareAcceleration } from '../../utils/platform-utils';\n\nexport interface DecodeResult {\n frames: VideoFrame[];\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface VideoDecoderConfig {\n codec: string;\n width: number;\n height: number;\n description?: ArrayBuffer;\n hardwareAcceleration?: HardwareAcceleration;\n}\n\nexport interface DecodeOptions {\n timeoutMs?: number;\n pollIntervalMs?: number;\n}\n\nexport interface DecodeScrubResult {\n before: VideoFrame | null;\n after: VideoFrame | null;\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface DecodeScrubOptions {\n timeoutMs?: number;\n /**\n * Backpressure: keep decodeQueueSize small so we don't decode far past target.\n */\n maxQueueSize?: number;\n /**\n * Optional cooperative abort hook (e.g., session cancelled by a newer seek).\n */\n shouldAbort?: () => boolean;\n}\n\n/**\n * Scrub decode: feed chunks in order, stop as soon as output crosses targetTimeUs.\n *\n * Why this lives in helpers:\n * - Centralize VideoDecoder quirks (no-await flush, HW accel defaults)\n * - Keep on-demand session focused on IO/demux/cache, not decode control flow\n */\nexport async function decodeChunksForScrub(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n targetTimeUs: number,\n options: DecodeScrubOptions = {}\n): Promise<DecodeScrubResult> {\n const { timeoutMs = 2000, maxQueueSize = 2, shouldAbort } = options;\n const startTime = performance.now();\n let outputFrames = 0;\n\n // Keep at most two frames: last <= target and first > target.\n let before: VideoFrame | null = null;\n let after: VideoFrame | null = null;\n\n return await new Promise<DecodeScrubResult>((resolve, reject) => {\n let settled = false;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n\n const settle = (ok: boolean, err?: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames,\n durationMs: performance.now() - startTime,\n };\n\n if (!ok) {\n if (before) before.close();\n if (after) after.close();\n reject(err instanceof Error ? err : new Error(String(err ?? 'Scrub decode failed')));\n return;\n }\n\n resolve({ before, after, stats });\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n outputFrames += 1;\n\n if (settled) {\n frame.close();\n return;\n }\n\n const ts = frame.timestamp ?? 0;\n if (ts <= targetTimeUs) {\n if (before) before.close();\n before = frame;\n return;\n }\n\n if (!after) {\n after = frame;\n // We have both sides (or at least the first after); stop as soon as possible.\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n\n frame.close();\n },\n error: (e) => {\n settle(false, e);\n },\n });\n\n try {\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration ?? getRecommendedHardwareAcceleration(),\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n } catch (e) {\n settle(false, e);\n return;\n }\n\n timeoutId = setTimeout(() => {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n // Best-effort: if we got at least one frame, still treat as success.\n if (before || after) {\n settle(true);\n } else {\n settle(false, new Error(`Scrub decode timeout after ${timeoutMs}ms`));\n }\n }, timeoutMs);\n\n const feed = async () => {\n try {\n for (const chunk of chunks) {\n if (settled) break;\n if (shouldAbort?.()) break;\n\n // Backpressure: keep queue small so we don't decode far past target.\n while (!settled && !shouldAbort?.() && decoder.decodeQueueSize > maxQueueSize) {\n await new Promise((r) => setTimeout(r, 0));\n }\n\n if (settled) break;\n if (shouldAbort?.()) break;\n decoder.decode(chunk);\n }\n\n // If we already settled (found target), we're done.\n if (settled) return;\n if (shouldAbort?.()) {\n // If cancelled, resolve best-effort if any output exists; otherwise fail.\n if (before || after) {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(false, new Error('Scrub decode aborted'));\n return;\n }\n\n // We may have hit end-of-input without seeing a frame after target (e.g., target at end).\n // Trigger flush (do not await) and give output a chance before timeout.\n decoder.flush().catch(() => {});\n } catch (e) {\n settle(false, e);\n }\n };\n\n void feed();\n });\n}\n\n/**\n * Decode chunks using native VideoDecoder without awaiting flush\n * This avoids Windows hardware acceleration flush hang bug\n *\n * Strategy:\n * - Feed all chunks to decoder\n * - Call flush() but don't await (may hang on Windows HW)\n * - Poll decodeQueueSize to detect completion\n * - Timeout fallback ensures no infinite hang\n */\nexport async function decodeChunksWithoutFlush(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n options: DecodeOptions = {}\n): Promise<DecodeResult> {\n const { timeoutMs = 2000, pollIntervalMs = 10 } = options;\n const startTime = performance.now();\n const frames: VideoFrame[] = [];\n\n return new Promise((resolve, reject) => {\n let isSettled = false;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (pollTimer !== null) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n if (timeoutTimer !== null) {\n clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const settle = (success: boolean, errorMsg?: string) => {\n if (isSettled) return;\n isSettled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames: frames.length,\n durationMs: performance.now() - startTime,\n };\n\n if (success && frames.length > 0) {\n resolve({ frames, stats });\n } else {\n reject(new Error(errorMsg || 'Decode failed'));\n }\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n frames.push(frame);\n },\n error: (error) => {\n settle(false, error.message);\n },\n });\n\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration ?? getRecommendedHardwareAcceleration(),\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n\n // Feed all chunks\n for (const chunk of chunks) {\n decoder.decode(chunk);\n }\n\n // Call flush but don't await (may hang on Windows HW acceleration)\n decoder.flush().catch(() => {\n // Flush errors are non-critical\n });\n\n // Poll decode queue to detect completion\n pollTimer = setInterval(() => {\n if (decoder.decodeQueueSize === 0 && frames.length > 0) {\n decoder.close();\n settle(true);\n }\n }, pollIntervalMs);\n\n // Timeout fallback\n timeoutTimer = setTimeout(() => {\n decoder.close();\n settle(frames.length > 0, `Decode timeout after ${timeoutMs}ms`);\n }, timeoutMs);\n });\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { StreamTarget, ArrayBufferTarget, Muxer } from "../../medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
1
+ import { StreamTarget, ArrayBufferTarget, Muxer } from "../../node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
2
2
  import { checkBrowserCompatibility } from "../../utils/platform-utils.js";
3
3
  class MP4Muxer {
4
4
  muxer;
@@ -1,4 +1,4 @@
1
- import * as mp4box_all from "../medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
1
+ import * as mp4box_all from "../node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
2
2
  const lib = mp4box_all;
3
3
  const MP4Box = lib.default && typeof lib.default.createFile === "function" ? lib.default : lib;
4
4
  if (typeof MP4Box.createFile !== "function") {
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/worker/WorkerPool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,MAAM,SAAS,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACxC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAeD,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,eAAe,CAA+C;gBAE1D,MAAM,EAAE,gBAAgB;IAOpC;;OAEG;YACW,YAAY;IAgD1B,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAK1D;;;;;;OAMG;IACG,WAAW,CACf,IAAI,EAAE,UAAU,EAChB,EAAE,CAAC,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,UAAU,CAAC;IAsChB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7D;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU9C;;OAEG;IACH,YAAY,IAAI,IAAI;IAOpB;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAQrD;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,EAAE,CAE5B;IAED;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;CAI5C"}
1
+ {"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/worker/WorkerPool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,MAAM,SAAS,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACxC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAaD,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,eAAe,CAA+C;gBAE1D,MAAM,EAAE,gBAAgB;IAOpC;;OAEG;YACW,YAAY;IA8C1B,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAK1D;;;;;;OAMG;IACG,WAAW,CACf,IAAI,EAAE,UAAU,EAChB,EAAE,CAAC,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,UAAU,CAAC;IAsChB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7D;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU9C;;OAEG;IACH,YAAY,IAAI,IAAI;IAOpB;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAQrD;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,EAAE,CAE5B;IAED;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;CAI5C"}
@@ -3,9 +3,7 @@ import { WorkerMessageType } from "./types.js";
3
3
  import { loadWorkerManifest } from "./worker-manifest.js";
4
4
  const WORKER_FILE_NAMES = {
5
5
  // videoDemux: 'video-demux', // DEPRECATED: Removed - use IndexedVideoSource
6
- audioDemux: "audio-demux",
7
- // videoDecode: 'video-decode', // DEPRECATED: Removed - use OnDemandVideoSession (main thread)
8
- audioDecode: "audio-decode",
6
+ // videoDecode: 'video-decode', // DEPRECATED: Removed - use VideoWindowDecodeSession (main thread)
9
7
  videoCompose: "video-compose",
10
8
  audioCompose: "audio-compose",
11
9
  videoEncode: "video-encode"
@@ -33,9 +31,7 @@ class WorkerPool {
33
31
  }
34
32
  const stageMap = {
35
33
  "video-demux": "demux",
36
- "audio-demux": "demux",
37
34
  "video-decode": "decode",
38
- "audio-decode": "decode",
39
35
  decode: "decode",
40
36
  "video-compose": "compose",
41
37
  "audio-compose": "compose",
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerPool.js","sources":["../../src/worker/WorkerPool.ts"],"sourcesContent":["/**\n * WorkerPool: Manages worker instances with key-based access\n * Provides lazy creation and unified management of workers\n */\n\nimport { BaseWorker } from './BaseWorker';\nimport type { WorkerType, WorkerStatus, WorkerConfigurePayload } from './types';\nimport { WorkerMessageType } from './types';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { loadWorkerManifest, type WorkerManifest } from './worker-manifest';\n\nexport interface WorkerPoolConfig {\n eventBus: EventBus<EventPayloadMap>;\n workerConfigs?: Record<WorkerType, any>;\n /** Worker files base path (default: '/meframe-workers') */\n workerPath?: string;\n workerExtension?: string;\n}\n\n/**\n * Worker name mapping (worker type -> file name)\n */\nconst WORKER_FILE_NAMES: Partial<Record<WorkerType, string>> = {\n // videoDemux: 'video-demux', // DEPRECATED: Removed - use IndexedVideoSource\n audioDemux: 'audio-demux',\n // videoDecode: 'video-decode', // DEPRECATED: Removed - use OnDemandVideoSession (main thread)\n audioDecode: 'audio-decode',\n videoCompose: 'video-compose',\n audioCompose: 'audio-compose',\n videoEncode: 'video-encode',\n};\n\nexport class WorkerPool {\n private pool = new Map<string, BaseWorker>();\n private eventBus: EventBus<EventPayloadMap>;\n private workerConfigs: Record<string, any>;\n private workerPath: string;\n private workerExtension: string;\n private manifestPromise: Promise<WorkerManifest | null> | null = null;\n\n constructor(config: WorkerPoolConfig) {\n this.eventBus = config.eventBus;\n this.workerConfigs = config.workerConfigs || {};\n this.workerPath = config.workerPath || '/meframe-workers';\n this.workerExtension = config.workerExtension || '.js';\n }\n\n /**\n * Get worker URL for a specific worker type\n */\n private async getWorkerUrl(type: WorkerType): Promise<string> {\n const fileName = WORKER_FILE_NAMES[type];\n\n if (!fileName) {\n throw new Error(`[WorkerPool] Worker type '${type}' is deprecated or not supported`);\n }\n\n // Map worker type to its stage directory\n const stageMap: Record<string, string> = {\n 'video-demux': 'demux',\n 'audio-demux': 'demux',\n 'video-decode': 'decode',\n 'audio-decode': 'decode',\n decode: 'decode',\n 'video-compose': 'compose',\n 'audio-compose': 'compose',\n 'video-encode': 'encode',\n encode: 'encode',\n };\n const stage = stageMap[fileName];\n\n // Dev mode: use .ts source files directly (no manifest needed)\n if (this.workerExtension === '.ts') {\n return `${this.workerPath}/stages/${stage}/${fileName}.worker.ts`;\n }\n\n // Production mode: try to load manifest for hashed filenames\n if (!this.manifestPromise) {\n this.manifestPromise = loadWorkerManifest(this.workerPath);\n }\n\n const manifest = await this.manifestPromise;\n\n if (manifest) {\n // Use hashed filename from manifest\n const manifestKey = `${fileName}.worker`;\n const hashedPath = manifest[manifestKey];\n\n if (hashedPath) {\n // Manifest contains full relative path from workers directory\n return `${this.workerPath}/${hashedPath}`;\n }\n }\n\n // Fallback: use non-hashed filename (backward compatibility)\n return `${this.workerPath}/stages/${stage}/${fileName}.worker.js`;\n }\n\n get(type: WorkerType, id?: string): BaseWorker | undefined {\n const key = id ? `${type}#${id}` : type;\n return this.pool.get(key) ?? undefined;\n }\n\n /**\n * Get or create a worker instance\n * @param type - Worker type\n * @param id - Optional ID for per-resource or per-clip workers\n * @param options - Optional configuration\n * - lazy: If true, skip initial configure (default: false)\n */\n async getOrCreate(\n type: WorkerType,\n id?: string,\n options?: { lazy?: boolean }\n ): Promise<BaseWorker> {\n const key = id ? `${type}#${id}` : type;\n\n const existing = this.pool.get(key);\n if (!existing) {\n // Generate worker URL based on worker path and type\n const url = await this.getWorkerUrl(type);\n\n const worker = new BaseWorker({\n type,\n url,\n eventBus: this.eventBus,\n clipId: id,\n });\n\n // Only initialize if not in lazy mode\n if (!options?.lazy) {\n const config = this.workerConfigs[type];\n await worker.initialize(config);\n }\n\n this.pool.set(key, worker);\n return worker;\n }\n\n if (!options?.lazy) {\n const config = this.workerConfigs[type];\n if (config) {\n await existing.send(WorkerMessageType.Configure, {\n config,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n\n return existing;\n }\n\n async setConfig(type: WorkerType, config: any): Promise<void> {\n const existing = this.workerConfigs[type] || {};\n const mergedConfig = { ...existing, ...config };\n\n this.workerConfigs[type] = mergedConfig;\n\n for (const [key, worker] of this.pool.entries()) {\n if (key === type || key.startsWith(`${type}#`)) {\n await worker.send(WorkerMessageType.Configure, {\n config: mergedConfig,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n }\n\n /**\n * Terminate a specific worker\n */\n terminate(type: WorkerType, id?: string): void {\n const key = id ? `${type}#${id}` : type;\n const worker = this.pool.get(key);\n\n if (worker) {\n worker.terminate();\n this.pool.delete(key);\n }\n }\n\n /**\n * Terminate all workers\n */\n terminateAll(): void {\n for (const worker of this.pool.values()) {\n worker.terminate();\n }\n this.pool.clear();\n }\n\n /**\n * Get status of all workers\n */\n get status(): Record<string, WorkerStatus[WorkerType]> {\n const result: Record<string, WorkerStatus[WorkerType]> = {};\n\n for (const [key, worker] of this.pool.entries()) {\n result[key] = worker.status;\n }\n\n return result;\n }\n\n /**\n * Get list of active worker keys\n */\n get activeWorkers(): string[] {\n return Array.from(this.pool.keys());\n }\n\n /**\n * Check if a worker exists\n */\n has(type: WorkerType, id?: string): boolean {\n const key = id ? `${type}#${id}` : type;\n return this.pool.has(key);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkerPool.js","sources":["../../src/worker/WorkerPool.ts"],"sourcesContent":["/**\n * WorkerPool: Manages worker instances with key-based access\n * Provides lazy creation and unified management of workers\n */\n\nimport { BaseWorker } from './BaseWorker';\nimport type { WorkerType, WorkerStatus, WorkerConfigurePayload } from './types';\nimport { WorkerMessageType } from './types';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { loadWorkerManifest, type WorkerManifest } from './worker-manifest';\n\nexport interface WorkerPoolConfig {\n eventBus: EventBus<EventPayloadMap>;\n workerConfigs?: Record<WorkerType, any>;\n /** Worker files base path (default: '/meframe-workers') */\n workerPath?: string;\n workerExtension?: string;\n}\n\n/**\n * Worker name mapping (worker type -> file name)\n */\nconst WORKER_FILE_NAMES: Partial<Record<WorkerType, string>> = {\n // videoDemux: 'video-demux', // DEPRECATED: Removed - use IndexedVideoSource\n // videoDecode: 'video-decode', // DEPRECATED: Removed - use VideoWindowDecodeSession (main thread)\n videoCompose: 'video-compose',\n audioCompose: 'audio-compose',\n videoEncode: 'video-encode',\n};\n\nexport class WorkerPool {\n private pool = new Map<string, BaseWorker>();\n private eventBus: EventBus<EventPayloadMap>;\n private workerConfigs: Record<string, any>;\n private workerPath: string;\n private workerExtension: string;\n private manifestPromise: Promise<WorkerManifest | null> | null = null;\n\n constructor(config: WorkerPoolConfig) {\n this.eventBus = config.eventBus;\n this.workerConfigs = config.workerConfigs || {};\n this.workerPath = config.workerPath || '/meframe-workers';\n this.workerExtension = config.workerExtension || '.js';\n }\n\n /**\n * Get worker URL for a specific worker type\n */\n private async getWorkerUrl(type: WorkerType): Promise<string> {\n const fileName = WORKER_FILE_NAMES[type];\n\n if (!fileName) {\n throw new Error(`[WorkerPool] Worker type '${type}' is deprecated or not supported`);\n }\n\n // Map worker type to its stage directory\n const stageMap: Record<string, string> = {\n 'video-demux': 'demux',\n 'video-decode': 'decode',\n decode: 'decode',\n 'video-compose': 'compose',\n 'audio-compose': 'compose',\n 'video-encode': 'encode',\n encode: 'encode',\n };\n const stage = stageMap[fileName];\n\n // Dev mode: use .ts source files directly (no manifest needed)\n if (this.workerExtension === '.ts') {\n return `${this.workerPath}/stages/${stage}/${fileName}.worker.ts`;\n }\n\n // Production mode: try to load manifest for hashed filenames\n if (!this.manifestPromise) {\n this.manifestPromise = loadWorkerManifest(this.workerPath);\n }\n\n const manifest = await this.manifestPromise;\n\n if (manifest) {\n // Use hashed filename from manifest\n const manifestKey = `${fileName}.worker`;\n const hashedPath = manifest[manifestKey];\n\n if (hashedPath) {\n // Manifest contains full relative path from workers directory\n return `${this.workerPath}/${hashedPath}`;\n }\n }\n\n // Fallback: use non-hashed filename (backward compatibility)\n return `${this.workerPath}/stages/${stage}/${fileName}.worker.js`;\n }\n\n get(type: WorkerType, id?: string): BaseWorker | undefined {\n const key = id ? `${type}#${id}` : type;\n return this.pool.get(key) ?? undefined;\n }\n\n /**\n * Get or create a worker instance\n * @param type - Worker type\n * @param id - Optional ID for per-resource or per-clip workers\n * @param options - Optional configuration\n * - lazy: If true, skip initial configure (default: false)\n */\n async getOrCreate(\n type: WorkerType,\n id?: string,\n options?: { lazy?: boolean }\n ): Promise<BaseWorker> {\n const key = id ? `${type}#${id}` : type;\n\n const existing = this.pool.get(key);\n if (!existing) {\n // Generate worker URL based on worker path and type\n const url = await this.getWorkerUrl(type);\n\n const worker = new BaseWorker({\n type,\n url,\n eventBus: this.eventBus,\n clipId: id,\n });\n\n // Only initialize if not in lazy mode\n if (!options?.lazy) {\n const config = this.workerConfigs[type];\n await worker.initialize(config);\n }\n\n this.pool.set(key, worker);\n return worker;\n }\n\n if (!options?.lazy) {\n const config = this.workerConfigs[type];\n if (config) {\n await existing.send(WorkerMessageType.Configure, {\n config,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n\n return existing;\n }\n\n async setConfig(type: WorkerType, config: any): Promise<void> {\n const existing = this.workerConfigs[type] || {};\n const mergedConfig = { ...existing, ...config };\n\n this.workerConfigs[type] = mergedConfig;\n\n for (const [key, worker] of this.pool.entries()) {\n if (key === type || key.startsWith(`${type}#`)) {\n await worker.send(WorkerMessageType.Configure, {\n config: mergedConfig,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n }\n\n /**\n * Terminate a specific worker\n */\n terminate(type: WorkerType, id?: string): void {\n const key = id ? `${type}#${id}` : type;\n const worker = this.pool.get(key);\n\n if (worker) {\n worker.terminate();\n this.pool.delete(key);\n }\n }\n\n /**\n * Terminate all workers\n */\n terminateAll(): void {\n for (const worker of this.pool.values()) {\n worker.terminate();\n }\n this.pool.clear();\n }\n\n /**\n * Get status of all workers\n */\n get status(): Record<string, WorkerStatus[WorkerType]> {\n const result: Record<string, WorkerStatus[WorkerType]> = {};\n\n for (const [key, worker] of this.pool.entries()) {\n result[key] = worker.status;\n }\n\n return result;\n }\n\n /**\n * Get list of active worker keys\n */\n get activeWorkers(): string[] {\n return Array.from(this.pool.keys());\n }\n\n /**\n * Check if a worker exists\n */\n has(type: WorkerType, id?: string): boolean {\n const key = id ? `${type}#${id}` : type;\n return this.pool.has(key);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ export type PortLike = MessagePort | DedicatedWorkerGlobalScope | Worker;
6
6
  /**
7
7
  * Worker types in the pipeline
8
8
  */
9
- export type WorkerType = 'audioDemux' | 'audioDecode' | 'videoCompose' | 'audioCompose' | 'videoEncode';
9
+ export type WorkerType = 'videoCompose' | 'audioCompose' | 'videoEncode';
10
10
  export interface WorkerConfigurePayload<T = any> {
11
11
  config: T;
12
12
  initial: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/worker/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/worker/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../src/worker/types.ts"],"sourcesContent":["/**\n * Worker Communication Protocol Types\n * Define standard message types and protocols for worker communication\n */\n\n// A port-like object that supports postMessage/onmessage used by WorkerChannel\nexport type PortLike = MessagePort | DedicatedWorkerGlobalScope | Worker;\n\n/**\n * Worker types in the pipeline\n */\nexport type WorkerType =\n // | 'videoDemux' // DEPRECATED: Replaced by IndexedVideoSource (main-thread GOP-based streaming)\n | 'audioDemux'\n // | 'videoDecode' // DEPRECATED: Replaced by OnDemandVideoSession (main-thread decoding)\n | 'audioDecode' // Clip-local audio decoder\n | 'videoCompose'\n | 'audioCompose'\n | 'videoEncode'; // Clip-local video encoder (L2 only)\n\nexport interface WorkerConfigurePayload<T = any> {\n config: T;\n initial: boolean;\n}\n\n/**\n * Worker state definition\n */\nexport interface WorkerStateInfo {\n state: 'idle' | 'busy' | 'error' | 'terminated';\n taskCount: number;\n lastError?: string;\n}\n\n/**\n * Worker status for all workers\n */\nexport type WorkerStatus = Record<WorkerType, WorkerStateInfo>;\n\n/**\n * Worker wrapper interface\n */\nexport interface IWorker {\n readonly type: WorkerType;\n readonly state: WorkerStateInfo;\n\n initialize(): Promise<void>;\n terminate(): Promise<void>;\n restart(): Promise<void>;\n\n postMessage(message: any, transfer?: Transferable[]): void;\n on(event: string, handler: (data: any) => void): void;\n off(event: string, handler: (data: any) => void): void;\n}\n\n// Message types for different workers\nexport enum WorkerMessageType {\n // Init = 'init', // configure, { initial: true }\n Ready = 'ready',\n Error = 'error',\n Dispose = 'dispose',\n Configure = 'configure',\n LoadResource = 'load_resource',\n ResourceLoaded = 'resource_loaded',\n ResourceProgress = 'resource_progress',\n ConfigureDemux = 'configure_demux',\n AppendBuffer = 'append_buffer',\n DemuxSamples = 'demux_samples',\n FlushDemux = 'flush_demux',\n ConfigureDecode = 'configure_decode',\n DecodeChunk = 'decode_chunk',\n DecodedFrame = 'decoded_frame',\n SeekGop = 'seek_gop',\n SetComposition = 'set_composition',\n ApplyPatch = 'apply_patch',\n RenderFrame = 'render_frame',\n ComposeFrameReady = 'compose_frame_ready',\n ConfigureEncode = 'configure_encode',\n EncodeFrame = 'encode_frame',\n EncodeAudio = 'encode_audio',\n EncodedChunk = 'encoded_chunk',\n FlushEncode = 'flush_encode',\n AddChunk = 'add_chunk',\n PerformanceStats = 'performance_stats',\n RenderWindow = 'renderWindow',\n AudioTrackAdd = 'audio_track:add',\n AudioTrackRemove = 'audio_track:remove',\n AudioTrackUpdate = 'audio_track:update',\n}\n\n// Base message structure\nexport interface WorkerMessage<T = any> {\n type: WorkerMessageType;\n id: string; // Unique message ID for request-response pairing\n payload?: T;\n transfer?: Transferable[]; // Objects to transfer ownership\n timestamp: number;\n}\n\n// Response message structure\nexport interface WorkerResponse<T = any> {\n id: string; // Matches request ID\n success: boolean;\n result?: T;\n error?: WorkerError;\n timestamp: number;\n}\n\n// Error structure\nexport interface WorkerError {\n code: string;\n message: string;\n details?: any;\n stack?: string;\n}\n\n// Worker configuration\nexport interface WorkerConfig {\n name: string;\n workerUrl?: string;\n capabilities?: string[];\n maxRetries?: number;\n timeout?: number;\n}\n\n// Message handler type\nexport type MessageHandler<T = any, R = any> = (\n payload: T,\n transfer?: Transferable[]\n) => Promise<R> | R;\n\n// Message handler registry\nexport interface MessageHandlers {\n [key: string]: MessageHandler;\n}\n\n/**\n * Unified stream connection message for the data plane\n * Used by the stream-pipeline refactor to reduce message surface\n */\nexport interface ConnectMsg {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n clipId?: string;\n trackId?: string;\n}\n\n// Transferable types used in workers\nexport interface TransferableFrame {\n frame: VideoFrame;\n timestamp: number;\n duration?: number;\n}\n\nexport interface TransferableChunk {\n chunk: EncodedVideoChunk | EncodedAudioChunk;\n timestamp: number;\n duration?: number;\n trackId: string;\n}\n\nexport interface TransferableBuffer {\n buffer: ArrayBuffer;\n offset?: number;\n length?: number;\n}\n\n// Worker state\nexport enum WorkerState {\n Idle = 'idle',\n Initializing = 'initializing',\n Ready = 'ready',\n Processing = 'processing',\n Error = 'error',\n Disposed = 'disposed',\n}\n\n// Performance metrics\nexport interface WorkerPerformanceStats {\n workerId: string;\n workerName: string;\n messagesProcessed: number;\n averageProcessingTime: number;\n maxProcessingTime: number;\n errorCount: number;\n queueSize: number;\n memoryUsage?: number;\n}\n\n// Request tracking\nexport interface PendingRequest {\n id: string;\n type: WorkerMessageType;\n timestamp: number;\n timeout?: number;\n resolve: (value: any) => void;\n reject: (error: any) => void;\n retryCount?: number;\n maxRetries?: number;\n}\n\n// Stream support\nexport interface StreamConfig {\n highWaterMark?: number;\n strategy?: QueuingStrategy;\n}\n\n// Worker pool settings\nexport interface WorkerPoolSettings {\n maxWorkers?: number;\n workerIdleTimeout?: number;\n enableSharedArrayBuffer?: boolean;\n enableOffscreenCanvas?: boolean;\n}\n\n// Message priority levels\nexport enum MessagePriority {\n Low = 0,\n Normal = 1,\n High = 2,\n Critical = 3,\n}\n\n// Priority message\nexport interface PriorityMessage<T = any> extends WorkerMessage<T> {\n priority: MessagePriority;\n}\n"],"names":["WorkerMessageType","WorkerState"],"mappings":"AAwDO,IAAK,sCAAAA,uBAAL;AAELA,qBAAA,OAAA,IAAQ;AACRA,qBAAA,OAAA,IAAQ;AACRA,qBAAA,SAAA,IAAU;AACVA,qBAAA,WAAA,IAAY;AACZA,qBAAA,cAAA,IAAe;AACfA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,cAAA,IAAe;AACfA,qBAAA,cAAA,IAAe;AACfA,qBAAA,YAAA,IAAa;AACbA,qBAAA,iBAAA,IAAkB;AAClBA,qBAAA,aAAA,IAAc;AACdA,qBAAA,cAAA,IAAe;AACfA,qBAAA,SAAA,IAAU;AACVA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,YAAA,IAAa;AACbA,qBAAA,aAAA,IAAc;AACdA,qBAAA,mBAAA,IAAoB;AACpBA,qBAAA,iBAAA,IAAkB;AAClBA,qBAAA,aAAA,IAAc;AACdA,qBAAA,aAAA,IAAc;AACdA,qBAAA,cAAA,IAAe;AACfA,qBAAA,aAAA,IAAc;AACdA,qBAAA,UAAA,IAAW;AACXA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,cAAA,IAAe;AACfA,qBAAA,eAAA,IAAgB;AAChBA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,kBAAA,IAAmB;AA/BT,SAAAA;AAAA,GAAA,qBAAA,CAAA,CAAA;AAiHL,IAAK,gCAAAC,iBAAL;AACLA,eAAA,MAAA,IAAO;AACPA,eAAA,cAAA,IAAe;AACfA,eAAA,OAAA,IAAQ;AACRA,eAAA,YAAA,IAAa;AACbA,eAAA,OAAA,IAAQ;AACRA,eAAA,UAAA,IAAW;AAND,SAAAA;AAAA,GAAA,eAAA,CAAA,CAAA;"}
1
+ {"version":3,"file":"types.js","sources":["../../src/worker/types.ts"],"sourcesContent":["/**\n * Worker Communication Protocol Types\n * Define standard message types and protocols for worker communication\n */\n\n// A port-like object that supports postMessage/onmessage used by WorkerChannel\nexport type PortLike = MessagePort | DedicatedWorkerGlobalScope | Worker;\n\n/**\n * Worker types in the pipeline\n */\nexport type WorkerType =\n // | 'videoDemux' // DEPRECATED: Replaced by IndexedVideoSource (main-thread GOP-based streaming)\n // | 'audioDemux' // DEPRECATED: Replaced by main-thread parsing + AudioSampleCache\n // | 'videoDecode' // DEPRECATED: Replaced by VideoWindowDecodeSession (main-thread decoding)\n // | 'audioDecode' // DEPRECATED: Replaced by AudioWindowPreparer (main-thread decoding)\n 'videoCompose' | 'audioCompose' | 'videoEncode'; // Clip-local video encoder (L2 only)\n\nexport interface WorkerConfigurePayload<T = any> {\n config: T;\n initial: boolean;\n}\n\n/**\n * Worker state definition\n */\nexport interface WorkerStateInfo {\n state: 'idle' | 'busy' | 'error' | 'terminated';\n taskCount: number;\n lastError?: string;\n}\n\n/**\n * Worker status for all workers\n */\nexport type WorkerStatus = Record<WorkerType, WorkerStateInfo>;\n\n/**\n * Worker wrapper interface\n */\nexport interface IWorker {\n readonly type: WorkerType;\n readonly state: WorkerStateInfo;\n\n initialize(): Promise<void>;\n terminate(): Promise<void>;\n restart(): Promise<void>;\n\n postMessage(message: any, transfer?: Transferable[]): void;\n on(event: string, handler: (data: any) => void): void;\n off(event: string, handler: (data: any) => void): void;\n}\n\n// Message types for different workers\nexport enum WorkerMessageType {\n // Init = 'init', // configure, { initial: true }\n Ready = 'ready',\n Error = 'error',\n Dispose = 'dispose',\n Configure = 'configure',\n LoadResource = 'load_resource',\n ResourceLoaded = 'resource_loaded',\n ResourceProgress = 'resource_progress',\n ConfigureDemux = 'configure_demux',\n AppendBuffer = 'append_buffer',\n DemuxSamples = 'demux_samples',\n FlushDemux = 'flush_demux',\n ConfigureDecode = 'configure_decode',\n DecodeChunk = 'decode_chunk',\n DecodedFrame = 'decoded_frame',\n SeekGop = 'seek_gop',\n SetComposition = 'set_composition',\n ApplyPatch = 'apply_patch',\n RenderFrame = 'render_frame',\n ComposeFrameReady = 'compose_frame_ready',\n ConfigureEncode = 'configure_encode',\n EncodeFrame = 'encode_frame',\n EncodeAudio = 'encode_audio',\n EncodedChunk = 'encoded_chunk',\n FlushEncode = 'flush_encode',\n AddChunk = 'add_chunk',\n PerformanceStats = 'performance_stats',\n RenderWindow = 'renderWindow',\n AudioTrackAdd = 'audio_track:add',\n AudioTrackRemove = 'audio_track:remove',\n AudioTrackUpdate = 'audio_track:update',\n}\n\n// Base message structure\nexport interface WorkerMessage<T = any> {\n type: WorkerMessageType;\n id: string; // Unique message ID for request-response pairing\n payload?: T;\n transfer?: Transferable[]; // Objects to transfer ownership\n timestamp: number;\n}\n\n// Response message structure\nexport interface WorkerResponse<T = any> {\n id: string; // Matches request ID\n success: boolean;\n result?: T;\n error?: WorkerError;\n timestamp: number;\n}\n\n// Error structure\nexport interface WorkerError {\n code: string;\n message: string;\n details?: any;\n stack?: string;\n}\n\n// Worker configuration\nexport interface WorkerConfig {\n name: string;\n workerUrl?: string;\n capabilities?: string[];\n maxRetries?: number;\n timeout?: number;\n}\n\n// Message handler type\nexport type MessageHandler<T = any, R = any> = (\n payload: T,\n transfer?: Transferable[]\n) => Promise<R> | R;\n\n// Message handler registry\nexport interface MessageHandlers {\n [key: string]: MessageHandler;\n}\n\n/**\n * Unified stream connection message for the data plane\n * Used by the stream-pipeline refactor to reduce message surface\n */\nexport interface ConnectMsg {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n clipId?: string;\n trackId?: string;\n}\n\n// Transferable types used in workers\nexport interface TransferableFrame {\n frame: VideoFrame;\n timestamp: number;\n duration?: number;\n}\n\nexport interface TransferableChunk {\n chunk: EncodedVideoChunk | EncodedAudioChunk;\n timestamp: number;\n duration?: number;\n trackId: string;\n}\n\nexport interface TransferableBuffer {\n buffer: ArrayBuffer;\n offset?: number;\n length?: number;\n}\n\n// Worker state\nexport enum WorkerState {\n Idle = 'idle',\n Initializing = 'initializing',\n Ready = 'ready',\n Processing = 'processing',\n Error = 'error',\n Disposed = 'disposed',\n}\n\n// Performance metrics\nexport interface WorkerPerformanceStats {\n workerId: string;\n workerName: string;\n messagesProcessed: number;\n averageProcessingTime: number;\n maxProcessingTime: number;\n errorCount: number;\n queueSize: number;\n memoryUsage?: number;\n}\n\n// Request tracking\nexport interface PendingRequest {\n id: string;\n type: WorkerMessageType;\n timestamp: number;\n timeout?: number;\n resolve: (value: any) => void;\n reject: (error: any) => void;\n retryCount?: number;\n maxRetries?: number;\n}\n\n// Stream support\nexport interface StreamConfig {\n highWaterMark?: number;\n strategy?: QueuingStrategy;\n}\n\n// Worker pool settings\nexport interface WorkerPoolSettings {\n maxWorkers?: number;\n workerIdleTimeout?: number;\n enableSharedArrayBuffer?: boolean;\n enableOffscreenCanvas?: boolean;\n}\n\n// Message priority levels\nexport enum MessagePriority {\n Low = 0,\n Normal = 1,\n High = 2,\n Critical = 3,\n}\n\n// Priority message\nexport interface PriorityMessage<T = any> extends WorkerMessage<T> {\n priority: MessagePriority;\n}\n"],"names":["WorkerMessageType","WorkerState"],"mappings":"AAsDO,IAAK,sCAAAA,uBAAL;AAELA,qBAAA,OAAA,IAAQ;AACRA,qBAAA,OAAA,IAAQ;AACRA,qBAAA,SAAA,IAAU;AACVA,qBAAA,WAAA,IAAY;AACZA,qBAAA,cAAA,IAAe;AACfA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,cAAA,IAAe;AACfA,qBAAA,cAAA,IAAe;AACfA,qBAAA,YAAA,IAAa;AACbA,qBAAA,iBAAA,IAAkB;AAClBA,qBAAA,aAAA,IAAc;AACdA,qBAAA,cAAA,IAAe;AACfA,qBAAA,SAAA,IAAU;AACVA,qBAAA,gBAAA,IAAiB;AACjBA,qBAAA,YAAA,IAAa;AACbA,qBAAA,aAAA,IAAc;AACdA,qBAAA,mBAAA,IAAoB;AACpBA,qBAAA,iBAAA,IAAkB;AAClBA,qBAAA,aAAA,IAAc;AACdA,qBAAA,aAAA,IAAc;AACdA,qBAAA,cAAA,IAAe;AACfA,qBAAA,aAAA,IAAc;AACdA,qBAAA,UAAA,IAAW;AACXA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,cAAA,IAAe;AACfA,qBAAA,eAAA,IAAgB;AAChBA,qBAAA,kBAAA,IAAmB;AACnBA,qBAAA,kBAAA,IAAmB;AA/BT,SAAAA;AAAA,GAAA,qBAAA,CAAA,CAAA;AAiHL,IAAK,gCAAAC,iBAAL;AACLA,eAAA,MAAA,IAAO;AACPA,eAAA,cAAA,IAAe;AACfA,eAAA,OAAA,IAAQ;AACRA,eAAA,YAAA,IAAa;AACbA,eAAA,OAAA,IAAQ;AACRA,eAAA,UAAA,IAAW;AAND,SAAAA;AAAA,GAAA,eAAA,CAAA,CAAA;"}
@@ -1 +1 @@
1
- {"version":3,"file":"worker-event-whitelist.d.ts","sourceRoot":"","sources":["../../src/worker/worker-event-whitelist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,UAAU,EAAE,YAAY,EAAE,CA0DrE,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAGvF;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAKlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAGhE,CAAC"}
1
+ {"version":3,"file":"worker-event-whitelist.d.ts","sourceRoot":"","sources":["../../src/worker/worker-event-whitelist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,UAAU,EAAE,YAAY,EAAE,CA0CrE,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAGvF;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAKlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAGhE,CAAC"}
@@ -15,8 +15,8 @@ export interface WorkerManifest {
15
15
  *
16
16
  * The manifest maps worker names to their hashed filenames:
17
17
  * {
18
- * "video-decode.worker": "stages/decode/video-decode.worker.a1b2c3d4.js",
19
- * "video-demux.worker": "stages/demux/video-demux.worker.e5f6g7h8.js"
18
+ * "video-compose.worker": "stages/compose/video-compose.worker.a1b2c3d4.js",
19
+ * "video-encode.worker": "stages/encode/video-encode.worker.e5f6g7h8.js"
20
20
  * }
21
21
  */
22
22
  export declare function loadWorkerManifest(workerPath: string): Promise<WorkerManifest | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"worker-manifest.js","sources":["../../src/worker/worker-manifest.ts"],"sourcesContent":["/**\n * Worker manifest for content-hashed worker filenames\n *\n * The manifest maps original worker names to hashed filenames,\n * enabling cache busting for worker files.\n */\n\nexport interface WorkerManifest {\n [workerName: string]: string;\n}\n\n/**\n * Cached manifest to avoid repeated fetches\n */\nlet cachedManifest: WorkerManifest | null | undefined = undefined;\n\n/**\n * Load worker manifest from the given worker path\n *\n * @param workerPath - Base path for worker files (e.g., '/meframe-workers')\n * @returns Promise<WorkerManifest | null> - Manifest object or null if not found\n *\n * The manifest maps worker names to their hashed filenames:\n * {\n * \"video-decode.worker\": \"stages/decode/video-decode.worker.a1b2c3d4.js\",\n * \"video-demux.worker\": \"stages/demux/video-demux.worker.e5f6g7h8.js\"\n * }\n */\nexport async function loadWorkerManifest(workerPath: string): Promise<WorkerManifest | null> {\n // Return cached result if available\n if (cachedManifest !== undefined) {\n return cachedManifest;\n }\n\n try {\n const manifestUrl = `${workerPath}/worker-manifest.json`;\n\n const response = await fetch(manifestUrl);\n\n if (!response.ok) {\n console.warn(\n `[WorkerManifest] Manifest not found at ${manifestUrl}, falling back to non-hashed filenames`\n );\n cachedManifest = null;\n return null;\n }\n\n const manifest: WorkerManifest = await response.json();\n\n // Validate manifest structure\n if (typeof manifest !== 'object' || manifest === null) {\n console.error('[WorkerManifest] Invalid manifest format');\n cachedManifest = null;\n return null;\n }\n\n cachedManifest = manifest;\n console.log(`[WorkerManifest] Loaded manifest with ${Object.keys(manifest).length} worker(s)`);\n\n return manifest;\n } catch (error) {\n // Network error or invalid JSON - fall back to non-hashed filenames\n console.warn(\n '[WorkerManifest] Failed to load manifest, using fallback:',\n error instanceof Error ? error.message : String(error)\n );\n cachedManifest = null;\n return null;\n }\n}\n\n/**\n * Reset the cached manifest (useful for testing)\n */\nexport function resetManifestCache(): void {\n cachedManifest = undefined;\n}\n"],"names":[],"mappings":"AAcA,IAAI,iBAAoD;AAcxD,eAAsB,mBAAmB,YAAoD;AAE3F,MAAI,mBAAmB,QAAW;AAChC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,cAAc,GAAG,UAAU;AAEjC,UAAM,WAAW,MAAM,MAAM,WAAW;AAExC,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ;AAAA,QACN,0CAA0C,WAAW;AAAA,MAAA;AAEvD,uBAAiB;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,WAA2B,MAAM,SAAS,KAAA;AAGhD,QAAI,OAAO,aAAa,YAAY,aAAa,MAAM;AACrD,cAAQ,MAAM,0CAA0C;AACxD,uBAAiB;AACjB,aAAO;AAAA,IACT;AAEA,qBAAiB;AACjB,YAAQ,IAAI,yCAAyC,OAAO,KAAK,QAAQ,EAAE,MAAM,YAAY;AAE7F,WAAO;AAAA,EACT,SAAS,OAAO;AAEd,YAAQ;AAAA,MACN;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA;AAEvD,qBAAiB;AACjB,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"worker-manifest.js","sources":["../../src/worker/worker-manifest.ts"],"sourcesContent":["/**\n * Worker manifest for content-hashed worker filenames\n *\n * The manifest maps original worker names to hashed filenames,\n * enabling cache busting for worker files.\n */\n\nexport interface WorkerManifest {\n [workerName: string]: string;\n}\n\n/**\n * Cached manifest to avoid repeated fetches\n */\nlet cachedManifest: WorkerManifest | null | undefined = undefined;\n\n/**\n * Load worker manifest from the given worker path\n *\n * @param workerPath - Base path for worker files (e.g., '/meframe-workers')\n * @returns Promise<WorkerManifest | null> - Manifest object or null if not found\n *\n * The manifest maps worker names to their hashed filenames:\n * {\n * \"video-compose.worker\": \"stages/compose/video-compose.worker.a1b2c3d4.js\",\n * \"video-encode.worker\": \"stages/encode/video-encode.worker.e5f6g7h8.js\"\n * }\n */\nexport async function loadWorkerManifest(workerPath: string): Promise<WorkerManifest | null> {\n // Return cached result if available\n if (cachedManifest !== undefined) {\n return cachedManifest;\n }\n\n try {\n const manifestUrl = `${workerPath}/worker-manifest.json`;\n\n const response = await fetch(manifestUrl);\n\n if (!response.ok) {\n console.warn(\n `[WorkerManifest] Manifest not found at ${manifestUrl}, falling back to non-hashed filenames`\n );\n cachedManifest = null;\n return null;\n }\n\n const manifest: WorkerManifest = await response.json();\n\n // Validate manifest structure\n if (typeof manifest !== 'object' || manifest === null) {\n console.error('[WorkerManifest] Invalid manifest format');\n cachedManifest = null;\n return null;\n }\n\n cachedManifest = manifest;\n console.log(`[WorkerManifest] Loaded manifest with ${Object.keys(manifest).length} worker(s)`);\n\n return manifest;\n } catch (error) {\n // Network error or invalid JSON - fall back to non-hashed filenames\n console.warn(\n '[WorkerManifest] Failed to load manifest, using fallback:',\n error instanceof Error ? error.message : String(error)\n );\n cachedManifest = null;\n return null;\n }\n}\n\n/**\n * Reset the cached manifest (useful for testing)\n */\nexport function resetManifestCache(): void {\n cachedManifest = undefined;\n}\n"],"names":[],"mappings":"AAcA,IAAI,iBAAoD;AAcxD,eAAsB,mBAAmB,YAAoD;AAE3F,MAAI,mBAAmB,QAAW;AAChC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,cAAc,GAAG,UAAU;AAEjC,UAAM,WAAW,MAAM,MAAM,WAAW;AAExC,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ;AAAA,QACN,0CAA0C,WAAW;AAAA,MAAA;AAEvD,uBAAiB;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,WAA2B,MAAM,SAAS,KAAA;AAGhD,QAAI,OAAO,aAAa,YAAY,aAAa,MAAM;AACrD,cAAQ,MAAM,0CAA0C;AACxD,uBAAiB;AACjB,aAAO;AAAA,IACT;AAEA,qBAAiB;AACjB,YAAQ,IAAI,yCAAyC,OAAO,KAAK,QAAQ,EAAE,MAAM,YAAY;AAE7F,WAAO;AAAA,EACT,SAAS,OAAO;AAEd,YAAQ;AAAA,MACN;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA;AAEvD,qBAAiB;AACjB,WAAO;AAAA,EACT;AACF;"}