@meframe/core 0.0.22 → 0.0.24

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 (45) hide show
  1. package/dist/Meframe.d.ts +17 -0
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +20 -0
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +18 -1
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +19 -1
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/index.d.ts +3 -3
  10. package/dist/cache/index.d.ts.map +1 -1
  11. package/dist/cache/l2/IndexedDBStore.d.ts +74 -0
  12. package/dist/cache/l2/IndexedDBStore.d.ts.map +1 -0
  13. package/dist/cache/l2/IndexedDBStore.js +180 -0
  14. package/dist/cache/l2/IndexedDBStore.js.map +1 -0
  15. package/dist/cache/{L2Cache.d.ts → l2/L2Cache.d.ts} +24 -14
  16. package/dist/cache/l2/L2Cache.d.ts.map +1 -0
  17. package/dist/cache/l2/L2Cache.js +329 -0
  18. package/dist/cache/l2/L2Cache.js.map +1 -0
  19. package/dist/cache/l2/OPFSStore.d.ts +46 -0
  20. package/dist/cache/l2/OPFSStore.d.ts.map +1 -0
  21. package/dist/cache/l2/OPFSStore.js +131 -0
  22. package/dist/cache/l2/OPFSStore.js.map +1 -0
  23. package/dist/config/defaults.d.ts.map +1 -1
  24. package/dist/config/defaults.js +1 -0
  25. package/dist/config/defaults.js.map +1 -1
  26. package/dist/config/types.d.ts +3 -0
  27. package/dist/config/types.d.ts.map +1 -1
  28. package/dist/model/validation.js +2 -2
  29. package/dist/model/validation.js.map +1 -1
  30. package/dist/orchestrator/Orchestrator.js +1 -1
  31. package/dist/orchestrator/Orchestrator.js.map +1 -1
  32. package/dist/orchestrator/VideoClipSession.d.ts +1 -1
  33. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  34. package/dist/orchestrator/VideoClipSession.js +25 -36
  35. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  36. package/dist/orchestrator/types.d.ts +1 -0
  37. package/dist/orchestrator/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/cache/BatchWriter.d.ts +0 -25
  40. package/dist/cache/BatchWriter.d.ts.map +0 -1
  41. package/dist/cache/CacheStatsDecorator.d.ts +0 -27
  42. package/dist/cache/CacheStatsDecorator.d.ts.map +0 -1
  43. package/dist/cache/L2Cache.d.ts.map +0 -1
  44. package/dist/cache/L2Cache.js +0 -488
  45. package/dist/cache/L2Cache.js.map +0 -1
package/dist/Meframe.d.ts CHANGED
@@ -71,6 +71,23 @@ export declare class Meframe {
71
71
  * Useful for debugging or forcing re-render
72
72
  */
73
73
  clearCache(): Promise<void>;
74
+ /**
75
+ * List all cached projects
76
+ */
77
+ listCachedProjects(): Promise<Array<{
78
+ projectId: string;
79
+ totalBytes: number;
80
+ clipCount: number;
81
+ lastAccess: number;
82
+ }>>;
83
+ /**
84
+ * Clear cache for a specific project
85
+ */
86
+ clearProjectCache(projectId: string): Promise<void>;
87
+ /**
88
+ * Get cache size for a specific project (in bytes)
89
+ */
90
+ getProjectCacheSize(projectId: string): Promise<number>;
74
91
  /**
75
92
  * Clean up and destroy the instance
76
93
  */
@@ -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;IAqBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD;;OAEG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,aAAa;IAoDjB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA4FnD;;;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;IAoCF;;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":"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"}
package/dist/Meframe.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CompositionModel } from "./model/CompositionModel.js";
2
+ import "./config/defaults.js";
2
3
  import { loadConfig } from "./config/ConfigLoader.js";
3
4
  import { Orchestrator } from "./orchestrator/Orchestrator.js";
4
5
  import { PlaybackController } from "./controllers/PlaybackController.js";
