@meframe/core 0.0.10 → 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.
Files changed (57) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +3 -0
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +12 -0
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +16 -4
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  9. package/dist/controllers/PlaybackController.js +3 -0
  10. package/dist/controllers/PlaybackController.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/model/CompositionModel.d.ts +2 -1
  15. package/dist/model/CompositionModel.d.ts.map +1 -1
  16. package/dist/model/CompositionModel.js +80 -11
  17. package/dist/model/CompositionModel.js.map +1 -1
  18. package/dist/model/types.d.ts +30 -1
  19. package/dist/model/types.d.ts.map +1 -1
  20. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  21. package/dist/orchestrator/CompositionPlanner.js +18 -2
  22. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  23. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  24. package/dist/orchestrator/Orchestrator.js +25 -6
  25. package/dist/orchestrator/Orchestrator.js.map +1 -1
  26. package/dist/orchestrator/VideoClipSession.d.ts +2 -1
  27. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  28. package/dist/orchestrator/VideoClipSession.js +16 -1
  29. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  30. package/dist/orchestrator/types.d.ts +1 -0
  31. package/dist/orchestrator/types.d.ts.map +1 -1
  32. package/dist/stages/compose/LayerRenderer.d.ts +2 -0
  33. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  34. package/dist/stages/compose/instructions.d.ts +24 -1
  35. package/dist/stages/compose/instructions.d.ts.map +1 -1
  36. package/dist/stages/compose/types.d.ts +2 -0
  37. package/dist/stages/compose/types.d.ts.map +1 -1
  38. package/dist/stages/load/ResourceLoader.d.ts +8 -0
  39. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  40. package/dist/stages/load/ResourceLoader.js +74 -12
  41. package/dist/stages/load/ResourceLoader.js.map +1 -1
  42. package/dist/stages/load/types.d.ts +1 -0
  43. package/dist/stages/load/types.d.ts.map +1 -1
  44. package/dist/stages/mux/MP4Muxer.js +1 -1
  45. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  46. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  47. package/dist/stages/mux/MuxManager.js +36 -5
  48. package/dist/stages/mux/MuxManager.js.map +1 -1
  49. package/dist/utils/animation-utils.d.ts +16 -0
  50. package/dist/utils/animation-utils.d.ts.map +1 -0
  51. package/dist/utils/image-utils.d.ts +5 -0
  52. package/dist/utils/image-utils.d.ts.map +1 -0
  53. package/dist/utils/image-utils.js +32 -0
  54. package/dist/utils/image-utils.js.map +1 -0
  55. package/dist/workers/stages/compose/video-compose.worker.js +263 -85
  56. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
  57. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,qBAAa,OAAO;IAClB,wDAAwD;IACxD,KAAK,EAAE,YAAY,CAAU;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2DAA2D;IAC3D,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACtC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAExE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,CAAsC;IAErD,OAAO;IAiBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQjE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD;;OAEG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,aAAa;IAkDjB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA0FnD;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAmCF;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB9B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAYhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAWpB"}
1
+ {"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,qBAAa,OAAO;IAClB,wDAAwD;IACxD,KAAK,EAAE,YAAY,CAAU;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2DAA2D;IAC3D,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACtC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAExE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,CAAsC;IAErD,OAAO;IAiBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQjE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD;;OAEG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,aAAa;IAkDjB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA6FnD;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAmCF;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB9B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAYhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAWpB"}
package/dist/Meframe.js CHANGED
@@ -195,6 +195,9 @@ class Meframe {
195
195
  durationMs: model.durationUs / 1e3,
196
196
  format: options.format || "mp4"
197
197
  });
198
+ this.eventBus.emit(MeframeEvent.ExportProgress, {
199
+ progress: 1
200
+ });
198
201
  return blob;
