@meframe/core 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/CompositionModel.d.ts +2 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +80 -11
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/types.d.ts +30 -1
- package/dist/model/types.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +18 -2
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +12 -2
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +2 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +16 -1
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts +2 -0
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/instructions.d.ts +24 -1
- package/dist/stages/compose/instructions.d.ts.map +1 -1
- package/dist/stages/compose/types.d.ts +2 -0
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +8 -0
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +74 -12
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/types.d.ts +1 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/utils/animation-utils.d.ts +16 -0
- package/dist/utils/animation-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.d.ts +5 -0
- package/dist/utils/image-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.js +32 -0
- package/dist/utils/image-utils.js.map +1 -0
- package/dist/workers/stages/compose/video-compose.worker.js +256 -29
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import type { Resource, CompositionModel } from '../../model';\nimport type { Orchestrator, ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventHandlers } from './EventHandlers';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private orchestrator?: Orchestrator;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventHandlers?: EventHandlers;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n\n constructor(options?: ResourceLoaderOptions) {\n const maxConcurrent = options?.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options?.onProgress, options?.config);\n this.eventBus = options?.eventBus;\n this.onStateChange = options?.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n\n if (options?.orchestrator) {\n this.bind(options.orchestrator);\n }\n }\n\n /**\n * Bind to Orchestrator event system\n */\n bind(orchestrator: Orchestrator): void {\n this.unbind();\n this.orchestrator = orchestrator;\n\n this.eventHandlers = new EventHandlers(\n orchestrator,\n (resourceId) => this.cancel(resourceId),\n (model) => this.handleModelSet(model)\n );\n }\n\n /**\n * Unbind from Orchestrator\n */\n unbind(): void {\n this.eventHandlers?.dispose();\n this.eventHandlers = undefined;\n this.orchestrator = undefined;\n }\n\n private handleModelSet(model: CompositionModel): void {\n this.model = model;\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n trackId?: string\n ): void {\n if (this.taskManager.hasActiveTask(resource.id) && priority !== 'high') {\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, trackId);\n this.processQueue();\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n // Note: Each handler only deals with data, state is managed here\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video' || task.resource.type === 'audio') {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId);\n this.processQueue();\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → transfer to worker\n * Note: Images don't need streaming (typically < 5MB)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n const blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n\n // Create ImageBitmap in main thread, then transfer to worker\n // Note: In Worker context, ImageBitmap is the only way to create VideoFrame from image data\n const imageBitmap = await createImageBitmap(blob, {\n premultiplyAlpha: 'premultiply',\n colorSpaceConversion: 'default',\n imageOrientation: 'from-image',\n });\n\n await this.transferImageToWorker(task, imageBitmap);\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n */\n private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n if (!this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId, {\n lazy: true,\n });\n\n await composeWorker.send(\n 'receive_image',\n {\n resourceId: task.resourceId,\n sessionId: task.sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream || !this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.orchestrator.workers.get(workerType, task.sessionId, {\n lazy: true,\n });\n\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n if (this.orchestrator) {\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n this.enqueueLoad(resource, options?.priority || 'normal', options?.sessionId, options?.trackId);\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.unbind();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import type { Resource, CompositionModel } from '../../model';\nimport type { Orchestrator, ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventHandlers } from './EventHandlers';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\nimport { createImageBitmapFromBlob } from '../../utils/image-utils';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private orchestrator?: Orchestrator;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventHandlers?: EventHandlers;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n private blobCache = new Map<string, Blob>();\n private pendingTransfers = new Map<string, string[]>();\n\n constructor(options?: ResourceLoaderOptions) {\n const maxConcurrent = options?.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options?.onProgress, options?.config);\n this.eventBus = options?.eventBus;\n this.onStateChange = options?.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n\n if (options?.orchestrator) {\n this.bind(options.orchestrator);\n }\n }\n\n /**\n * Bind to Orchestrator event system\n */\n bind(orchestrator: Orchestrator): void {\n this.unbind();\n this.orchestrator = orchestrator;\n\n this.eventHandlers = new EventHandlers(\n orchestrator,\n (resourceId) => this.cancel(resourceId),\n (model) => this.handleModelSet(model)\n );\n }\n\n /**\n * Unbind from Orchestrator\n */\n unbind(): void {\n this.eventHandlers?.dispose();\n this.eventHandlers = undefined;\n this.orchestrator = undefined;\n }\n\n private handleModelSet(model: CompositionModel): void {\n this.model = model;\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n trackId?: string,\n isMainTrack = false\n ): void {\n if (this.taskManager.hasActiveTask(resource.id)) {\n if (priority === 'high') {\n // Preview channel: high priority can preempt (existing logic preserved)\n } else if (isMainTrack) {\n // Main track resource conflict: throw error for PreRenderService to detect\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n } else {\n // Attachment resource conflict: check cache\n if (this.blobCache.has(resource.id)) {\n // Already cached: immediately create ImageBitmap and transfer\n void this.transferCachedImage(resource.id, sessionId);\n return;\n } else {\n // Loading in progress: register to pending queue, will transfer after load complete\n this.registerPendingTransfer(resource.id, sessionId);\n console.debug(\n `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`\n );\n return;\n }\n }\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, trackId);\n this.processQueue();\n }\n\n private registerPendingTransfer(resourceId: string, sessionId?: string): void {\n if (!sessionId) return;\n\n const pending = this.pendingTransfers.get(resourceId) || [];\n if (!pending.includes(sessionId)) {\n pending.push(sessionId);\n this.pendingTransfers.set(resourceId, pending);\n }\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n // Note: Each handler only deals with data, state is managed here\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video' || task.resource.type === 'audio') {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId);\n this.processQueue();\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → transfer to worker\n * Note: Images don't need streaming (typically < 5MB)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n // Check cache\n let blob = this.blobCache.get(task.resourceId);\n\n if (!blob) {\n // Not cached: download and cache\n blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n this.blobCache.set(task.resourceId, blob);\n }\n\n // Create ImageBitmap from Blob (with special handling for SVG)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n await this.transferImageToWorker(task, imageBitmap);\n\n // Process pending transfer queue\n const pending = this.pendingTransfers.get(task.resourceId);\n if (pending && pending.length > 0) {\n for (const sessionId of pending) {\n await this.transferCachedImage(task.resourceId, sessionId);\n }\n this.pendingTransfers.delete(task.resourceId);\n }\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n */\n private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n if (!this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId, {\n lazy: true,\n });\n\n await composeWorker.send(\n 'receive_image',\n {\n resourceId: task.resourceId,\n sessionId: task.sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n /**\n * Transfer cached image to a session\n * Creates new ImageBitmap from cached Blob and transfers to worker\n */\n private async transferCachedImage(resourceId: string, sessionId?: string): Promise<void> {\n const blob = this.blobCache.get(resourceId);\n if (!blob || !sessionId) return;\n\n // Create new ImageBitmap from cached Blob (with SVG support)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Transfer to Worker\n if (!this.orchestrator) return;\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', sessionId, {\n lazy: true,\n });\n\n await composeWorker.send(\n 'receive_image',\n {\n resourceId,\n sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream || !this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.orchestrator.workers.get(workerType, task.sessionId, {\n lazy: true,\n });\n\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n if (this.orchestrator) {\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.trackId,\n options?.isMainTrack ?? false\n );\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.blobCache.clear();\n this.pendingTransfers.clear();\n this.unbind();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/load/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGrD,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IACtD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;IACvD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE;QACP,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;KAC7E,CAAC;CACH;AAGD,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAGD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/load/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Transform2D } from '../model/types';
|
|
2
|
+
|
|
3
|
+
export interface AnimationKeyframeInput {
|
|
4
|
+
time: number;
|
|
5
|
+
transform?: Transform2D;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
easing?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function interpolateKeyframes(keyframes: AnimationKeyframeInput[], timeUs: number): {
|
|
10
|
+
transform: Transform2D;
|
|
11
|
+
opacity?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function interpolateTransform(from: Transform2D, to: Transform2D, t: number): Transform2D;
|
|
14
|
+
export declare function lerp(a: number, b: number, t: number): number;
|
|
15
|
+
export declare function applyEasing(t: number, easing: string): number;
|
|
16
|
+
//# sourceMappingURL=animation-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation-utils.d.ts","sourceRoot":"","sources":["../../src/utils/animation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,sBAAsB,EAAE,EACnC,MAAM,EAAE,MAAM,GACb;IAAE,SAAS,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA8D9C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW,CAU/F;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkB7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.d.ts","sourceRoot":"","sources":["../../src/utils/image-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAYhF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
async function createImageBitmapFromBlob(blob) {
|
|
2
|
+
if (blob.type === "image/svg+xml") {
|
|
3
|
+
return createImageBitmapFromSVG(blob);
|
|
4
|
+
}
|
|
5
|
+
return createImageBitmap(blob, {
|
|
6
|
+
premultiplyAlpha: "premultiply",
|
|
7
|
+
colorSpaceConversion: "default",
|
|
8
|
+
imageOrientation: "from-image"
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
async function createImageBitmapFromSVG(blob) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const url = URL.createObjectURL(blob);
|
|
14
|
+
const img = new Image();
|
|
15
|
+
img.onload = () => {
|
|
16
|
+
URL.revokeObjectURL(url);
|
|
17
|
+
createImageBitmap(img, {
|
|
18
|
+
premultiplyAlpha: "premultiply",
|
|
19
|
+
colorSpaceConversion: "default"
|
|
20
|
+
}).then(resolve).catch(reject);
|
|
21
|
+
};
|
|
22
|
+
img.onerror = () => {
|
|
23
|
+
URL.revokeObjectURL(url);
|
|
24
|
+
reject(new Error("Failed to load SVG image"));
|
|
25
|
+
};
|
|
26
|
+
img.src = url;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
createImageBitmapFromBlob
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=image-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.js","sources":["../../src/utils/image-utils.ts"],"sourcesContent":["/**\n * Create ImageBitmap from Blob with format-specific handling\n */\nexport async function createImageBitmapFromBlob(blob: Blob): Promise<ImageBitmap> {\n // SVG needs special handling (load via Image element first)\n if (blob.type === 'image/svg+xml') {\n return createImageBitmapFromSVG(blob);\n }\n\n // Standard raster images\n return createImageBitmap(blob, {\n premultiplyAlpha: 'premultiply',\n colorSpaceConversion: 'default',\n imageOrientation: 'from-image',\n });\n}\n\n/**\n * Create ImageBitmap from SVG blob\n * SVG requires loading through Image element first for proper rendering\n */\nasync function createImageBitmapFromSVG(blob: Blob): Promise<ImageBitmap> {\n return new Promise((resolve, reject) => {\n const url = URL.createObjectURL(blob);\n const img = new Image();\n\n img.onload = () => {\n URL.revokeObjectURL(url);\n createImageBitmap(img, {\n premultiplyAlpha: 'premultiply',\n colorSpaceConversion: 'default',\n })\n .then(resolve)\n .catch(reject);\n };\n\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('Failed to load SVG image'));\n };\n\n img.src = url;\n });\n}\n"],"names":[],"mappings":"AAGA,eAAsB,0BAA0B,MAAkC;AAEhF,MAAI,KAAK,SAAS,iBAAiB;AACjC,WAAO,yBAAyB,IAAI;AAAA,EACtC;AAGA,SAAO,kBAAkB,MAAM;AAAA,IAC7B,kBAAkB;AAAA,IAClB,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,EAAA,CACnB;AACH;AAMA,eAAe,yBAAyB,MAAkC;AACxE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,UAAM,MAAM,IAAI,MAAA;AAEhB,QAAI,SAAS,MAAM;AACjB,UAAI,gBAAgB,GAAG;AACvB,wBAAkB,KAAK;AAAA,QACrB,kBAAkB;AAAA,QAClB,sBAAsB;AAAA,MAAA,CACvB,EACE,KAAK,OAAO,EACZ,MAAM,MAAM;AAAA,IACjB;AAEA,QAAI,UAAU,MAAM;AAClB,UAAI,gBAAgB,GAAG;AACvB,aAAO,IAAI,MAAM,0BAA0B,CAAC;AAAA,IAC9C;AAEA,QAAI,MAAM;AAAA,EACZ,CAAC;AACH;"}
|
|
@@ -39,7 +39,8 @@ class LayerRenderer {
|
|
|
39
39
|
this.ctx.globalCompositeOperation = layer.blendMode;
|
|
40
40
|
}
|
|
41
41
|
if (layer.transform) {
|
|
42
|
-
this.
|
|
42
|
+
const layerDimensions = this.getLayerDimensions(layer);
|
|
43
|
+
this.applyTransform(layer.transform, layerDimensions);
|
|
43
44
|
}
|
|
44
45
|
switch (layer.type) {
|
|
45
46
|
case "video":
|
|
@@ -59,9 +60,60 @@ class LayerRenderer {
|
|
|
59
60
|
this.ctx.restore();
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
parseDimension(value, canvasSize) {
|
|
64
|
+
if (value === void 0) return void 0;
|
|
65
|
+
if (typeof value === "number") return value;
|
|
66
|
+
const strValue = value;
|
|
67
|
+
if (strValue.includes("%")) {
|
|
68
|
+
const numValue = parseFloat(strValue);
|
|
69
|
+
return isNaN(numValue) ? void 0 : numValue / 100 * canvasSize;
|
|
70
|
+
}
|
|
71
|
+
const parsed = parseFloat(strValue);
|
|
72
|
+
return isNaN(parsed) ? void 0 : parsed;
|
|
73
|
+
}
|
|
74
|
+
getLayerDimensions(layer) {
|
|
75
|
+
if (layer.type === "image") {
|
|
76
|
+
const imageLayer = layer;
|
|
77
|
+
if (imageLayer.source) {
|
|
78
|
+
const imgWidth = imageLayer.source.width;
|
|
79
|
+
const imgHeight = imageLayer.source.height;
|
|
80
|
+
const isAttachment = !!imageLayer.attachmentId;
|
|
81
|
+
if (isAttachment) {
|
|
82
|
+
const targetWidthRaw = imageLayer.targetWidth;
|
|
83
|
+
const targetHeightRaw = imageLayer.targetHeight;
|
|
84
|
+
const targetWidth = this.parseDimension(targetWidthRaw, this.width);
|
|
85
|
+
const targetHeight = this.parseDimension(targetHeightRaw, this.height);
|
|
86
|
+
if (targetWidth && targetHeight) {
|
|
87
|
+
return { width: targetWidth, height: targetHeight };
|
|
88
|
+
} else if (targetWidth) {
|
|
89
|
+
return {
|
|
90
|
+
width: targetWidth,
|
|
91
|
+
height: imgHeight / imgWidth * targetWidth
|
|
92
|
+
};
|
|
93
|
+
} else if (targetHeight) {
|
|
94
|
+
return {
|
|
95
|
+
width: imgWidth / imgHeight * targetHeight,
|
|
96
|
+
height: targetHeight
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { width: imgWidth, height: imgHeight };
|
|
101
|
+
}
|
|
102
|
+
} else if (layer.type === "video") {
|
|
103
|
+
const videoLayer = layer;
|
|
104
|
+
const videoFrame = videoLayer.videoFrame;
|
|
105
|
+
return {
|
|
106
|
+
width: videoFrame.displayWidth || videoFrame.codedWidth,
|
|
107
|
+
height: videoFrame.displayHeight || videoFrame.codedHeight
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { width: this.width, height: this.height };
|
|
111
|
+
}
|
|
112
|
+
applyTransform(transform, layerDimensions) {
|
|
113
|
+
const anchorX = transform.anchorX ?? 0.5;
|
|
114
|
+
const anchorY = transform.anchorY ?? 0.5;
|
|
115
|
+
const centerX = layerDimensions.width * anchorX;
|
|
116
|
+
const centerY = layerDimensions.height * anchorY;
|
|
65
117
|
this.ctx.translate(transform.x + centerX, transform.y + centerY);
|
|
66
118
|
if (transform.rotation) {
|
|
67
119
|
this.ctx.rotate(transform.rotation);
|
|
@@ -114,6 +166,33 @@ class LayerRenderer {
|
|
|
114
166
|
if (!source) {
|
|
115
167
|
return;
|
|
116
168
|
}
|
|
169
|
+
const isAttachment = !!layer.attachmentId;
|
|
170
|
+
const imgWidth = source.width;
|
|
171
|
+
const imgHeight = source.height;
|
|
172
|
+
let renderWidth;
|
|
173
|
+
let renderHeight;
|
|
174
|
+
if (isAttachment) {
|
|
175
|
+
const targetWidthRaw = layer.targetWidth;
|
|
176
|
+
const targetHeightRaw = layer.targetHeight;
|
|
177
|
+
const targetWidth = this.parseDimension(targetWidthRaw, this.width);
|
|
178
|
+
const targetHeight = this.parseDimension(targetHeightRaw, this.height);
|
|
179
|
+
if (targetWidth && targetHeight) {
|
|
180
|
+
renderWidth = targetWidth;
|
|
181
|
+
renderHeight = targetHeight;
|
|
182
|
+
} else if (targetWidth) {
|
|
183
|
+
renderWidth = targetWidth;
|
|
184
|
+
renderHeight = imgHeight / imgWidth * targetWidth;
|
|
185
|
+
} else if (targetHeight) {
|
|
186
|
+
renderHeight = targetHeight;
|
|
187
|
+
renderWidth = imgWidth / imgHeight * targetHeight;
|
|
188
|
+
} else {
|
|
189
|
+
renderWidth = imgWidth;
|
|
190
|
+
renderHeight = imgHeight;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
renderWidth = this.width;
|
|
194
|
+
renderHeight = this.height;
|
|
195
|
+
}
|
|
117
196
|
if (crop) {
|
|
118
197
|
this.ctx.drawImage(
|
|
119
198
|
source,
|
|
@@ -123,11 +202,11 @@ class LayerRenderer {
|
|
|
123
202
|
crop.height,
|
|
124
203
|
0,
|
|
125
204
|
0,
|
|
126
|
-
|
|
127
|
-
|
|
205
|
+
renderWidth,
|
|
206
|
+
renderHeight
|
|
128
207
|
);
|
|
129
208
|
} else {
|
|
130
|
-
this.ctx.drawImage(source, 0, 0,
|
|
209
|
+
this.ctx.drawImage(source, 0, 0, renderWidth, renderHeight);
|
|
131
210
|
}
|
|
132
211
|
}
|
|
133
212
|
}
|
|
@@ -810,9 +889,87 @@ class VideoComposer {
|
|
|
810
889
|
this.filterProcessor.clearCache();
|
|
811
890
|
}
|
|
812
891
|
}
|
|
892
|
+
function interpolateKeyframes(keyframes, timeUs) {
|
|
893
|
+
const defaultTransform = {
|
|
894
|
+
x: 0,
|
|
895
|
+
y: 0,
|
|
896
|
+
scaleX: 1,
|
|
897
|
+
scaleY: 1,
|
|
898
|
+
rotation: 0,
|
|
899
|
+
anchorX: 0.5,
|
|
900
|
+
anchorY: 0.5
|
|
901
|
+
};
|
|
902
|
+
if (keyframes.length === 0) {
|
|
903
|
+
return { transform: defaultTransform };
|
|
904
|
+
}
|
|
905
|
+
const firstFrame = keyframes[0];
|
|
906
|
+
const lastFrame = keyframes[keyframes.length - 1];
|
|
907
|
+
if (timeUs <= firstFrame.time) {
|
|
908
|
+
return {
|
|
909
|
+
transform: { ...defaultTransform, ...firstFrame.transform },
|
|
910
|
+
opacity: firstFrame.opacity
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
if (timeUs >= lastFrame.time) {
|
|
914
|
+
return {
|
|
915
|
+
transform: { ...defaultTransform, ...lastFrame.transform },
|
|
916
|
+
opacity: lastFrame.opacity
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
let prevFrame = firstFrame;
|
|
920
|
+
let nextFrame = lastFrame;
|
|
921
|
+
for (let i = 0; i < keyframes.length - 1; i++) {
|
|
922
|
+
const currentFrame = keyframes[i];
|
|
923
|
+
const followingFrame = keyframes[i + 1];
|
|
924
|
+
if (timeUs >= currentFrame.time && timeUs < followingFrame.time) {
|
|
925
|
+
prevFrame = currentFrame;
|
|
926
|
+
nextFrame = followingFrame;
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const duration = nextFrame.time - prevFrame.time;
|
|
931
|
+
const elapsed = timeUs - prevFrame.time;
|
|
932
|
+
const progress = elapsed / duration;
|
|
933
|
+
const easedProgress = applyEasing(progress, prevFrame.easing ?? "linear");
|
|
934
|
+
const prevTransform = prevFrame.transform ?? { x: 0, y: 0 };
|
|
935
|
+
const nextTransform = nextFrame.transform ?? { x: 0, y: 0 };
|
|
936
|
+
const transform = interpolateTransform(prevTransform, nextTransform, easedProgress);
|
|
937
|
+
const opacity = prevFrame.opacity !== void 0 && nextFrame.opacity !== void 0 ? lerp(prevFrame.opacity, nextFrame.opacity, easedProgress) : void 0;
|
|
938
|
+
return { transform, opacity };
|
|
939
|
+
}
|
|
940
|
+
function interpolateTransform(from, to, t) {
|
|
941
|
+
return {
|
|
942
|
+
x: lerp(from.x ?? 0, to.x ?? 0, t),
|
|
943
|
+
y: lerp(from.y ?? 0, to.y ?? 0, t),
|
|
944
|
+
scaleX: lerp(from.scaleX ?? 1, to.scaleX ?? 1, t),
|
|
945
|
+
scaleY: lerp(from.scaleY ?? 1, to.scaleY ?? 1, t),
|
|
946
|
+
rotation: lerp(from.rotation ?? 0, to.rotation ?? 0, t),
|
|
947
|
+
anchorX: lerp(from.anchorX ?? 0.5, to.anchorX ?? 0.5, t),
|
|
948
|
+
anchorY: lerp(from.anchorY ?? 0.5, to.anchorY ?? 0.5, t)
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function lerp(a, b, t) {
|
|
952
|
+
return a + (b - a) * t;
|
|
953
|
+
}
|
|
954
|
+
function applyEasing(t, easing) {
|
|
955
|
+
switch (easing) {
|
|
956
|
+
case "linear":
|
|
957
|
+
return t;
|
|
958
|
+
case "ease-in":
|
|
959
|
+
return t * t * t;
|
|
960
|
+
case "ease-out": {
|
|
961
|
+
const oneMinusT = 1 - t;
|
|
962
|
+
return 1 - oneMinusT * oneMinusT * oneMinusT;
|
|
963
|
+
}
|
|
964
|
+
case "ease-in-out":
|
|
965
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
966
|
+
default:
|
|
967
|
+
return t;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
813
970
|
function resolveActiveLayers(layers, timestamp) {
|
|
814
971
|
return layers.filter((layer) => {
|
|
815
|
-
if (layer.
|
|
972
|
+
if (!layer.payload.attachmentId) {
|
|
816
973
|
return true;
|
|
817
974
|
}
|
|
818
975
|
if (layer.status !== "ready") {
|
|
@@ -823,7 +980,7 @@ function resolveActiveLayers(layers, timestamp) {
|
|
|
823
980
|
);
|
|
824
981
|
});
|
|
825
982
|
}
|
|
826
|
-
function materializeLayer(layer, frame) {
|
|
983
|
+
function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
827
984
|
const baseLayer = {
|
|
828
985
|
id: layer.layerId,
|
|
829
986
|
type: layer.type,
|
|
@@ -853,22 +1010,74 @@ function materializeLayer(layer, frame) {
|
|
|
853
1010
|
lineHeight: payload.lineHeight,
|
|
854
1011
|
textAlign: payload.align,
|
|
855
1012
|
verticalAlign: "bottom"
|
|
856
|
-
// Subtitles positioned at bottom
|
|
857
1013
|
};
|
|
858
1014
|
}
|
|
859
1015
|
if (layer.type === "image") {
|
|
860
1016
|
const payload = layer.payload;
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1017
|
+
const resourceId = payload.resourceId;
|
|
1018
|
+
const source = imageMap.get(resourceId) ?? null;
|
|
1019
|
+
const imageLayer = {
|
|
1020
|
+
...baseLayer,
|
|
1021
|
+
type: "image",
|
|
1022
|
+
source,
|
|
1023
|
+
attachmentId: payload.attachmentId
|
|
1024
|
+
};
|
|
1025
|
+
if (payload.targetWidth !== void 0) {
|
|
1026
|
+
imageLayer.targetWidth = payload.targetWidth;
|
|
1027
|
+
}
|
|
1028
|
+
if (payload.targetHeight !== void 0) {
|
|
1029
|
+
imageLayer.targetHeight = payload.targetHeight;
|
|
1030
|
+
}
|
|
1031
|
+
if (payload.animation && globalTimeUs !== void 0) {
|
|
1032
|
+
const animState = computeAnimationState(payload.animation, globalTimeUs);
|
|
1033
|
+
if (!animState.visible) {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
imageLayer.transform = {
|
|
1037
|
+
x: animState.transform.x ?? 0,
|
|
1038
|
+
y: animState.transform.y ?? 0,
|
|
1039
|
+
scaleX: animState.transform.scaleX ?? 1,
|
|
1040
|
+
scaleY: animState.transform.scaleY ?? 1,
|
|
1041
|
+
rotation: animState.transform.rotation ?? 0,
|
|
1042
|
+
anchorX: animState.transform.anchorX ?? 0.5,
|
|
1043
|
+
anchorY: animState.transform.anchorY ?? 0.5
|
|
867
1044
|
};
|
|
1045
|
+
if (animState.opacity !== void 0) {
|
|
1046
|
+
imageLayer.opacity = animState.opacity;
|
|
1047
|
+
}
|
|
868
1048
|
}
|
|
1049
|
+
return imageLayer;
|
|
869
1050
|
}
|
|
870
1051
|
return baseLayer;
|
|
871
1052
|
}
|
|
1053
|
+
function computeAnimationState(animation, globalTimeUs) {
|
|
1054
|
+
const { position, keyframes, overlayClipStartUs } = animation;
|
|
1055
|
+
const relativeTimeUs = globalTimeUs - overlayClipStartUs;
|
|
1056
|
+
if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {
|
|
1057
|
+
return {
|
|
1058
|
+
transform: { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0, anchorX: 0.5, anchorY: 0.5 },
|
|
1059
|
+
opacity: 0,
|
|
1060
|
+
visible: false
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
const animState = interpolateKeyframes(keyframes, relativeTimeUs);
|
|
1064
|
+
const rotationDeg = animState.transform?.rotation ?? 0;
|
|
1065
|
+
const rotationRad = rotationDeg * Math.PI / 180;
|
|
1066
|
+
const finalTransform = {
|
|
1067
|
+
x: position.x + (animState.transform?.x ?? 0),
|
|
1068
|
+
y: position.y + (animState.transform?.y ?? 0),
|
|
1069
|
+
scaleX: animState.transform?.scaleX ?? 1,
|
|
1070
|
+
scaleY: animState.transform?.scaleY ?? 1,
|
|
1071
|
+
rotation: rotationRad,
|
|
1072
|
+
anchorX: animState.transform?.anchorX ?? 0.5,
|
|
1073
|
+
anchorY: animState.transform?.anchorY ?? 0.5
|
|
1074
|
+
};
|
|
1075
|
+
return {
|
|
1076
|
+
transform: finalTransform,
|
|
1077
|
+
opacity: animState.opacity,
|
|
1078
|
+
visible: true
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
872
1081
|
class VideoComposeWorker {
|
|
873
1082
|
channel;
|
|
874
1083
|
composer = null;
|
|
@@ -878,7 +1087,7 @@ class VideoComposeWorker {
|
|
|
878
1087
|
upstreamPort = null;
|
|
879
1088
|
instructions = null;
|
|
880
1089
|
streamState = null;
|
|
881
|
-
|
|
1090
|
+
imageMap = /* @__PURE__ */ new Map();
|
|
882
1091
|
constructor() {
|
|
883
1092
|
this.channel = new WorkerChannel(self, {
|
|
884
1093
|
name: "VideoComposeWorker",
|
|
@@ -923,7 +1132,6 @@ class VideoComposeWorker {
|
|
|
923
1132
|
*/
|
|
924
1133
|
async handleConfigure(payload) {
|
|
925
1134
|
const { config, initial } = payload;
|
|
926
|
-
console.log("[VideoComposeWorker] handleConfigure", config, initial);
|
|
927
1135
|
const hasValidDimensions = config.width > 0 && config.height > 0;
|
|
928
1136
|
const hasValidFps = config.fps > 0;
|
|
929
1137
|
if (!hasValidDimensions || !hasValidFps) {
|
|
@@ -1043,8 +1251,8 @@ class VideoComposeWorker {
|
|
|
1043
1251
|
this.upstreamPort?.close();
|
|
1044
1252
|
this.downstreamPort = null;
|
|
1045
1253
|
this.upstreamPort = null;
|
|
1046
|
-
this.
|
|
1047
|
-
this.
|
|
1254
|
+
this.imageMap.forEach((bitmap) => bitmap.close());
|
|
1255
|
+
this.imageMap.clear();
|
|
1048
1256
|
this.instructions = null;
|
|
1049
1257
|
this.streamState = null;
|
|
1050
1258
|
this.channel.state = WorkerState.Disposed;
|
|
@@ -1067,16 +1275,21 @@ class VideoComposeWorker {
|
|
|
1067
1275
|
* only accepts ImageBitmap/OffscreenCanvas, not HTMLImageElement or Blob
|
|
1068
1276
|
*/
|
|
1069
1277
|
async handleReceiveImage(payload) {
|
|
1070
|
-
const { sessionId, imageBitmap } = payload;
|
|
1278
|
+
const { resourceId, sessionId, imageBitmap } = payload;
|
|
1071
1279
|
if (!this.sessionId) {
|
|
1072
1280
|
this.sessionId = sessionId;
|
|
1073
1281
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1282
|
+
const existing = this.imageMap.get(resourceId);
|
|
1283
|
+
if (existing) {
|
|
1284
|
+
existing.close();
|
|
1076
1285
|
}
|
|
1077
|
-
this.
|
|
1286
|
+
this.imageMap.set(resourceId, imageBitmap);
|
|
1078
1287
|
if (this.instructions) {
|
|
1079
|
-
|
|
1288
|
+
const mainLayer = this.instructions.layers.find((l) => !l.payload.attachmentId);
|
|
1289
|
+
const mainResourceId = mainLayer?.payload.resourceId;
|
|
1290
|
+
if (resourceId === mainResourceId) {
|
|
1291
|
+
await this.startImageFrameStream();
|
|
1292
|
+
}
|
|
1080
1293
|
}
|
|
1081
1294
|
return { success: true };
|
|
1082
1295
|
}
|
|
@@ -1087,8 +1300,8 @@ class VideoComposeWorker {
|
|
|
1087
1300
|
this.upstreamPort?.close();
|
|
1088
1301
|
this.downstreamPort = null;
|
|
1089
1302
|
this.upstreamPort = null;
|
|
1090
|
-
this.
|
|
1091
|
-
this.
|
|
1303
|
+
this.imageMap.forEach((bitmap) => bitmap.close());
|
|
1304
|
+
this.imageMap.clear();
|
|
1092
1305
|
return { success: true };
|
|
1093
1306
|
}
|
|
1094
1307
|
async startImageFrameStream() {
|
|
@@ -1099,6 +1312,14 @@ class VideoComposeWorker {
|
|
|
1099
1312
|
if (!timeline) {
|
|
1100
1313
|
return;
|
|
1101
1314
|
}
|
|
1315
|
+
const mainLayer = this.instructions.layers.find((l) => !l.payload.attachmentId);
|
|
1316
|
+
if (!mainLayer) return;
|
|
1317
|
+
const mainResourceId = mainLayer.payload.resourceId;
|
|
1318
|
+
const imageBitmap = this.imageMap.get(mainResourceId);
|
|
1319
|
+
if (!imageBitmap) {
|
|
1320
|
+
console.warn("[VideoComposeWorker] Main track ImageBitmap not found:", mainResourceId);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1102
1323
|
const { composeStream, cacheStream, encodeStream } = this.composer.createStreams();
|
|
1103
1324
|
const { clipDurationUs, compositionFps } = timeline;
|
|
1104
1325
|
let currentTimeUs = 0;
|
|
@@ -1108,7 +1329,7 @@ class VideoComposeWorker {
|
|
|
1108
1329
|
controller.close();
|
|
1109
1330
|
return;
|
|
1110
1331
|
}
|
|
1111
|
-
const videoFrame = new VideoFrame(
|
|
1332
|
+
const videoFrame = new VideoFrame(imageBitmap, {
|
|
1112
1333
|
timestamp: currentTimeUs,
|
|
1113
1334
|
duration: frameDurationFromFps(compositionFps)
|
|
1114
1335
|
});
|
|
@@ -1149,9 +1370,15 @@ class VideoComposeWorker {
|
|
|
1149
1370
|
if (!activeLayers.length) {
|
|
1150
1371
|
return null;
|
|
1151
1372
|
}
|
|
1152
|
-
const
|
|
1373
|
+
const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
|
|
1374
|
+
const globalTimeUs = clipStartUs + clipRelativeTime;
|
|
1375
|
+
const layers = activeLayers.map((layer) => materializeLayer(layer, frame, this.imageMap, globalTimeUs)).filter((layer) => layer !== null);
|
|
1376
|
+
if (!layers.length) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1153
1379
|
return {
|
|
1154
1380
|
timeUs: clipRelativeTime,
|
|
1381
|
+
globalTimeUs,
|
|
1155
1382
|
layers,
|
|
1156
1383
|
transition: VideoComposeWorker.buildTransition(
|
|
1157
1384
|
instruction.transitions,
|