@@ -28,6 +29,7 @@ class Meframe {
28
29
  this.events = this.eventBus.asReadonly();
29
30
  this.pluginManager = new PluginManager(this);
30
31
  this.orchestrator = new Orchestrator({
32
+ projectId: config.global.projectId,
31
33
  maxWorkers: config.maxWorkers,
32
34
  cacheConfig: config.cache,
33
35
  eventBus: this.eventBus,
@@ -259,6 +261,24 @@ class Meframe {
259
261
  await this.orchestrator.cacheManager.clear();
260
262
  console.log("[Meframe] Cache cleared successfully");
261
263
  }
264
+ /**
265
+ * List all cached projects
266
+ */
267
+ async listCachedProjects() {
268
+ return this.orchestrator.cacheManager.listProjects();
269
+ }
270
+ /**
271
+ * Clear cache for a specific project
272
+ */
273
+ async clearProjectCache(projectId) {
274
+ return this.orchestrator.cacheManager.clearProject(projectId);
275
+ }
276
+ /**
277
+ * Get cache size for a specific project (in bytes)
278
+ */
279
+ async getProjectCacheSize(projectId) {
280
+ return this.orchestrator.cacheManager.getProjectSize(projectId);
281
+ }
262
282
  /**
263
283
  * Clean up and destroy the instance
264
284
  */
@@ -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 }\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 this.preRenderService?.start();\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 this.playbackController?.renderCurrentFrame(0);\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 }\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({\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 // Only check main track clips (attachments are already included)\n const mainTrack = this.model.findTrack(this.model.mainTrackId);\n const allClips = mainTrack?.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 projectId: config.global.projectId,\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 }\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 this.preRenderService?.start();\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 this.playbackController?.renderCurrentFrame(0);\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 }\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({\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 // Only check main track clips (attachments are already included)\n const mainTrack = this.model.findTrack(this.model.mainTrackId);\n const allClips = mainTrack?.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 * List all cached projects\n */\n async listCachedProjects(): Promise<\n Array<{ projectId: string; totalBytes: number; clipCount: number; lastAccess: number }>\n > {\n return this.orchestrator.cacheManager.listProjects();\n }\n\n /**\n * Clear cache for a specific project\n */\n async clearProjectCache(projectId: string): Promise<void> {\n return this.orchestrator.cacheManager.clearProject(projectId);\n }\n\n /**\n * Get cache size for a specific project (in bytes)\n */\n async getProjectCacheSize(projectId: string): Promise<number> {\n return this.orchestrator.cacheManager.getProjectSize(projectId);\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,6 +1,6 @@
1
1
  import { TimeUs } from '../model/types';
2
2
  import { RcFrame } from '../model';
3
- import { L2Cache } from './L2Cache';
3
+ import { L2Cache } from './l2/L2Cache';
4
4
  import { EventBus } from '../event/EventBus';
5
5
  import { EventPayloadMap } from '../event/events';
6
6
 
@@ -123,6 +123,23 @@ export declare class CacheManager {
123
123
  * Get chunk metadata (decoderConfig) from L2 cache
124
124
  */
125
125
  getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null>;
126
+ /**
127
+ * List all cached projects
128
+ */
129
+ listProjects(): Promise<Array<{
130
+ projectId: string;
131
+ totalBytes: number;
132
+ clipCount: number;
133
+ lastAccess: number;
134
+ }>>;
135
+ /**
136
+ * Clear all cache data for a specific project
137
+ */
138
+ clearProject(projectId: string): Promise<void>;
139
+ /**
140
+ * Get cache size for a specific project (in bytes)
141
+ */
142
+ getProjectSize(projectId: string): Promise<number>;
126
143
  /**
127
144
  * Check if incoming frame satisfies clip ready condition
128
145
  * O(1) complexity - only checks single waiter and increments counter
@@ -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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"}
@@ -1,5 +1,5 @@
1
1
  import { VideoL1Cache } from "./l1/VideoL1Cache.js";
2
- import { L2Cache } from "./L2Cache.js";
2
+ import { L2Cache } from "./l2/L2Cache.js";
3
3
  import { MeframeEvent } from "../event/events.js";
4
4
  import { AudioL1Cache } from "./l1/AudioL1Cache.js";
5
5
  import { WaiterReplacedError } from "../utils/errors.js";
@@ -352,6 +352,24 @@ class CacheManager {
352
352
  async getL2Metadata(clipId, track) {
353
353
  return this.l2Cache.getClipMetadata(clipId, track);
354
354
  }
355
+ /**
356
+ * List all cached projects
357
+ */
358
+ async listProjects() {
359
+ return this.l2Cache.listProjects();
360
+ }
361
+ /**
362
+ * Clear all cache data for a specific project
363
+ */
364
+ async clearProject(projectId) {
365
+ return this.l2Cache.clearProject(projectId);
366
+ }
367
+ /**
368
+ * Get cache size for a specific project (in bytes)
369
+ */
370
+ async getProjectSize(projectId) {
371
+ return this.l2Cache.getProjectSize(projectId);
372
+ }
355
373
  /**
356
374
  * Check if incoming frame satisfies clip ready condition
357
375
  * O(1) complexity - only checks single waiter and increments counter
@@ -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';\nimport { WaiterReplacedError } from '../utils/errors';\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 ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\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 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 // Calculate global time for event emission\n const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;\n\n this.checkAndNotifyClipReady(params.clipId, timestamp);\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(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, 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 clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\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 this.videoL1Cache.invalidateClip(clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\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 /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n private checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\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":[],"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 './l2/L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\nimport { WaiterReplacedError } from '../utils/errors';\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 ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\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 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 // Calculate global time for event emission\n const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;\n\n this.checkAndNotifyClipReady(params.clipId, timestamp);\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(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, 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 clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\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 this.videoL1Cache.invalidateClip(clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\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 /**\n * List all cached projects\n */\n async listProjects(): Promise<\n Array<{ projectId: string; totalBytes: number; clipCount: number; lastAccess: number }>\n > {\n return this.l2Cache.listProjects();\n }\n\n /**\n * Clear all cache data for a specific project\n */\n async clearProject(projectId: string): Promise<void> {\n return this.l2Cache.clearProject(projectId);\n }\n\n /**\n * Get cache size for a specific project (in bytes)\n */\n async getProjectSize(projectId: string): Promise<number> {\n return this.l2Cache.getProjectSize(projectId);\n }\n\n /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n private checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\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":[],"mappings":";;;;;AAwCO,MAAM,aAAa;AAAA,EACP;AAAA,EACA;AAAA,EACR;AAAA,EACD,uCAAuB,IAAA;AAAA,EACvB;AAAA,EAER,YAAY,QAA4B,UAAsC;AAC5E,SAAK,eAAe,IAAI,aAAa,OAAO,EAAE;AAC9C,SAAK,UAAU,IAAI,QAAQ,OAAO,EAAE;AACpC,SAAK,eAAe,IAAI,aAAA;AACxB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,QAAQ,KAAA;AAAA,EACrB;AAAA,EAEA,MAAM,sBACJ,QACA,QAOe;AACf,UAAM,SAAS,OAAO,UAAA;AACtB,UAAM,UAAU,YAA2B;AACzC,YAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,UAAI,MAAM;AACR,eAAO,YAAA;AACP;AAAA,MACF;AACA,UAAI,OAAO;AACT,cAAM,MAAM,OAAO,MAAM,IAAI,OAAO,MAAM;AAC1C,cAAM,gBAAgB,KAAK,MAAM,MAAY,GAAG;AAEhD,cAAM,QAAS,MAAc,SAAS;AACtC,cAAM,WAAY,MAAc;AAChC,cAAM,YAAY,UAAU;AAC5B,cAAM,aAAa,UAAU;AAE7B,cAAM,YAAY,MAAM,aAAa;AAErC,cAAM,UAAU,KAAK,aAAa;AAAA,UAChC;AAAA,UACA,OAAO;AAAA,UACP;AAAA,UACA,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QAAA;AAEF,YAAI,CAAC,SAAS;AACZ,gBAAM,QAAA;AACN;AAAA,QACF;AAGA,cAAM,gBAAgB,OAAO,eAAe,KAAK;AAEjD,aAAK,wBAAwB,OAAO,QAAQ,SAAS;AAErD,YAAI,iBAAiB,GAAG;AACtB,eAAK,UAAU,KAAK,aAAa,YAAY;AAAA,YAC3C,QAAQ;AAAA,YACR,QAAQ,OAAO;AAAA,YACf,OAAO;AAAA,YACP,MAAM,QAAQ,gBAAgB;AAAA,UAAA,CAC/B;AAAA,QACH;AAEA,cAAM,OAAO,EAAE,QAAQ,OAAO,QAAQ,QAAQ,UAAA;AAC9C,aAAK,UAAU,KAAK,aAAa,mBAAmB;AAAA,UAClD,QAAQ;AAAA,UACR,aAAa,KAAK,MAAM,eAAe,aAAa;AAAA,UACpD,cAAc;AAAA,UACd,SAAS,OAAO;AAAA,UAChB,QAAQ,OAAO;AAAA,QAAA,CAChB;AAED,eAAO,QAAQ,IAAI;AAAA,MACrB;AAEA,YAAM,QAAA;AAAA,IACR;AAEA,QAAI;AACF,YAAM,QAAA;AAAA,IACR,SAAS,OAAO;AACd,WAAK,UAAU,KAAK,aAAa,qBAAqB;AAAA,QACpD,QAAQ;AAAA,QACR,QAAQ;AAAA,MAAA,CACT;AACD,aAAO,YAAA;AACP,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,qBACJ,QACA,QACA,OACA,SAIe;AACf,UAAM,SAAS,OAAO,UAAA;AACtB,UAAM,SAAuD,CAAA;AAC7D,UAAM,YAAY,UAAU,UAAU,KAAK;AAC3C,QAAI,gBAAqB;AACzB,QAAI;AACJ,UAAM,UAAU,YAA2B;AACzC,YAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,YAAM,EAAE,OAAO,UAAU,cAAA,IAAkB,SAAS,CAAA;AACpD,UAAI,eAAe;AACjB,mBAAW;AAEX,YAAI,CAAC,iBAAiB,cAAc,eAAe;AACjD,0BAAgB,cAAc;AAAA,QAChC;AAAA,MACF;AACA,UAAI,MAAM;AAER,YAAI,OAAO,SAAS,GAAG;AACrB,gBAAM,KAAK,QAAQ,IAAI,QAAQ,QAAQ,OAAO;AAAA,YAC5C,YAAY;AAAA,YACZ,UAAU;AAAA,UAAA,CACX;AACD,gBAAM,aAAa,OAAO,CAAC;AAC3B,cAAI,YAAY;AACd,iBAAK,UAAU,KAAK,aAAa,YAAY;AAAA,cAC3C;AAAA,cACA,QAAQ,WAAW;AAAA,cACnB,OAAO;AAAA,cACP,MAAM,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAAA,YAAA,CACtD;AAAA,UACH;AAAA,QACF,OAAO;AAEL,gBAAM,KAAK,QAAQ,aAAa,QAAQ,KAAK;AAAA,QAC/C;AACA,eAAO,YAAA;AAGP,YAAI,SAAS,YAAY;AACvB,kBAAQ,WAAW,QAAS;AAAA,QAC9B;AACA;AAAA,MACF;AAEA,UAAI,OAAO;AACT,eAAO,KAAK,KAAK;AAEjB,aAAK,UAAU,KAAK,aAAa,kBAAkB;AAAA,UACjD,QAAQ,MAAM;AAAA,UACd,YAAY,MAAM,YAAY;AAAA,UAC9B;AAAA,UACA,MAAM,MAAM;AAAA,QAAA,CACb;AAGD,YAAI,OAAO,UAAU,WAAW;AAC9B,gBAAM,eAAe,OAAO,OAAO,CAAC;AACpC,cAAI,aAAa,SAAS,GAAG;AAC3B,kBAAM,KAAK,QAAQ,IAAI,QAAQ,cAAc,OAAO;AAAA,cAClD,UAAU;AAAA,YAAA,CACX;AACD,iBAAK,UAAU,KAAK,aAAa,YAAY;AAAA,cAC3C;AAAA,cACA,QAAQ,MAAM;AAAA,cACd,OAAO;AAAA,cACP,MAAM,aAAa,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAAA,YAAA,CAC5D;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAA;AAAA,IACR;AAEA,QAAI;AACF,YAAM,QAAA;AAAA,IACR,SAAS,OAAO;AAEd,UAAI,OAAO,SAAS,GAAG;AACrB,cAAM,KAAK,QAAQ,IAAI,QAAQ,QAAQ,OAAO;AAAA,UAC5C,UAAU;AAAA,QAAA,CACX;AAAA,MACH;AACA,WAAK,UAAU,KAAK,aAAa,kBAAkB;AAAA,QACjD,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MAAA,CACD;AACD,aAAO,YAAA;AAGP,UAAI,SAAS,SAAS;AACpB,gBAAQ,QAAQ,KAAc;AAAA,MAChC;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,iBACE,QACA,UACM;AACN,SAAK,aAAa,aAAa,QAAQ,QAAQ;AAAA,EACjD;AAAA,EAEA,iBAAiB,QAAgB,WAAsB,gBAA8B;AACnF,SAAK,aAAa,iBAAiB,QAAQ,WAAW,cAAc;AAAA,EACtE;AAAA,EAEA,WAAW,QAAgB,SAAiB,OAAsC;AAChF,WAAO,KAAK,aAAa,OAAO,QAAQ,SAAS,KAAK;AAAA,EACxD;AAAA,EAEA,uBACE,QACA,SACA,OACiF;AACjF,WAAO,KAAK,aAAa,mBAAmB,QAAQ,SAAS,KAAK;AAAA,EACpE;AAAA,EAEA,WAAW,QAAyB;AAClC,WAAO,KAAK,aAAa,WAAW,MAAM;AAAA,EAC5C;AAAA,EAEA,kBAAwB;AACtB,SAAK,aAAa,MAAA;AAAA,EACpB;AAAA,EAEA,mBAAmB,QAAsB;AACvC,SAAK,aAAa,aAAa,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,QAAgB,QAAgC;AACvD,WAAO,KAAK,aAAa,IAAI,QAAQ,MAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkFA,MAAM,eAAe,QAA+B;AAClD,SAAK,aAAa,eAAe,MAAM;AACvC,UAAM,KAAK,QAAQ,eAAe,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAAsB;AACvC,SAAK,aAAa,eAAe,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAAyB;AACnC,WAAO,KAAK,aAAa,QAAQ,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBACE,QACA,UAAgF,IAC9D;AAClB,UAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,UAAM,cAAc,QAAQ,eAAe;AAG3C,UAAM,oBAAoB,KAAK,aAAa,kBAAkB,QAAQ,WAAW;AACjF,QAAI,qBAAqB,eAAe;AACtC,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAGA,UAAM,YAAY,KAAK,iBAAiB,IAAI,MAAM;AAClD,QAAI,WAAW;AACb,UAAI,UAAU,WAAW;AACvB,qBAAa,UAAU,SAAS;AAAA,MAClC;AACA,gBAAU,OAAO,IAAI,oBAAoB,MAAM,CAAC;AAAA,IAClD;AAEA,WAAO,IAAI,QAAiB,CAAC,SAAS,WAAW;AAC/C,YAAM,SAA0B;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,MAAA;AAGF,WAAK,iBAAiB,IAAI,QAAQ,MAAM;AAExC,UAAI,QAAQ,aAAa,QAAQ,YAAY,GAAG;AAC9C,eAAO,YAAY,WAAW,MAAM;AAClC,cAAI,KAAK,iBAAiB,IAAI,MAAM,MAAM,QAAQ;AAChD,iBAAK,iBAAiB,OAAO,MAAM;AAAA,UACrC;AACA,kBAAQ,KAAK;AAAA,QACf,GAAG,QAAQ,SAAS;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,aAAa,MAAA;AAClB,UAAM,KAAK,QAAQ,MAAA;AAAA,EACrB;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,IAAI,KAAK,aAAa,YAAA;AAAA,MACtB,IAAI,KAAK,QAAQ,YAAA;AAAA,IAAY;AAAA,EAEjC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBACJ,QACA,OACuE;AACvE,WAAO,KAAK,QAAQ,iBAAiB,QAAQ,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAgB,OAA4C;AAC5E,UAAM,SAAS,MAAM,KAAK,QAAQ,gBAAgB,QAAQ,KAAK;AAC/D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,QAAgB,OAAyC;AAC9E,UAAM,KAAK,QAAQ,aAAa,QAAQ,KAAK;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAAgB,OAA+C;AACjF,WAAO,KAAK,QAAQ,gBAAgB,QAAQ,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAEJ;AACA,WAAO,KAAK,QAAQ,aAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAkC;AACnD,WAAO,KAAK,QAAQ,aAAa,SAAS;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,WAAoC;AACvD,WAAO,KAAK,QAAQ,eAAe,SAAS;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBAAwB,QAAgB,kBAAgC;AAC9E,UAAM,SAAS,KAAK,iBAAiB,IAAI,MAAM;AAC/C,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAIA,UAAM,cAAc,OAAO,gBAAgB,KAAK,oBAAoB,OAAO;AAE3E,QAAI,aAAa;AACf,aAAO;AAEP,UAAI,OAAO,gBAAgB,OAAO,eAAe;AAC/C,YAAI,OAAO,WAAW;AACpB,uBAAa,OAAO,SAAS;AAAA,QAC/B;AACA,eAAO,QAAQ,IAAI;AACnB,aAAK,iBAAiB,OAAO,MAAM;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+CF;"}
@@ -1,7 +1,7 @@
1
1
  export { VideoL1Cache } from './l1/VideoL1Cache';
2
- export { L2Cache } from './L2Cache';
2
+ export { L2Cache } from './l2/L2Cache';
3
+ export { OPFSStore } from './l2/OPFSStore';
4
+ export { IndexedDBStore } from './l2/IndexedDBStore';
3
5
  export { CacheManager } from './CacheManager';
4
- export { BatchWriter } from './BatchWriter';
5
- export { CacheStatsDecorator } from './CacheStatsDecorator';
6
6
  export type { CacheConfig, CacheStats, CacheEntry, GOP, ClipMetadata } from './types';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { ChunkBatch } from './OPFSStore';
2
+
3
+ export interface ChunkRecord {
4
+ projectId: string;
5
+ clipId: string;
6
+ track: 'video' | 'audio';
7
+ fileName: string;
8
+ batches: ChunkBatch[];
9
+ lastAccess: number;
10
+ totalBytes: number;
11
+ isComplete: boolean;
12
+ expectedDurationUs?: number;
13
+ metadata?: {
14
+ codec?: string;
15
+ description?: Uint8Array;
16
+ codedWidth?: number;
17
+ codedHeight?: number;
18
+ displayAspectWidth?: number;
19
+ displayAspectHeight?: number;
20
+ colorSpace?: VideoColorSpaceInit;
21
+ hardwareAcceleration?: HardwareAcceleration;
22
+ optimizeForLatency?: boolean;
23
+ sampleRate?: number;
24
+ numberOfChannels?: number;
25
+ };
26
+ }
27
+ /**
28
+ * IndexedDB storage for L2 cache metadata
29
+ * Handles all database operations for chunk records
30
+ */
31
+ export declare class IndexedDBStore {
32
+ private db;
33
+ private initPromise;
34
+ init(): Promise<void>;
35
+ private initStorage;
36
+ /**
37
+ * Get a chunk record
38
+ */
39
+ getRecord(projectId: string, clipId: string, track: string): Promise<ChunkRecord | null>;
40
+ /**
41
+ * Put/update a chunk record
42
+ */
43
+ putRecord(record: ChunkRecord): Promise<void>;
44
+ /**
45
+ * Delete a chunk record
46
+ */
47
+ deleteRecord(projectId: string, clipId: string, track: string): Promise<void>;
48
+ /**
49
+ * Update last access time for a record
50
+ */
51
+ updateLastAccess(projectId: string, clipId: string, track: string): Promise<void>;
52
+ /**
53
+ * Collect all records for a specific clip
54
+ */
55
+ collectRecordsByClipId(projectId: string, clipId: string): Promise<ChunkRecord[]>;
56
+ /**
57
+ * Get all records for a specific project
58
+ */
59
+ getRecordsByProjectId(projectId: string): Promise<ChunkRecord[]>;
60
+ /**
61
+ * Get all records sorted by lastAccess (for quota enforcement)
62
+ */
63
+ getAllRecordsSortedByAccess(): Promise<ChunkRecord[]>;
64
+ /**
65
+ * Get all records (for project statistics)
66
+ */
67
+ getAllRecords(): Promise<ChunkRecord[]>;
68
+ /**
69
+ * Clear all data from chunks and meta stores
70
+ */
71
+ clear(): Promise<void>;
72
+ private promisifyRequest;
73
+ }
74
+ //# sourceMappingURL=IndexedDBStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexedDBStore.d.ts","sourceRoot":"","sources":["../../../src/cache/l2/IndexedDBStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,UAAU,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,UAAU,CAAC,EAAE,mBAAmB,CAAC;QACjC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;QAC5C,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAA4B;IACtC,OAAO,CAAC,WAAW,CAA8B;IAE3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,WAAW;IAqCzB;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAU9F;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnF;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAavF;;OAEG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA4BvF;;OAEG;IACG,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAatE;;OAEG;IACG,2BAA2B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB3D;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAwB7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,OAAO,CAAC,gBAAgB;CAMzB"}