199
202
  } catch (error) {
200
203
  this.setState("error");
@@ -1 +1 @@
1
- {"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n this.playbackController?.seek(0);\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n }\n\n /**\n * Start preview and return a handle for control\n */\n startPreview(options?: {\n canvas?: HTMLCanvasElement | OffscreenCanvas;\n startUs?: TimeUs;\n }): PreviewHandle {\n this.ensureReady();\n\n // Use provided canvas or the one from config\n const canvas = options?.canvas || this.canvas;\n if (!canvas) {\n throw new Error('Canvas is required for preview');\n }\n\n // Store canvas for later use\n this.canvas = canvas;\n\n // Create playback controller\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this.orchestrator as any, this.eventBus, {\n canvas,\n startUs: options?.startUs,\n });\n this.playbackController.setAudioSession(this.orchestrator.audioSession);\n }\n\n // Start pre-render service for background caching\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Connect playback controller to pre-render service\n // Update pre-render position when playback position changes\n this.playbackController.on(MeframeEvent.PlaybackTimeUpdate, (payload: any) => {\n this.preRenderService?.updatePlaybackTime(payload.timeUs);\n });\n\n // Update playback active state\n this.playbackController.on(MeframeEvent.PlaybackPlay, () => {\n this.preRenderService?.setPlaybackActive(true);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackPause, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackStop, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n // Return preview handle\n return this.playbackController;\n }\n\n /**\n * Export the composition\n * Uses L2 cache for fast export, muxes in main thread\n */\n async export(options: ExportOptions): Promise<Blob> {\n this.ensureReady();\n this.setState('exporting');\n\n try {\n const model = this.model;\n if (!model) {\n throw new Error('No composition model set');\n }\n\n // 1. Check L2 cache readiness\n const readiness = await this.getExportReadiness();\n\n if (!readiness.ready) {\n // Emit preparing event\n this.eventBus.emit(MeframeEvent.ExportPreparing, {\n totalClips: readiness.totalClips,\n cachedClips: readiness.cachedClips,\n missingClips: readiness.missingClips,\n });\n\n // Ensure PreRenderService is started\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Create AbortController for timeout control\n const abortController = new AbortController();\n const timeoutMs = 5 * 60 * 1000; // 5 minutes\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, timeoutMs);\n\n try {\n // Actively trigger missing clips L2 rendering\n await this.preRenderService.ensureClipsInL2(readiness.missingClips, {\n onProgress: (completed, total) => {\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: completed / total,\n stage: 'preparing',\n message: `Preparing cache: ${completed}/${total} clips`,\n });\n },\n signal: abortController.signal,\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`Export preparation timeout after ${timeoutMs / 1000}s`);\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // 2. Continue normal export flow\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n this.eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n // Delegate to orchestrator\n const blob = await this.orchestrator.export(model, options);\n\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.ExportError, {\n error: error as Error,\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Check export readiness\n * Returns coverage info for L2 cache\n */\n async getExportReadiness(): Promise<{\n ready: boolean;\n totalClips: number;\n cachedClips: number;\n missingClips: string[];\n coverage: number;\n }> {\n if (!this.model) {\n return {\n ready: false,\n totalClips: 0,\n cachedClips: 0,\n missingClips: [],\n coverage: 0,\n };\n }\n\n const videoTracks = this.model.tracks.filter((t) => t.kind === 'video');\n const allClips = videoTracks.flatMap((t) => t.clips);\n\n const missingClips: string[] = [];\n let cachedCount = 0;\n\n for (const clip of allClips) {\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n cachedCount++;\n } else {\n missingClips.push(clip.id);\n }\n }\n\n return {\n ready: missingClips.length === 0,\n totalClips: allClips.length,\n cachedClips: cachedCount,\n missingClips,\n coverage: allClips.length > 0 ? cachedCount / allClips.length : 0,\n };\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clear all L1/L2 cache data\n * Useful for debugging or forcing re-render\n */\n async clearCache(): Promise<void> {\n this.ensureReady();\n\n // Stop playback first\n this.playbackController?.stop();\n\n // Stop pre-render service\n this.preRenderService?.stop();\n\n // Clear cache through CacheManager\n await this.orchestrator.cacheManager.clear();\n\n console.log('[Meframe] Cache cleared successfully');\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n this.playbackController?.seek(0);\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n }\n\n /**\n * Start preview and return a handle for control\n */\n startPreview(options?: {\n canvas?: HTMLCanvasElement | OffscreenCanvas;\n startUs?: TimeUs;\n }): PreviewHandle {\n this.ensureReady();\n\n // Use provided canvas or the one from config\n const canvas = options?.canvas || this.canvas;\n if (!canvas) {\n throw new Error('Canvas is required for preview');\n }\n\n // Store canvas for later use\n this.canvas = canvas;\n\n // Create playback controller\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this.orchestrator as any, this.eventBus, {\n canvas,\n startUs: options?.startUs,\n });\n this.playbackController.setAudioSession(this.orchestrator.audioSession);\n }\n\n // Start pre-render service for background caching\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Connect playback controller to pre-render service\n // Update pre-render position when playback position changes\n this.playbackController.on(MeframeEvent.PlaybackTimeUpdate, (payload: any) => {\n this.preRenderService?.updatePlaybackTime(payload.timeUs);\n });\n\n // Update playback active state\n this.playbackController.on(MeframeEvent.PlaybackPlay, () => {\n this.preRenderService?.setPlaybackActive(true);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackPause, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackStop, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n // Return preview handle\n return this.playbackController;\n }\n\n /**\n * Export the composition\n * Uses L2 cache for fast export, muxes in main thread\n */\n async export(options: ExportOptions): Promise<Blob> {\n this.ensureReady();\n this.setState('exporting');\n\n try {\n const model = this.model;\n if (!model) {\n throw new Error('No composition model set');\n }\n\n // 1. Check L2 cache readiness\n const readiness = await this.getExportReadiness();\n\n if (!readiness.ready) {\n // Emit preparing event\n this.eventBus.emit(MeframeEvent.ExportPreparing, {\n totalClips: readiness.totalClips,\n cachedClips: readiness.cachedClips,\n missingClips: readiness.missingClips,\n });\n\n // Ensure PreRenderService is started\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Create AbortController for timeout control\n const abortController = new AbortController();\n const timeoutMs = 5 * 60 * 1000; // 5 minutes\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, timeoutMs);\n\n try {\n // Actively trigger missing clips L2 rendering\n await this.preRenderService.ensureClipsInL2(readiness.missingClips, {\n onProgress: (completed, total) => {\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: completed / total,\n stage: 'preparing',\n message: `Preparing cache: ${completed}/${total} clips`,\n });\n },\n signal: abortController.signal,\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`Export preparation timeout after ${timeoutMs / 1000}s`);\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // 2. Continue normal export flow\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n this.eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n // Delegate to orchestrator\n const blob = await this.orchestrator.export(model, options);\n\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 1,\n });\n\n return blob;\n } catch (error) {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.ExportError, {\n error: error as Error,\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Check export readiness\n * Returns coverage info for L2 cache\n */\n async getExportReadiness(): Promise<{\n ready: boolean;\n totalClips: number;\n cachedClips: number;\n missingClips: string[];\n coverage: number;\n }> {\n if (!this.model) {\n return {\n ready: false,\n totalClips: 0,\n cachedClips: 0,\n missingClips: [],\n coverage: 0,\n };\n }\n\n const videoTracks = this.model.tracks.filter((t) => t.kind === 'video');\n const allClips = videoTracks.flatMap((t) => t.clips);\n\n const missingClips: string[] = [];\n let cachedCount = 0;\n\n for (const clip of allClips) {\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n cachedCount++;\n } else {\n missingClips.push(clip.id);\n }\n }\n\n return {\n ready: missingClips.length === 0,\n totalClips: allClips.length,\n cachedClips: cachedCount,\n missingClips,\n coverage: allClips.length > 0 ? cachedCount / allClips.length : 0,\n };\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clear all L1/L2 cache data\n * Useful for debugging or forcing re-render\n */\n async clearCache(): Promise<void> {\n this.ensureReady();\n\n // Stop playback first\n this.playbackController?.stop();\n\n // Stop pre-render service\n this.preRenderService?.stop();\n\n // Clear cache through CacheManager\n await this.orchestrator.cacheManager.clear();\n\n console.log('[Meframe] Cache cleared successfully');\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -51,6 +51,7 @@ export declare class CacheManager {
51
51
  clipId: string;
52
52
  trackId: string;
53
53
  fps: number;
54
+ clipStartUs?: TimeUs;
54
55
  onFrame: (info: {
55
56
  clipId: string;
56
57
  timeUs: TimeUs;
@@ -76,7 +77,18 @@ export declare class CacheManager {
76
77
  } | null;
77
78
  hasClipPCM(clipId: string): boolean;
78
79
  resetAudioCache(): void;
80
+ /**
81
+ * Get frame from cache (L1 or L2 decode)
82
+ * @param timeUs - Clip-relative timestamp (0-based)
83
+ * @param clipId - Clip identifier
84
+ */
79
85
  getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null>;
86
+ /**
87
+ * Wait for frame to be available in cache
88
+ * @param timeUs - Clip-relative timestamp (0-based)
89
+ * @param clipId - Clip identifier
90
+ * @param options - Wait options (timeout, tolerance, etc.)
91
+ */
80
92
  waitForFrame(timeUs: TimeUs, clipId: string, options?: WaitForFrameOptions): Promise<WaitForFrameResult>;
81
93
  invalidateClip(clipId: string): Promise<void>;
82
94
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"}
@@ -49,9 +49,10 @@ class CacheManager {
49
49
  return;
50
50
  }
51
51
  this.notifyFrameWaiters(params.clipId, timestamp, frameDuration, rcFrame);
52
- if (timestamp === 0) {
52
+ const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;
53
+ if (globalTimeUs === 0) {
53
54
  this.eventBus?.emit(MeframeEvent.CacheCover, {
54
- timeUs: timestamp,
55
+ timeUs: globalTimeUs,
55
56
  clipId: params.clipId,
56
57
  level: "L1",
57
58
  size: rcFrame.sizeEstimate ?? 0
@@ -59,8 +60,8 @@ class CacheManager {
59
60
  }
60
61
  const info = { clipId: params.clipId, timeUs: timestamp };
61
62
  this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {
62
- timeUs: timestamp,
63
- frameNumber: Math.floor(timestamp / frameDuration),
63
+ timeUs: globalTimeUs,
64
+ frameNumber: Math.floor(globalTimeUs / frameDuration),
64
65
  renderTimeMs: 0,
65
66
  trackId: params.trackId,
66
67
  clipId: params.clipId
@@ -182,6 +183,11 @@ class CacheManager {
182
183
  resetAudioCache() {
183
184
  this.audioL1Cache.reset();
184
185
  }
186
+ /**
187
+ * Get frame from cache (L1 or L2 decode)
188
+ * @param timeUs - Clip-relative timestamp (0-based)
189
+ * @param clipId - Clip identifier
190
+ */
185
191
  async getFrame(timeUs, clipId) {
186
192
  const rcFrame = this.videoL1Cache.get(timeUs, clipId);
187
193
  if (rcFrame) {
@@ -203,6 +209,12 @@ class CacheManager {
203
209
  );
204
210
  return tracked;
205
211
  }
212
+ /**
213
+ * Wait for frame to be available in cache
214
+ * @param timeUs - Clip-relative timestamp (0-based)
215
+ * @param clipId - Clip identifier
216
+ * @param options - Wait options (timeout, tolerance, etc.)
217
+ */
206
218
  waitForFrame(timeUs, clipId, options = {}) {
207
219
  const existing = this.videoL1Cache.get(timeUs, clipId);
208
220
  if (existing) {
@@ -1 +1 @@
1
- {"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface WaitForFrameOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n toleranceUs?: number;\n}\n\ninterface FrameWaiter {\n requestKey: string;\n clipId: string;\n targetTimeUs: TimeUs;\n resolve: (result: WaitForFrameResult) => void;\n reject: (reason?: unknown) => void;\n toleranceUs: number;\n timeoutId?: ReturnType<typeof setTimeout>;\n abortCleanup?: () => void;\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_WAIT_TOLERANCE_US = 33_333; // ≈1 frame @30fps\n\nexport interface WaitForFrameResult {\n frame: RcFrame | null;\n source: 'l1' | 'wait';\n timestampUs: TimeUs;\n clipId: string;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly l2Cache: L2Cache;\n private pendingFramePromises = new Map<string, Promise<WaitForFrameResult>>();\n private frameWaiters = new Map<string, Set<FrameWaiter>>();\n private clipReadyWaiters = new Map<string, ClipReadyWaiter[]>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame | { frame: VideoFrame; metadata?: any }>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = (value as any).frame || value;\n const metadata = (value as any).metadata;\n const gopSerial = metadata?.gopSerial;\n const isKeyframe = metadata?.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId,\n gopSerial,\n isKeyframe\n );\n if (!rcFrame) {\n await process();\n return;\n }\n\n this.notifyFrameWaiters(params.clipId, timestamp, frameDuration, rcFrame);\n\n if (timestamp === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: timestamp,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: timestamp,\n frameNumber: Math.floor(timestamp / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n throw error;\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n getClipPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n }\n\n async getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const rcFrame = this.videoL1Cache.get(timeUs, clipId);\n if (rcFrame) {\n return rcFrame;\n }\n\n const decodeKey = this.makeRequestKey(clipId, timeUs);\n const pending = this.pendingFramePromises.get(decodeKey);\n if (pending) {\n const result = await pending;\n return result.frame;\n }\n\n const decodePromise = this.decodeFromL2(timeUs, clipId);\n const tracked = decodePromise.finally(() => {\n this.pendingFramePromises.delete(decodeKey);\n });\n\n this.pendingFramePromises.set(\n decodeKey,\n tracked.then((frame) => ({ frame, source: 'wait' as const, timestampUs: timeUs, clipId }))\n );\n return tracked;\n }\n\n waitForFrame(\n timeUs: TimeUs,\n clipId: string,\n options: WaitForFrameOptions = {}\n ): Promise<WaitForFrameResult> {\n const existing = this.videoL1Cache.get(timeUs, clipId);\n if (existing) {\n return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n }\n\n const requestKey = this.makeRequestKey(clipId, timeUs);\n const existingPromise = this.pendingFramePromises.get(requestKey);\n if (existingPromise) {\n return existingPromise;\n }\n\n const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n const toleranceUs = Math.max(\n options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n DEFAULT_WAIT_TOLERANCE_US\n );\n\n const waiter: FrameWaiter = {\n requestKey,\n clipId,\n targetTimeUs: timeUs,\n resolve,\n reject,\n toleranceUs,\n };\n\n let waiters = this.frameWaiters.get(clipId);\n if (!waiters) {\n waiters = new Set();\n this.frameWaiters.set(clipId, waiters);\n }\n waiters.add(waiter);\n\n const signal = options.signal;\n if (signal) {\n const onAbort = (): void => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new DOMException('Render aborted', 'AbortError'));\n };\n\n if (signal.aborted) {\n onAbort();\n return;\n }\n\n signal.addEventListener('abort', onAbort, { once: true });\n waiter.abortCleanup = () => {\n signal.removeEventListener('abort', onAbort);\n };\n }\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new Error('waitForFrame timeout'));\n }, options.timeoutMs);\n }\n });\n\n const trackedPromise = promise.finally(() => {\n this.pendingFramePromises.delete(requestKey);\n });\n\n this.pendingFramePromises.set(requestKey, trackedPromise);\n return trackedPromise;\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n console.log(`[CacheManager] invalidateClip(${clipId}) - clearing L1 and L2`);\n this.videoL1Cache.invalidateRange(0, Infinity, clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n evictClip(clipId: string): void {\n this.videoL1Cache.evictClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n isClipCached(clipId: string): boolean {\n return this.videoL1Cache.isClipCached(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId);\n\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n resolve,\n reject,\n };\n\n const waiters = this.clipReadyWaiters.get(clipId) || [];\n waiters.push(waiter);\n this.clipReadyWaiters.set(clipId, waiters);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (waiters) {\n const remaining = waiters.filter((w) => w !== waiter);\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n private async decodeFromL2(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const encodedChunk = await this.l2Cache.get(timeUs, clipId);\n if (!encodedChunk) {\n return null;\n }\n\n return null;\n }\n\n private notifyFrameWaiters(\n clipId: string,\n timestampUs: TimeUs,\n frameDurationUs: TimeUs,\n frame: RcFrame\n ): void {\n const waiters = this.frameWaiters.get(clipId);\n if (!waiters || waiters.size === 0) {\n return;\n }\n\n const resolved: FrameWaiter[] = [];\n\n for (const waiter of waiters) {\n const matches = this.matchesTimestamp(\n waiter.targetTimeUs,\n timestampUs,\n frameDurationUs,\n waiter.toleranceUs\n );\n\n if (!matches) continue;\n\n resolved.push(waiter);\n this.cleanupWaiter(waiter);\n\n waiter.resolve({\n frame,\n source: 'wait',\n timestampUs,\n clipId,\n });\n }\n\n for (const waiter of resolved) {\n waiters.delete(waiter);\n }\n\n if (waiters.size === 0) {\n this.frameWaiters.delete(clipId);\n }\n\n this.notifyClipReadyWaiters(clipId);\n }\n\n private notifyClipReadyWaiters(clipId: string): void {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (!waiters || waiters.length === 0) {\n return;\n }\n\n const frameCount = this.videoL1Cache.getClipFrameCount(clipId);\n const resolved: ClipReadyWaiter[] = [];\n\n for (const waiter of waiters) {\n if (frameCount >= waiter.minFrameCount) {\n resolved.push(waiter);\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n }\n }\n\n const remaining = waiters.filter((w) => !resolved.includes(w));\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n\n private cleanupWaiter(waiter: FrameWaiter): void {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n waiter.timeoutId = undefined;\n }\n\n if (waiter.abortCleanup) {\n waiter.abortCleanup();\n waiter.abortCleanup = undefined;\n }\n }\n\n private removeWaiter(waiter: FrameWaiter): void {\n const waiters = this.frameWaiters.get(waiter.clipId);\n if (!waiters) return;\n\n waiters.delete(waiter);\n if (waiters.size === 0) {\n this.frameWaiters.delete(waiter.clipId);\n }\n }\n\n private matchesTimestamp(\n targetTimeUs: TimeUs,\n actualTimeUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs\n ): boolean {\n if (targetTimeUs === actualTimeUs) return true;\n\n const delta = Math.abs(targetTimeUs - actualTimeUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n return true;\n }\n\n return false;\n }\n\n private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n return `${clipId}:${timeUs}`;\n }\n}\n"],"names":["waiters"],"mappings":"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"}
1
+ {"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface WaitForFrameOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n toleranceUs?: number;\n}\n\ninterface FrameWaiter {\n requestKey: string;\n clipId: string;\n targetTimeUs: TimeUs;\n resolve: (result: WaitForFrameResult) => void;\n reject: (reason?: unknown) => void;\n toleranceUs: number;\n timeoutId?: ReturnType<typeof setTimeout>;\n abortCleanup?: () => void;\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_WAIT_TOLERANCE_US = 33_333; // ≈1 frame @30fps\n\nexport interface WaitForFrameResult {\n frame: RcFrame | null;\n source: 'l1' | 'wait';\n timestampUs: TimeUs;\n clipId: string;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly l2Cache: L2Cache;\n private pendingFramePromises = new Map<string, Promise<WaitForFrameResult>>();\n private frameWaiters = new Map<string, Set<FrameWaiter>>();\n private clipReadyWaiters = new Map<string, ClipReadyWaiter[]>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame | { frame: VideoFrame; metadata?: any }>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n clipStartUs?: TimeUs;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = (value as any).frame || value;\n const metadata = (value as any).metadata;\n const gopSerial = metadata?.gopSerial;\n const isKeyframe = metadata?.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId,\n gopSerial,\n isKeyframe\n );\n if (!rcFrame) {\n await process();\n return;\n }\n\n this.notifyFrameWaiters(params.clipId, timestamp, frameDuration, rcFrame);\n\n // Calculate global time for event emission\n const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;\n\n // Emit cover event only for the first frame of the composition (global time = 0)\n if (globalTimeUs === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: globalTimeUs,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: globalTimeUs,\n frameNumber: Math.floor(globalTimeUs / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n throw error;\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n getClipPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n }\n\n /**\n * Get frame from cache (L1 or L2 decode)\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n async getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const rcFrame = this.videoL1Cache.get(timeUs, clipId);\n if (rcFrame) {\n return rcFrame;\n }\n\n const decodeKey = this.makeRequestKey(clipId, timeUs);\n const pending = this.pendingFramePromises.get(decodeKey);\n if (pending) {\n const result = await pending;\n return result.frame;\n }\n\n const decodePromise = this.decodeFromL2(timeUs, clipId);\n const tracked = decodePromise.finally(() => {\n this.pendingFramePromises.delete(decodeKey);\n });\n\n this.pendingFramePromises.set(\n decodeKey,\n tracked.then((frame) => ({ frame, source: 'wait' as const, timestampUs: timeUs, clipId }))\n );\n return tracked;\n }\n\n /**\n * Wait for frame to be available in cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n * @param options - Wait options (timeout, tolerance, etc.)\n */\n waitForFrame(\n timeUs: TimeUs,\n clipId: string,\n options: WaitForFrameOptions = {}\n ): Promise<WaitForFrameResult> {\n const existing = this.videoL1Cache.get(timeUs, clipId);\n if (existing) {\n return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n }\n\n const requestKey = this.makeRequestKey(clipId, timeUs);\n const existingPromise = this.pendingFramePromises.get(requestKey);\n if (existingPromise) {\n return existingPromise;\n }\n\n const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n const toleranceUs = Math.max(\n options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n DEFAULT_WAIT_TOLERANCE_US\n );\n\n const waiter: FrameWaiter = {\n requestKey,\n clipId,\n targetTimeUs: timeUs,\n resolve,\n reject,\n toleranceUs,\n };\n\n let waiters = this.frameWaiters.get(clipId);\n if (!waiters) {\n waiters = new Set();\n this.frameWaiters.set(clipId, waiters);\n }\n waiters.add(waiter);\n\n const signal = options.signal;\n if (signal) {\n const onAbort = (): void => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new DOMException('Render aborted', 'AbortError'));\n };\n\n if (signal.aborted) {\n onAbort();\n return;\n }\n\n signal.addEventListener('abort', onAbort, { once: true });\n waiter.abortCleanup = () => {\n signal.removeEventListener('abort', onAbort);\n };\n }\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new Error('waitForFrame timeout'));\n }, options.timeoutMs);\n }\n });\n\n const trackedPromise = promise.finally(() => {\n this.pendingFramePromises.delete(requestKey);\n });\n\n this.pendingFramePromises.set(requestKey, trackedPromise);\n return trackedPromise;\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n console.log(`[CacheManager] invalidateClip(${clipId}) - clearing L1 and L2`);\n this.videoL1Cache.invalidateRange(0, Infinity, clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n evictClip(clipId: string): void {\n this.videoL1Cache.evictClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n isClipCached(clipId: string): boolean {\n return this.videoL1Cache.isClipCached(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId);\n\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n resolve,\n reject,\n };\n\n const waiters = this.clipReadyWaiters.get(clipId) || [];\n waiters.push(waiter);\n this.clipReadyWaiters.set(clipId, waiters);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (waiters) {\n const remaining = waiters.filter((w) => w !== waiter);\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n private async decodeFromL2(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const encodedChunk = await this.l2Cache.get(timeUs, clipId);\n if (!encodedChunk) {\n return null;\n }\n\n return null;\n }\n\n private notifyFrameWaiters(\n clipId: string,\n timestampUs: TimeUs,\n frameDurationUs: TimeUs,\n frame: RcFrame\n ): void {\n const waiters = this.frameWaiters.get(clipId);\n if (!waiters || waiters.size === 0) {\n return;\n }\n\n const resolved: FrameWaiter[] = [];\n\n for (const waiter of waiters) {\n const matches = this.matchesTimestamp(\n waiter.targetTimeUs,\n timestampUs,\n frameDurationUs,\n waiter.toleranceUs\n );\n\n if (!matches) continue;\n\n resolved.push(waiter);\n this.cleanupWaiter(waiter);\n\n waiter.resolve({\n frame,\n source: 'wait',\n timestampUs,\n clipId,\n });\n }\n\n for (const waiter of resolved) {\n waiters.delete(waiter);\n }\n\n if (waiters.size === 0) {\n this.frameWaiters.delete(clipId);\n }\n\n this.notifyClipReadyWaiters(clipId);\n }\n\n private notifyClipReadyWaiters(clipId: string): void {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (!waiters || waiters.length === 0) {\n return;\n }\n\n const frameCount = this.videoL1Cache.getClipFrameCount(clipId);\n const resolved: ClipReadyWaiter[] = [];\n\n for (const waiter of waiters) {\n if (frameCount >= waiter.minFrameCount) {\n resolved.push(waiter);\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n }\n }\n\n const remaining = waiters.filter((w) => !resolved.includes(w));\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n\n private cleanupWaiter(waiter: FrameWaiter): void {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n waiter.timeoutId = undefined;\n }\n\n if (waiter.abortCleanup) {\n waiter.abortCleanup();\n waiter.abortCleanup = undefined;\n }\n }\n\n private removeWaiter(waiter: FrameWaiter): void {\n const waiters = this.frameWaiters.get(waiter.clipId);\n if (!waiters) return;\n\n waiters.delete(waiter);\n if (waiters.size === 0) {\n this.frameWaiters.delete(waiter.clipId);\n }\n }\n\n private matchesTimestamp(\n targetTimeUs: TimeUs,\n actualTimeUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs\n ): boolean {\n if (targetTimeUs === actualTimeUs) return true;\n\n const delta = Math.abs(targetTimeUs - actualTimeUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n return true;\n }\n\n return false;\n }\n\n private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n return `${clipId}:${timeUs}`;\n }\n}\n"],"names":["waiters"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,GAAG,CAA+D;IAG1E,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;gBAEhB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IAyCrF,IAAI,IAAI,IAAI;YAME,aAAa;IAkC3B,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAgBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKlD,MAAM,IAAI,IAAI;IAId,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIxD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,UAAU;YAuBJ,kBAAkB;YAwBlB,uBAAuB;IA8CrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;YAID,kBAAkB;CAOjC"}
1
+ {"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,GAAG,CAA+D;IAG1E,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;gBAEhB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IAyCrF,IAAI,IAAI,IAAI;YAME,aAAa;IAkC3B,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAgBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKlD,MAAM,IAAI,IAAI;IAId,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIxD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIzD,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,UAAU;YAuBJ,kBAAkB;YAyBlB,uBAAuB;IA8CrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;YAID,kBAAkB;CAOjC"}
@@ -176,6 +176,9 @@ class PlaybackController {
176
176
  }
177
177
  setupListeners() {
178
178
  this.orchestrator.on(MeframeEvent.CacheCover, (event) => {
179
+ if (this.state === "playing" || this.state === "buffering") {
180
+ return;
181
+ }
179
182
  this.renderCurrentFrame(event.timeUs);
180
183
  });
181
184
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackState,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport { quantizeTimestampToFrame } from '../utils/time-utils';\nimport type { GlobalAudioSession } from '../stages/compose/GlobalAudioSession';\nimport type { Orchestrator } from '../orchestrator';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n // Playback state\n currentTimeUs: TimeUs = 0;\n private state: PlaybackState = 'idle';\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Animation loop\n private rafId: number | null = null;\n private startTime = 0;\n\n // Frame tracking\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n private audioContext: AudioContext | null = null;\n private audioSession: GlobalAudioSession | null = null;\n\n // Buffering state\n private isBuffering = false;\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n\n // Get 2D context with high quality settings\n const ctx = this.canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n colorSpace: 'srgb',\n } as any);\n if (!ctx) {\n throw new Error('Failed to get 2D context from canvas');\n }\n this.ctx = ctx as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n // Configure high quality rendering\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n\n // Set initial time if provided\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n if (options.autoStart) {\n this.play();\n }\n\n this.setupListeners();\n }\n\n // Playback control\n play(): void {\n if (this.state === 'playing') return;\n\n void this.startPlayback();\n }\n\n private async startPlayback(): Promise<void> {\n const wasIdle = this.state === 'idle';\n\n this.state = 'buffering';\n this.eventBus.emit(MeframeEvent.PlaybackBuffering);\n\n try {\n await this.orchestrator.ensureClipCache(this.currentTimeUs);\n\n const ready = await this.orchestrator.waitForClipReady(this.currentTimeUs, {\n minFrameCount: 30,\n timeoutMs: 3_000,\n });\n\n if (!ready) {\n console.warn('[PlaybackController] Buffering timeout, starting anyway');\n }\n\n this.state = 'playing';\n this.startTime = performance.now() - this.currentTimeUs / 1000 / this.playbackRate;\n await this.ensureAudioContext();\n if (this.audioSession && this.audioContext) {\n await this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n this.playbackLoop();\n\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n } catch (error) {\n console.error('[PlaybackController] Failed to start playback:', error);\n this.state = wasIdle ? 'idle' : 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n pause(): void {\n if (this.state !== 'playing') return;\n\n this.state = 'paused';\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.audioSession?.stopPlayback();\n\n this.eventBus.emit(MeframeEvent.PlaybackPause);\n }\n\n stop(): void {\n this.pause();\n this.currentTimeUs = 0;\n this.state = 'idle';\n this.frameCount = 0; // Reset frame counter\n this.lastFrameTime = 0; // Reset frame timing\n this.fps = 0; // Reset FPS\n\n // Clear canvas\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n this.audioSession?.reset();\n\n this.eventBus.emit(MeframeEvent.PlaybackStop);\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const wasPlaying = this.state === 'playing';\n const previousState = this.state;\n\n if (wasPlaying) {\n this.pause();\n }\n\n const clamped = this.clampTime(timeUs);\n this.currentTimeUs = clamped;\n\n this.state = 'seeking';\n this.audioSession?.stopPlayback();\n\n try {\n await this.renderCurrentFrame(clamped);\n this.eventBus.emit(MeframeEvent.PlaybackSeek, { timeUs: this.currentTimeUs });\n\n if (wasPlaying) {\n await this.startPlayback();\n } else {\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n } catch (error) {\n console.error('[PlaybackController] Seek error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n }\n\n // Playback properties\n setRate(rate: number): void {\n // Adjust start time to maintain current position\n const elapsed = performance.now() - this.startTime;\n this.playbackRate = rate;\n this.startTime = performance.now() - elapsed / rate;\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n\n this.audioSession?.setPlaybackRate(this.playbackRate);\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n\n this.audioSession?.setVolume(this.volume);\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession?.stopPlayback();\n } else if (this.state === 'playing' && this.audioContext) {\n this.audioSession?.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n const modelDuration = this.orchestrator.compositionModel?.durationUs;\n if (modelDuration !== undefined) {\n return modelDuration;\n }\n\n return 0;\n }\n\n get isPlaying(): boolean {\n return this.state === 'playing';\n }\n\n setAudioSession(session: GlobalAudioSession): void {\n this.audioSession = session;\n }\n\n // Resume is just an alias for play\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n private setupListeners(): void {\n this.orchestrator.on(MeframeEvent.CacheCover, (event) => {\n this.renderCurrentFrame(event.timeUs);\n });\n }\n\n // Private methods\n private playbackLoop(): void {\n // Only continue loop if actively playing (not buffering/paused/etc)\n if (this.state !== 'playing') {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n return;\n }\n\n this.rafId = requestAnimationFrame(async () => {\n // Check state again after async boundary\n if (this.state !== 'playing') {\n return;\n }\n\n this.updateTime();\n\n // Update audio clips based on current time\n this.audioSession?.updateTime(this.currentTimeUs);\n\n await this.renderCurrentFrame();\n\n // Check if still playing after render (might have entered buffering)\n if (this.state !== 'playing') {\n return;\n }\n\n // Calculate FPS based on actual frame timing\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n\n this.frameCount++;\n\n this.playbackLoop();\n });\n }\n\n private updateTime(): void {\n const elapsed = (performance.now() - this.startTime) * this.playbackRate;\n const rawTimeUs = elapsed * 1000;\n const fps = this.orchestrator.compositionModel?.fps;\n this.currentTimeUs = quantizeTimestampToFrame(rawTimeUs, 0, fps, 'nearest');\n\n // Check if reached end\n if (this.currentTimeUs >= this.duration) {\n if (this.loop) {\n this.currentTimeUs = 0;\n this.startTime = performance.now();\n } else {\n this.currentTimeUs = this.duration;\n this.pause();\n this.state = 'ended';\n this.eventBus.emit(MeframeEvent.PlaybackEnded, { timeUs: this.currentTimeUs });\n }\n }\n\n // Emit time update\n this.eventBus.emit(MeframeEvent.PlaybackTimeUpdate, { timeUs: this.currentTimeUs });\n }\n\n private async renderCurrentFrame(timeUs?: TimeUs): Promise<void> {\n try {\n const targetTime = timeUs ?? this.currentTimeUs;\n const rcFrame = await this.orchestrator.renderFrame(targetTime);\n if (!rcFrame) {\n // Cache miss during playback - trigger buffering\n if (this.state === 'playing') {\n await this.handlePlaybackBuffering(targetTime);\n }\n return;\n }\n\n await rcFrame.use((frame) => {\n // Ensure high quality rendering for every frame\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n });\n } catch (error) {\n console.error('Render error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n private async handlePlaybackBuffering(timeUs: TimeUs): Promise<void> {\n // Prevent duplicate buffering requests\n if (this.isBuffering) {\n return;\n }\n\n // Pause the playback loop\n const wasPlaying = this.state === 'playing';\n if (!wasPlaying) return;\n\n this.isBuffering = true;\n this.state = 'buffering';\n this.eventBus.emit(MeframeEvent.PlaybackBuffering);\n\n try {\n // Ensure clip cache for current time\n await this.orchestrator.ensureClipCache(timeUs);\n\n // Wait for minimum frames (smaller buffer for clip transitions)\n const ready = await this.orchestrator.waitForClipReady(timeUs, {\n minFrameCount: 15, // 0.5s @30fps - faster resume\n timeoutMs: 2_000,\n });\n\n if (!ready) {\n console.warn('[PlaybackController] Buffering timeout during playback');\n }\n\n // Resume playback\n this.state = 'playing';\n this.startTime = performance.now() - timeUs / 1000 / this.playbackRate;\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n\n // Restart playback loop\n if (!this.rafId) {\n this.playbackLoop();\n }\n } catch (error) {\n console.error('[PlaybackController] Buffering error:', error);\n this.state = 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n } finally {\n this.isBuffering = false;\n }\n }\n\n private clampTime(timeUs: TimeUs): TimeUs {\n return Math.max(0, Math.min(timeUs, this.duration));\n }\n\n // Cleanup\n dispose(): void {\n this.stop();\n }\n\n private async ensureAudioContext(): Promise<void> {\n if (this.audioContext) {\n return;\n }\n\n this.audioContext = new AudioContext();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackState,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport { quantizeTimestampToFrame } from '../utils/time-utils';\nimport type { GlobalAudioSession } from '../stages/compose/GlobalAudioSession';\nimport type { Orchestrator } from '../orchestrator';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n // Playback state\n currentTimeUs: TimeUs = 0;\n private state: PlaybackState = 'idle';\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Animation loop\n private rafId: number | null = null;\n private startTime = 0;\n\n // Frame tracking\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n private audioContext: AudioContext | null = null;\n private audioSession: GlobalAudioSession | null = null;\n\n // Buffering state\n private isBuffering = false;\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n\n // Get 2D context with high quality settings\n const ctx = this.canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n colorSpace: 'srgb',\n } as any);\n if (!ctx) {\n throw new Error('Failed to get 2D context from canvas');\n }\n this.ctx = ctx as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n // Configure high quality rendering\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n\n // Set initial time if provided\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n if (options.autoStart) {\n this.play();\n }\n\n this.setupListeners();\n }\n\n // Playback control\n play(): void {\n if (this.state === 'playing') return;\n\n void this.startPlayback();\n }\n\n private async startPlayback(): Promise<void> {\n const wasIdle = this.state === 'idle';\n\n this.state = 'buffering';\n this.eventBus.emit(MeframeEvent.PlaybackBuffering);\n\n try {\n await this.orchestrator.ensureClipCache(this.currentTimeUs);\n\n const ready = await this.orchestrator.waitForClipReady(this.currentTimeUs, {\n minFrameCount: 30,\n timeoutMs: 3_000,\n });\n\n if (!ready) {\n console.warn('[PlaybackController] Buffering timeout, starting anyway');\n }\n\n this.state = 'playing';\n this.startTime = performance.now() - this.currentTimeUs / 1000 / this.playbackRate;\n await this.ensureAudioContext();\n if (this.audioSession && this.audioContext) {\n await this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n this.playbackLoop();\n\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n } catch (error) {\n console.error('[PlaybackController] Failed to start playback:', error);\n this.state = wasIdle ? 'idle' : 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n pause(): void {\n if (this.state !== 'playing') return;\n\n this.state = 'paused';\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.audioSession?.stopPlayback();\n\n this.eventBus.emit(MeframeEvent.PlaybackPause);\n }\n\n stop(): void {\n this.pause();\n this.currentTimeUs = 0;\n this.state = 'idle';\n this.frameCount = 0; // Reset frame counter\n this.lastFrameTime = 0; // Reset frame timing\n this.fps = 0; // Reset FPS\n\n // Clear canvas\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n this.audioSession?.reset();\n\n this.eventBus.emit(MeframeEvent.PlaybackStop);\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const wasPlaying = this.state === 'playing';\n const previousState = this.state;\n\n if (wasPlaying) {\n this.pause();\n }\n\n const clamped = this.clampTime(timeUs);\n this.currentTimeUs = clamped;\n\n this.state = 'seeking';\n this.audioSession?.stopPlayback();\n\n try {\n await this.renderCurrentFrame(clamped);\n this.eventBus.emit(MeframeEvent.PlaybackSeek, { timeUs: this.currentTimeUs });\n\n if (wasPlaying) {\n await this.startPlayback();\n } else {\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n } catch (error) {\n console.error('[PlaybackController] Seek error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n }\n\n // Playback properties\n setRate(rate: number): void {\n // Adjust start time to maintain current position\n const elapsed = performance.now() - this.startTime;\n this.playbackRate = rate;\n this.startTime = performance.now() - elapsed / rate;\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n\n this.audioSession?.setPlaybackRate(this.playbackRate);\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n\n this.audioSession?.setVolume(this.volume);\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession?.stopPlayback();\n } else if (this.state === 'playing' && this.audioContext) {\n this.audioSession?.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n const modelDuration = this.orchestrator.compositionModel?.durationUs;\n if (modelDuration !== undefined) {\n return modelDuration;\n }\n\n return 0;\n }\n\n get isPlaying(): boolean {\n return this.state === 'playing';\n }\n\n setAudioSession(session: GlobalAudioSession): void {\n this.audioSession = session;\n }\n\n // Resume is just an alias for play\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n private setupListeners(): void {\n this.orchestrator.on(MeframeEvent.CacheCover, (event) => {\n // Only render cover in idle/paused state, not during playback\n if (this.state === 'playing' || this.state === 'buffering') {\n return;\n }\n\n // CacheCover event now contains global timeUs\n this.renderCurrentFrame(event.timeUs);\n });\n }\n\n // Private methods\n private playbackLoop(): void {\n // Only continue loop if actively playing (not buffering/paused/etc)\n if (this.state !== 'playing') {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n return;\n }\n\n this.rafId = requestAnimationFrame(async () => {\n // Check state again after async boundary\n if (this.state !== 'playing') {\n return;\n }\n\n this.updateTime();\n\n // Update audio clips based on current time\n this.audioSession?.updateTime(this.currentTimeUs);\n\n await this.renderCurrentFrame();\n\n // Check if still playing after render (might have entered buffering)\n if (this.state !== 'playing') {\n return;\n }\n\n // Calculate FPS based on actual frame timing\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n\n this.frameCount++;\n\n this.playbackLoop();\n });\n }\n\n private updateTime(): void {\n const elapsed = (performance.now() - this.startTime) * this.playbackRate;\n const rawTimeUs = elapsed * 1000;\n const fps = this.orchestrator.compositionModel?.fps;\n this.currentTimeUs = quantizeTimestampToFrame(rawTimeUs, 0, fps, 'nearest');\n\n // Check if reached end\n if (this.currentTimeUs >= this.duration) {\n if (this.loop) {\n this.currentTimeUs = 0;\n this.startTime = performance.now();\n } else {\n this.currentTimeUs = this.duration;\n this.pause();\n this.state = 'ended';\n this.eventBus.emit(MeframeEvent.PlaybackEnded, { timeUs: this.currentTimeUs });\n }\n }\n\n // Emit time update\n this.eventBus.emit(MeframeEvent.PlaybackTimeUpdate, { timeUs: this.currentTimeUs });\n }\n\n private async renderCurrentFrame(timeUs?: TimeUs): Promise<void> {\n try {\n const targetTime = timeUs ?? this.currentTimeUs;\n const rcFrame = await this.orchestrator.renderFrame(targetTime);\n\n if (!rcFrame) {\n // Cache miss during playback - trigger buffering\n if (this.state === 'playing') {\n await this.handlePlaybackBuffering(targetTime);\n }\n return;\n }\n\n await rcFrame.use((frame) => {\n // Ensure high quality rendering for every frame\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n });\n } catch (error) {\n console.error('Render error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n private async handlePlaybackBuffering(timeUs: TimeUs): Promise<void> {\n // Prevent duplicate buffering requests\n if (this.isBuffering) {\n return;\n }\n\n // Pause the playback loop\n const wasPlaying = this.state === 'playing';\n if (!wasPlaying) return;\n\n this.isBuffering = true;\n this.state = 'buffering';\n this.eventBus.emit(MeframeEvent.PlaybackBuffering);\n\n try {\n // Ensure clip cache for current time\n await this.orchestrator.ensureClipCache(timeUs);\n\n // Wait for minimum frames (smaller buffer for clip transitions)\n const ready = await this.orchestrator.waitForClipReady(timeUs, {\n minFrameCount: 15, // 0.5s @30fps - faster resume\n timeoutMs: 2_000,\n });\n\n if (!ready) {\n console.warn('[PlaybackController] Buffering timeout during playback');\n }\n\n // Resume playback\n this.state = 'playing';\n this.startTime = performance.now() - timeUs / 1000 / this.playbackRate;\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n\n // Restart playback loop\n if (!this.rafId) {\n this.playbackLoop();\n }\n } catch (error) {\n console.error('[PlaybackController] Buffering error:', error);\n this.state = 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n } finally {\n this.isBuffering = false;\n }\n }\n\n private clampTime(timeUs: TimeUs): TimeUs {\n return Math.max(0, Math.min(timeUs, this.duration));\n }\n\n // Cleanup\n dispose(): void {\n this.stop();\n }\n\n private async ensureAudioContext(): Promise<void> {\n if (this.audioContext) {\n return;\n }\n\n this.audioContext = new AudioContext();\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { Meframe } from './Meframe';
6
6
  export type { MeframeConfig, MeframeState } from './types';
7
7
  export { MeframeEvent } from './event/events';
8
8
  export { CompositionModel } from './model/CompositionModel';
9
- export type { CompositionModelData, CompositionPatch, DirtyRange, TimeUs, Track, Clip, Resource, Effect, Transition, Attachment, } from './model/types';
9
+ export type { CompositionModelData, CompositionPatch, DirtyRange, TimeUs, Track, Clip, Resource, Effect, Transition, Attachment, AnimationEffect, AnimationKeyframe, Transform2D, } from './model/types';
10
10
  export type { CacheConfig, CacheStats } from './cache/types';
11
11
  export type { Plugin, PluginHook } from './plugins/types';
12
12
  export { setupCanvasDPI, createHiDPICanvas, checkCanvasDPI } from './utils/canvas-utils';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,MAAM,EACN,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,UAAU,EACV,UAAU,GACX,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG7D,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGzF,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,MAAM,EACN,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,UAAU,EACV,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,GACZ,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG7D,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGzF,eAAO,MAAM,OAAO,UAAU,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @meframe/core-next - Next generation media processing framework\n * Based on WebCodecs API for high-performance video/audio processing\n */\n\n// Core exports\nexport { Meframe } from './Meframe';\nexport type { MeframeConfig, MeframeState } from './types';\nexport { MeframeEvent } from './event/events';\n\n// Model exports\nexport { CompositionModel } from './model/CompositionModel';\nexport type {\n CompositionModelData,\n CompositionPatch,\n DirtyRange,\n TimeUs,\n Track,\n Clip,\n Resource,\n Effect,\n Transition,\n Attachment,\n} from './model/types';\n\n// Cache exports\nexport type { CacheConfig, CacheStats } from './cache/types';\n\n// Plugin exports\nexport type { Plugin, PluginHook } from './plugins/types';\n\n// Utility exports\nexport { setupCanvasDPI, createHiDPICanvas, checkCanvasDPI } from './utils/canvas-utils';\n\n// Re-export version\nexport const VERSION = '0.0.1';\n"],"names":[],"mappings":";;;;AAmCO,MAAM,UAAU;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @meframe/core-next - Next generation media processing framework\n * Based on WebCodecs API for high-performance video/audio processing\n */\n\n// Core exports\nexport { Meframe } from './Meframe';\nexport type { MeframeConfig, MeframeState } from './types';\nexport { MeframeEvent } from './event/events';\n\n// Model exports\nexport { CompositionModel } from './model/CompositionModel';\nexport type {\n CompositionModelData,\n CompositionPatch,\n DirtyRange,\n TimeUs,\n Track,\n Clip,\n Resource,\n Effect,\n Transition,\n Attachment,\n AnimationEffect,\n AnimationKeyframe,\n Transform2D,\n} from './model/types';\n\n// Cache exports\nexport type { CacheConfig, CacheStats } from './cache/types';\n\n// Plugin exports\nexport type { Plugin, PluginHook } from './plugins/types';\n\n// Utility exports\nexport { setupCanvasDPI, createHiDPICanvas, checkCanvasDPI } from './utils/canvas-utils';\n\n// Re-export version\nexport const VERSION = '0.0.1';\n"],"names":[],"mappings":";;;;AAsCO,MAAM,UAAU;"}
@@ -44,6 +44,7 @@ export declare class CompositionModel {
44
44
  getDuration(): TimeUs;
45
45
  getTrackDuration(trackId: string): TimeUs;
46
46
  private buildIndexes;
47
- private calculateDuration;
47
+ private sinkAttachmentTracks;
48
+ private getTimeOverlap;
48
49
  }
49
50
  //# sourceMappingURL=CompositionModel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAI9E,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IAC1B,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChC,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IAyBtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKpE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAY/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI;IAOvF,kBAAkB,IAAI,QAAQ,EAAE;IAahC,WAAW,IAAI,MAAM;IAIrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUzC,OAAO,CAAC,YAAY;IAgCpB,OAAO,CAAC,iBAAiB;CAY1B"}
1
+ {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAmB,MAAM,SAAS,CAAC;AAI/F,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAG,MAAM,CAAC;IAC3B,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChC,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IA6BtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKpE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAY/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI;IAOvF,kBAAkB,IAAI,QAAQ,EAAE;IAahC,WAAW,IAAI,MAAM;IAIrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUzC,OAAO,CAAC,YAAY;IAwDpB,OAAO,CAAC,oBAAoB;IA0D5B,OAAO,CAAC,cAAc;CAoBvB"}