@meframe/core 0.0.26 → 0.0.28

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 (39) hide show
  1. package/dist/Meframe.d.ts +2 -2
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +9 -3
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +2 -0
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +9 -2
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/AudioL1Cache.d.ts +1 -1
  10. package/dist/cache/l1/AudioL1Cache.js +2 -2
  11. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  12. package/dist/controllers/PreRenderService.js +3 -0
  13. package/dist/controllers/PreRenderService.js.map +1 -1
  14. package/dist/model/CompositionModel.d.ts +1 -0
  15. package/dist/model/CompositionModel.d.ts.map +1 -1
  16. package/dist/model/CompositionModel.js +1 -1
  17. package/dist/model/CompositionModel.js.map +1 -1
  18. package/dist/model/patch.js +22 -59
  19. package/dist/model/patch.js.map +1 -1
  20. package/dist/model/types.d.ts +5 -0
  21. package/dist/model/types.d.ts.map +1 -1
  22. package/dist/model/types.js.map +1 -1
  23. package/dist/orchestrator/Orchestrator.d.ts +2 -1
  24. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  25. package/dist/orchestrator/Orchestrator.js +10 -2
  26. package/dist/orchestrator/Orchestrator.js.map +1 -1
  27. package/dist/stages/decode/BaseDecoder.js +1 -1
  28. package/dist/stages/decode/BaseDecoder.js.map +1 -1
  29. package/dist/stages/decode/types.d.ts +1 -0
  30. package/dist/stages/decode/types.d.ts.map +1 -1
  31. package/dist/workers/{BaseDecoder.BGsRDny7.js → BaseDecoder.BWYu1W0B.js} +2 -2
  32. package/dist/workers/BaseDecoder.BWYu1W0B.js.map +1 -0
  33. package/dist/workers/stages/decode/{audio-decode.worker.3SD5i_Oe.js → audio-decode.worker.DnS17GD9.js} +2 -2
  34. package/dist/workers/stages/decode/{audio-decode.worker.3SD5i_Oe.js.map → audio-decode.worker.DnS17GD9.js.map} +1 -1
  35. package/dist/workers/stages/decode/{video-decode.worker.BAB6216v.js → video-decode.worker.BEYsjOXp.js} +2 -2
  36. package/dist/workers/stages/decode/{video-decode.worker.BAB6216v.js.map → video-decode.worker.BEYsjOXp.js.map} +1 -1
  37. package/dist/workers/worker-manifest.json +2 -2
  38. package/package.json +1 -1
  39. package/dist/workers/BaseDecoder.BGsRDny7.js.map +0 -1
package/dist/Meframe.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';
2
- import { CompositionModelData, CompositionPatch, TimeUs } from './model/types';
2
+ import { CompositionModelData, CompositionPatch, SetCompositionModelOptions, TimeUs } from './model/types';
3
3
  import { PreviewHandle } from './controllers/types';
4
4
  import { CompositionModel } from './model/CompositionModel';
5
5
  import { PluginManager } from './plugins/PluginManager';
@@ -34,7 +34,7 @@ export declare class Meframe {
34
34
  /**
35
35
  * Set the composition model
36
36
  */
37
- setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void>;
37
+ setCompositionModel(model: CompositionModel | CompositionModelData, options?: SetCompositionModelOptions): Promise<void>;
38
38
  /**
39
39
  * Apply a patch to the composition model
40
40
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"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"}
package/dist/Meframe.js CHANGED
@@ -70,10 +70,16 @@ class Meframe {
70
70
  /**
71
71
  * Set the composition model
72
72
  */
73
- async setCompositionModel(model) {
73
+ async setCompositionModel(model, options) {
74
74
  this.ensureNotDestroyed();
75
+ const { needExport = true } = options || {};
76
+ if (needExport) {
77
+ this.preRenderService?.start();
78
+ } else {
79
+ this.preRenderService?.stop();
80
+ }
75
81
  const compositionModel = model instanceof CompositionModel ? model : new CompositionModel(model);
76
- await this.orchestrator.setCompositionModel(compositionModel);
82
+ await this.orchestrator.setCompositionModel(compositionModel, options);
77
83
  this.model = compositionModel;
78
84
  this.setState("ready");
79
85
  this.eventBus.emit(MeframeEvent.Ready, {
@@ -130,7 +136,7 @@ class Meframe {
130
136
  this.playbackController.on(MeframeEvent.PlaybackStop, () => {
131
137
  this.preRenderService?.setPlaybackActive(false);
132
138
  });
133
- this.playbackController?.renderCurrentFrame(0);
139
+ this.orchestrator.renderFrame(0);
134
140
  return this.playbackController;
135
141
  }
136
142
  /**
@@ -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 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
+ {"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 {\n CompositionModelData,\n CompositionPatch,\n SetCompositionModelOptions,\n TimeUs,\n} 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(\n model: CompositionModel | CompositionModelData,\n options?: SetCompositionModelOptions\n ): Promise<void> {\n this.ensureNotDestroyed();\n\n const { needExport = true } = options || {};\n\n if (needExport) {\n this.preRenderService?.start();\n } else {\n this.preRenderService?.stop();\n }\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, options);\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 this.orchestrator.renderFrame(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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"}
@@ -93,6 +93,8 @@ export declare class CacheManager {
93
93
  timeoutMs?: number;
94
94
  startTimeUs?: TimeUs;
95
95
  }): Promise<boolean>;
96
+ clearL1Cache(): void;
97
+ clearL2Cache(): Promise<void>;
96
98
  clear(): Promise<void>;
97
99
  getMetadata(): {
98
100
  l1: import('./l1/VideoL1Cache').L1CacheMetadata;
@@ -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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"}
@@ -169,7 +169,7 @@ class CacheManager {
169
169
  return this.audioL1Cache.hasClipPCM(clipId);
170
170
  }
171
171
  resetAudioCache() {
172
- this.audioL1Cache.reset();
172
+ this.audioL1Cache.clear();
173
173
  }
174
174
  clearClipAudioData(clipId) {
175
175
  this.audioL1Cache.clearClipPCM(clipId);
@@ -306,10 +306,17 @@ class CacheManager {
306
306
  }
307
307
  });
308
308
  }
309
- async clear() {
309
+ clearL1Cache() {
310
310
  this.videoL1Cache.clear();
311
+ this.audioL1Cache.clear();
312
+ }
313
+ async clearL2Cache() {
311
314
  await this.l2Cache.clear();
312
315
  }
316
+ async clear() {
317
+ this.clearL1Cache();
318
+ await this.clearL2Cache();
319
+ }
313
320
  getMetadata() {
314
321
  return {
315
322
  l1: this.videoL1Cache.getMetadata(),
@@ -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 './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>,\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;\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId\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 console.error('[CacheManager] receiveComposedFrames error: ', 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 }\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,QAAQ;AACd,cAAM,YAAY,MAAM,aAAa;AAErC,cAAM,UAAU,KAAK,aAAa;AAAA,UAChC;AAAA,UACA,OAAO;AAAA,UACP;AAAA,UACA,OAAO;AAAA,QAAA;AAIT,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,cAAQ,MAAM,gDAAgD,KAAK;AAAA,IACrE;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;AAAA,IACF;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
+ {"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>,\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;\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId\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 console.error('[CacheManager] receiveComposedFrames error: ', 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 }\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.clear();\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 clearL1Cache(): void {\n this.videoL1Cache.clear();\n this.audioL1Cache.clear();\n }\n\n async clearL2Cache(): Promise<void> {\n await this.l2Cache.clear();\n }\n\n async clear(): Promise<void> {\n this.clearL1Cache();\n await this.clearL2Cache();\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":"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"}
@@ -25,7 +25,7 @@ export declare class AudioL1Cache {
25
25
  metadata: AudioMetadata;
26
26
  }) | null;
27
27
  flush(): void;
28
- reset(): void;
28
+ clear(): void;
29
29
  setPlaybackRate(_rate: number): void;
30
30
  setVolume(volume: number): void;
31
31
  setMute(muted: boolean): void;
@@ -295,7 +295,7 @@ class AudioL1Cache {
295
295
  flush() {
296
296
  this.slots = [];
297
297
  }
298
- reset() {
298
+ clear() {
299
299
  this.flush();
300
300
  this.clipPCM.clear();
301
301
  this.metadata = { sampleRate: 48e3, numberOfChannels: 2 };
@@ -311,7 +311,7 @@ class AudioL1Cache {
311
311
  this.muted = muted;
312
312
  }
313
313
  dispose() {
314
- this.reset();
314
+ this.clear();
315
315
  }
316
316
  applyGain(planes) {
317
317
  if (this.muted || this.volume === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { AudioMetadata, AudioSlot } from './types';\nimport type { TimeUs } from '../../model/types';\n\ninterface PCMClipEntry {\n clipId: string;\n sampleRate: number;\n numberOfChannels: number;\n planes: Float32Array[];\n startUs: TimeUs;\n durationUs: TimeUs;\n lastAccessedAt: number;\n}\n\nexport class AudioL1Cache {\n private slots: AudioSlot[] = [];\n private clipPCM = new Map<string, PCMClipEntry>();\n private metadata: AudioMetadata = { sampleRate: 48_000, numberOfChannels: 2 };\n private volume = 1;\n private muted = false;\n private maxClips = 20;\n\n attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void {\n this.metadata = metadata;\n const reader = stream.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.addAudio(value);\n await pump();\n };\n\n pump().catch((error) => {\n console.error('[AudioL1Cache] stream error', error);\n reader.releaseLock();\n });\n }\n\n putClipAudioData(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? this.metadata.sampleRate;\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n const planes = this.extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n let entry = this.clipPCM.get(clipId);\n if (!entry) {\n entry = {\n clipId,\n sampleRate,\n numberOfChannels,\n planes: Array.from({ length: numberOfChannels }, () => new Float32Array(0)),\n startUs: 0, // Use clip-relative time (0-based), same as video cache\n durationUs: clipDurationUs,\n lastAccessedAt: Date.now(),\n };\n this.clipPCM.set(clipId, entry);\n\n if (this.clipPCM.size > this.maxClips) {\n this.evictLRU();\n }\n }\n\n // Calculate how many frames we should store based on clip duration\n const maxFrames = Math.ceil((clipDurationUs / 1_000_000) * sampleRate);\n const currentFrames = entry.planes[0]?.length ?? 0;\n\n // If we've already stored enough frames for the clip duration, skip\n if (currentFrames >= maxFrames) {\n return;\n }\n\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const existingPlane = entry.planes[channel];\n const newPlane = planes[channel];\n if (!existingPlane || !newPlane) continue;\n\n // Calculate how many frames from newPlane we should actually append\n const framesToAppend = Math.min(newPlane.length, maxFrames - currentFrames);\n\n if (framesToAppend <= 0) {\n continue;\n }\n\n const combined = new Float32Array(existingPlane.length + framesToAppend);\n combined.set(existingPlane, 0);\n combined.set(newPlane.subarray(0, framesToAppend), existingPlane.length);\n entry.planes[channel] = combined;\n }\n\n entry.lastAccessedAt = Date.now();\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n entry.lastAccessedAt = Date.now();\n\n const offsetUs = Math.max(0, startUs - entry.startUs);\n\n // Calculate actual stored duration based on actual frames, not expected durationUs\n const actualStoredFrames = entry.planes[0]?.length ?? 0;\n const actualStoredUs = (actualStoredFrames / entry.sampleRate) * 1_000_000;\n const availableDurationUs = Math.max(0, actualStoredUs - offsetUs);\n\n const requestedDurationUs = endUs - startUs;\n const durationUs = Math.min(requestedDurationUs, availableDurationUs);\n\n if (durationUs <= 0) {\n return null;\n }\n\n const offsetFrames = Math.floor((offsetUs / 1_000_000) * entry.sampleRate);\n const frameCount = Math.ceil((durationUs / 1_000_000) * entry.sampleRate);\n\n const result: Float32Array[] = [];\n for (let channel = 0; channel < entry.numberOfChannels; channel++) {\n const plane = entry.planes[channel];\n if (!plane) {\n result.push(new Float32Array(frameCount));\n continue;\n }\n\n const channelData = new Float32Array(frameCount);\n const copyLength = Math.min(frameCount, plane.length - offsetFrames);\n for (let i = 0; i < copyLength; i++) {\n channelData[i] = plane[offsetFrames + i] ?? 0;\n }\n result.push(channelData);\n }\n\n return result;\n }\n\n getPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n return {\n planes,\n sampleRate: entry.sampleRate,\n numberOfChannels: entry.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.clipPCM.has(clipId);\n }\n\n clearClipPCM(clipId: string): void {\n this.clipPCM.delete(clipId);\n }\n\n private evictLRU(): void {\n let oldestClipId: string | null = null;\n let oldestTime = Date.now();\n\n for (const [clipId, entry] of this.clipPCM) {\n if (entry.lastAccessedAt < oldestTime) {\n oldestTime = entry.lastAccessedAt;\n oldestClipId = clipId;\n }\n }\n\n if (oldestClipId) {\n this.clipPCM.delete(oldestClipId);\n }\n }\n\n private extractPlanesFromAudioData(\n audioData: AudioData,\n numberOfChannels: number,\n numberOfFrames: number\n ): Float32Array[] {\n const planes: Float32Array[] = Array.from(\n { length: numberOfChannels },\n () => new Float32Array(numberOfFrames)\n );\n\n const toFloat = (value: number): number => value / 32768;\n\n const fillInterleaved = (format: 'f32' | 's16'): boolean => {\n const samples =\n format === 'f32'\n ? new Float32Array(numberOfFrames * numberOfChannels)\n : new Int16Array(numberOfFrames * numberOfChannels);\n\n try {\n audioData.copyTo(samples, { format, planeIndex: 0 });\n } catch {\n return false;\n }\n\n for (let frame = 0; frame < numberOfFrames; frame += 1) {\n const offset = frame * numberOfChannels;\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n if (format === 'f32') {\n plane[frame] = (samples as Float32Array)[offset + channel] ?? 0;\n } else {\n plane[frame] = toFloat((samples as Int16Array)[offset + channel] ?? 0);\n }\n }\n }\n\n return true;\n };\n\n const fillPlanar = (format: 'f32-planar' | 's16-planar'): boolean => {\n try {\n if (format === 'f32-planar') {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel, format: 'f32-planar' });\n }\n return true;\n }\n\n const tmp = new Int16Array(numberOfFrames);\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(tmp, { planeIndex: channel, format: 's16-planar' as any });\n for (let i = 0; i < numberOfFrames; i += 1) {\n plane[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const fillFallback = (): boolean => {\n try {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel });\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const reportedFormat = (audioData as any).format as string | undefined;\n const attempts: Array<() => boolean> = [];\n const scheduled = new Set<string>();\n\n const scheduleAttempt = (token: string, attempt: () => boolean): void => {\n if (!scheduled.has(token)) {\n scheduled.add(token);\n attempts.push(attempt);\n }\n };\n\n if (reportedFormat) {\n switch (reportedFormat) {\n case 'f32':\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n break;\n case 's16':\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n break;\n case 'f32-planar':\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n break;\n case 's16-planar':\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n break;\n default:\n break;\n }\n }\n\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n\n let filled = false;\n for (const attempt of attempts) {\n if (attempt()) {\n filled = true;\n break;\n }\n }\n\n if (!filled) {\n filled = fillFallback();\n }\n\n if (!filled) {\n throw new Error('AudioL1Cache: unsupported AudioData format');\n }\n\n return planes;\n }\n\n addAudio(audio: AudioData): void {\n const numberOfChannels = audio.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audio.numberOfFrames ?? 0;\n\n if (!numberOfChannels || !numberOfFrames) {\n audio.close();\n return;\n }\n\n if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {\n this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };\n }\n\n if (numberOfChannels !== this.metadata.numberOfChannels) {\n this.metadata = { ...this.metadata, numberOfChannels };\n }\n\n const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);\n\n this.slots.push({\n timestampUs: audio.timestamp ?? 0,\n durationUs:\n audio.duration ?? Math.round((numberOfFrames / this.metadata.sampleRate) * 1_000_000),\n planes,\n });\n\n audio.close();\n }\n\n getClosest(timeUs: number): (AudioSlot & { metadata: AudioMetadata }) | null {\n if (this.slots.length === 0) {\n return null;\n }\n\n let closest: AudioSlot | null = null;\n let minDelta = Number.MAX_SAFE_INTEGER;\n\n for (const slot of this.slots) {\n const start = slot.timestampUs;\n const end = start + slot.durationUs;\n if (timeUs >= start && timeUs <= end) {\n closest = slot;\n break;\n }\n\n const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));\n if (delta < minDelta) {\n closest = slot;\n minDelta = delta;\n }\n }\n\n if (!closest) {\n return null;\n }\n\n return {\n ...closest,\n planes: this.applyGain(closest.planes),\n metadata: this.metadata,\n };\n }\n\n flush(): void {\n this.slots = [];\n }\n\n reset(): void {\n this.flush();\n this.clipPCM.clear();\n this.metadata = { sampleRate: 48_000, numberOfChannels: 2 };\n this.volume = 1;\n this.muted = false;\n }\n\n setPlaybackRate(_rate: number): void {\n // Reserved for future use\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n }\n\n setMute(muted: boolean): void {\n this.muted = muted;\n }\n\n dispose(): void {\n this.reset();\n }\n\n private applyGain(planes: Float32Array[]): Float32Array[] {\n if (this.muted || this.volume === 0) {\n return planes.map((plane) => new Float32Array(plane.length));\n }\n\n if (this.volume === 1) {\n return planes.map((plane) => plane.slice());\n }\n\n return planes.map((plane) => {\n const scaled = new Float32Array(plane.length);\n for (let i = 0; i < plane.length; i += 1) {\n scaled[i] = (plane[i] ?? 0) * this.volume;\n }\n return scaled;\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { AudioMetadata, AudioSlot } from './types';\nimport type { TimeUs } from '../../model/types';\n\ninterface PCMClipEntry {\n clipId: string;\n sampleRate: number;\n numberOfChannels: number;\n planes: Float32Array[];\n startUs: TimeUs;\n durationUs: TimeUs;\n lastAccessedAt: number;\n}\n\nexport class AudioL1Cache {\n private slots: AudioSlot[] = [];\n private clipPCM = new Map<string, PCMClipEntry>();\n private metadata: AudioMetadata = { sampleRate: 48_000, numberOfChannels: 2 };\n private volume = 1;\n private muted = false;\n private maxClips = 20;\n\n attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void {\n this.metadata = metadata;\n const reader = stream.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.addAudio(value);\n await pump();\n };\n\n pump().catch((error) => {\n console.error('[AudioL1Cache] stream error', error);\n reader.releaseLock();\n });\n }\n\n putClipAudioData(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? this.metadata.sampleRate;\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n const planes = this.extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n let entry = this.clipPCM.get(clipId);\n if (!entry) {\n entry = {\n clipId,\n sampleRate,\n numberOfChannels,\n planes: Array.from({ length: numberOfChannels }, () => new Float32Array(0)),\n startUs: 0, // Use clip-relative time (0-based), same as video cache\n durationUs: clipDurationUs,\n lastAccessedAt: Date.now(),\n };\n this.clipPCM.set(clipId, entry);\n\n if (this.clipPCM.size > this.maxClips) {\n this.evictLRU();\n }\n }\n\n // Calculate how many frames we should store based on clip duration\n const maxFrames = Math.ceil((clipDurationUs / 1_000_000) * sampleRate);\n const currentFrames = entry.planes[0]?.length ?? 0;\n\n // If we've already stored enough frames for the clip duration, skip\n if (currentFrames >= maxFrames) {\n return;\n }\n\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const existingPlane = entry.planes[channel];\n const newPlane = planes[channel];\n if (!existingPlane || !newPlane) continue;\n\n // Calculate how many frames from newPlane we should actually append\n const framesToAppend = Math.min(newPlane.length, maxFrames - currentFrames);\n\n if (framesToAppend <= 0) {\n continue;\n }\n\n const combined = new Float32Array(existingPlane.length + framesToAppend);\n combined.set(existingPlane, 0);\n combined.set(newPlane.subarray(0, framesToAppend), existingPlane.length);\n entry.planes[channel] = combined;\n }\n\n entry.lastAccessedAt = Date.now();\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n entry.lastAccessedAt = Date.now();\n\n const offsetUs = Math.max(0, startUs - entry.startUs);\n\n // Calculate actual stored duration based on actual frames, not expected durationUs\n const actualStoredFrames = entry.planes[0]?.length ?? 0;\n const actualStoredUs = (actualStoredFrames / entry.sampleRate) * 1_000_000;\n const availableDurationUs = Math.max(0, actualStoredUs - offsetUs);\n\n const requestedDurationUs = endUs - startUs;\n const durationUs = Math.min(requestedDurationUs, availableDurationUs);\n\n if (durationUs <= 0) {\n return null;\n }\n\n const offsetFrames = Math.floor((offsetUs / 1_000_000) * entry.sampleRate);\n const frameCount = Math.ceil((durationUs / 1_000_000) * entry.sampleRate);\n\n const result: Float32Array[] = [];\n for (let channel = 0; channel < entry.numberOfChannels; channel++) {\n const plane = entry.planes[channel];\n if (!plane) {\n result.push(new Float32Array(frameCount));\n continue;\n }\n\n const channelData = new Float32Array(frameCount);\n const copyLength = Math.min(frameCount, plane.length - offsetFrames);\n for (let i = 0; i < copyLength; i++) {\n channelData[i] = plane[offsetFrames + i] ?? 0;\n }\n result.push(channelData);\n }\n\n return result;\n }\n\n getPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n return {\n planes,\n sampleRate: entry.sampleRate,\n numberOfChannels: entry.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.clipPCM.has(clipId);\n }\n\n clearClipPCM(clipId: string): void {\n this.clipPCM.delete(clipId);\n }\n\n private evictLRU(): void {\n let oldestClipId: string | null = null;\n let oldestTime = Date.now();\n\n for (const [clipId, entry] of this.clipPCM) {\n if (entry.lastAccessedAt < oldestTime) {\n oldestTime = entry.lastAccessedAt;\n oldestClipId = clipId;\n }\n }\n\n if (oldestClipId) {\n this.clipPCM.delete(oldestClipId);\n }\n }\n\n private extractPlanesFromAudioData(\n audioData: AudioData,\n numberOfChannels: number,\n numberOfFrames: number\n ): Float32Array[] {\n const planes: Float32Array[] = Array.from(\n { length: numberOfChannels },\n () => new Float32Array(numberOfFrames)\n );\n\n const toFloat = (value: number): number => value / 32768;\n\n const fillInterleaved = (format: 'f32' | 's16'): boolean => {\n const samples =\n format === 'f32'\n ? new Float32Array(numberOfFrames * numberOfChannels)\n : new Int16Array(numberOfFrames * numberOfChannels);\n\n try {\n audioData.copyTo(samples, { format, planeIndex: 0 });\n } catch {\n return false;\n }\n\n for (let frame = 0; frame < numberOfFrames; frame += 1) {\n const offset = frame * numberOfChannels;\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n if (format === 'f32') {\n plane[frame] = (samples as Float32Array)[offset + channel] ?? 0;\n } else {\n plane[frame] = toFloat((samples as Int16Array)[offset + channel] ?? 0);\n }\n }\n }\n\n return true;\n };\n\n const fillPlanar = (format: 'f32-planar' | 's16-planar'): boolean => {\n try {\n if (format === 'f32-planar') {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel, format: 'f32-planar' });\n }\n return true;\n }\n\n const tmp = new Int16Array(numberOfFrames);\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(tmp, { planeIndex: channel, format: 's16-planar' as any });\n for (let i = 0; i < numberOfFrames; i += 1) {\n plane[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const fillFallback = (): boolean => {\n try {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel });\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const reportedFormat = (audioData as any).format as string | undefined;\n const attempts: Array<() => boolean> = [];\n const scheduled = new Set<string>();\n\n const scheduleAttempt = (token: string, attempt: () => boolean): void => {\n if (!scheduled.has(token)) {\n scheduled.add(token);\n attempts.push(attempt);\n }\n };\n\n if (reportedFormat) {\n switch (reportedFormat) {\n case 'f32':\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n break;\n case 's16':\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n break;\n case 'f32-planar':\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n break;\n case 's16-planar':\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n break;\n default:\n break;\n }\n }\n\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n\n let filled = false;\n for (const attempt of attempts) {\n if (attempt()) {\n filled = true;\n break;\n }\n }\n\n if (!filled) {\n filled = fillFallback();\n }\n\n if (!filled) {\n throw new Error('AudioL1Cache: unsupported AudioData format');\n }\n\n return planes;\n }\n\n addAudio(audio: AudioData): void {\n const numberOfChannels = audio.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audio.numberOfFrames ?? 0;\n\n if (!numberOfChannels || !numberOfFrames) {\n audio.close();\n return;\n }\n\n if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {\n this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };\n }\n\n if (numberOfChannels !== this.metadata.numberOfChannels) {\n this.metadata = { ...this.metadata, numberOfChannels };\n }\n\n const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);\n\n this.slots.push({\n timestampUs: audio.timestamp ?? 0,\n durationUs:\n audio.duration ?? Math.round((numberOfFrames / this.metadata.sampleRate) * 1_000_000),\n planes,\n });\n\n audio.close();\n }\n\n getClosest(timeUs: number): (AudioSlot & { metadata: AudioMetadata }) | null {\n if (this.slots.length === 0) {\n return null;\n }\n\n let closest: AudioSlot | null = null;\n let minDelta = Number.MAX_SAFE_INTEGER;\n\n for (const slot of this.slots) {\n const start = slot.timestampUs;\n const end = start + slot.durationUs;\n if (timeUs >= start && timeUs <= end) {\n closest = slot;\n break;\n }\n\n const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));\n if (delta < minDelta) {\n closest = slot;\n minDelta = delta;\n }\n }\n\n if (!closest) {\n return null;\n }\n\n return {\n ...closest,\n planes: this.applyGain(closest.planes),\n metadata: this.metadata,\n };\n }\n\n flush(): void {\n this.slots = [];\n }\n\n clear(): void {\n this.flush();\n this.clipPCM.clear();\n this.metadata = { sampleRate: 48_000, numberOfChannels: 2 };\n this.volume = 1;\n this.muted = false;\n }\n\n setPlaybackRate(_rate: number): void {\n // Reserved for future use\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n }\n\n setMute(muted: boolean): void {\n this.muted = muted;\n }\n\n dispose(): void {\n this.clear();\n }\n\n private applyGain(planes: Float32Array[]): Float32Array[] {\n if (this.muted || this.volume === 0) {\n return planes.map((plane) => new Float32Array(plane.length));\n }\n\n if (this.volume === 1) {\n return planes.map((plane) => plane.slice());\n }\n\n return planes.map((plane) => {\n const scaled = new Float32Array(plane.length);\n for (let i = 0; i < plane.length; i += 1) {\n scaled[i] = (plane[i] ?? 0) * this.volume;\n }\n return scaled;\n });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -21,6 +21,9 @@ class PreRenderService {
21
21
  this.orchestrator = orchestrator;
22
22
  }
23
23
  start() {
24
+ if (this._isRunning) return;
25
+ this._isRunning = true;
26
+ this.scheduleNextRender();
24
27
  }
25
28
  async stop() {
26
29
  this._isRunning = false;
@@ -1 +1 @@
1
- {"version":3,"file":"PreRenderService.js","sources":["../../src/controllers/PreRenderService.ts"],"sourcesContent":["import type { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';\nimport type { TimeUs } from '../model/types';\nimport type { Orchestrator } from '../orchestrator';\nimport { hasResourceId } from '../model/types';\n\n/**\n * PreRenderService: Background pre-render for filling L2 cache\n *\n * Dual-mode strategy:\n * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview\n * 2. When idle: Continue rendering subsequent clips for L2 export cache\n *\n * Goals:\n * - Maintain smooth preview (2-Clip L1 cache)\n * - Build complete L2 cache for fast export\n * - Operate in background without blocking\n */\nexport class PreRenderService implements IPreRenderService {\n private orchestrator: Orchestrator;\n private _isRunning = false;\n private _isPaused = false;\n private renderLoopId: number | null = null;\n private _framesRendered = 0;\n private _currentTimeUs: TimeUs = 0;\n private isPlaybackActive = false;\n private strategy: RenderStrategy = {\n direction: 'forward',\n lookahead: 3_000_000,\n lookbehind: 2_000_000,\n keyframesOnly: false,\n };\n\n private isRendering = false;\n private highPriorityMode = false;\n private exportWaiters: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];\n private progressCallback: ((completed: number, total: number) => void) | null = null;\n\n constructor(orchestrator: Orchestrator, _eventBus: any) {\n this.orchestrator = orchestrator;\n }\n\n start(): void {\n // if (this._isRunning) return;\n // this._isRunning = true;\n // this.scheduleNextRender();\n }\n\n async stop(): Promise<void> {\n this._isRunning = false;\n\n if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n }\n\n pause(): void {\n this._isPaused = true;\n }\n\n resume(): void {\n this._isPaused = false;\n if (this._isRunning && !this.renderLoopId) {\n this.scheduleNextRender();\n }\n }\n\n setWindow(size: TimeUs): void {\n this.strategy.lookahead = size;\n }\n\n setStrategy(strategy: Partial<RenderStrategy>): void {\n this.strategy = { ...this.strategy, ...strategy };\n }\n\n setPriority(_priority: 'low' | 'normal' | 'high'): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n updatePlaybackTime(timeUs: TimeUs): void {\n this._currentTimeUs = timeUs;\n }\n\n setPlaybackActive(active: boolean): void {\n this.isPlaybackActive = active;\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n get queueSize(): number {\n return 0;\n }\n\n get status(): PreRenderStatus {\n return {\n isRunning: this._isRunning,\n framesRendered: this._framesRendered,\n currentTimeUs: this._currentTimeUs,\n };\n }\n\n private scheduleNextRender(): void {\n if (!this._isRunning) return;\n\n if (this.highPriorityMode) {\n // High priority: use requestAnimationFrame for faster rendering\n this.renderLoopId = requestAnimationFrame(async () => {\n await this.renderTick();\n this.scheduleNextRender();\n }) as any;\n } else {\n // Normal priority: use requestIdleCallback\n this.renderLoopId = requestIdleCallback(\n async () => {\n await this.renderTick();\n this.scheduleNextRender();\n },\n { timeout: 100 }\n );\n }\n }\n\n private async renderTick(): Promise<void> {\n if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {\n return;\n }\n\n const model = this.orchestrator.compositionModel;\n\n if (this.isPlaybackActive) {\n // Playback mode: Do nothing, PlaybackController manages 2-clip window\n return;\n }\n\n // If already rendering, skip this tick\n if (this.isRendering) {\n return;\n }\n\n // Idle mode: Continue rendering clips beyond 2-Clip window for L2 cache\n // Only process main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n if (allClips.length === 0) {\n return;\n }\n // Find next clip to render, skip if resource is loading\n let clipToRender = null;\n let completedCount = 0;\n\n for (const clip of allClips) {\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n completedCount++;\n continue;\n }\n\n // Check if resource is being loaded by preview channel\n if (\n hasResourceId(clip) &&\n this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)\n ) {\n continue; // Preview priority: skip this clip, will retry in next tick\n }\n\n // Found available clip\n clipToRender = clip;\n break;\n }\n\n // Handle completion\n if (!clipToRender) {\n if (completedCount < allClips.length) {\n // All unprocessed clips are temporarily skipped due to resource conflicts\n // Continue loop, will retry in next tick\n return;\n }\n\n // Really done: all clips are in L2\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.resolve());\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n\n void this.stop();\n return;\n }\n\n // Render this clip completely with concurrency protection\n this.isRendering = true;\n try {\n const rendered = await this.renderClipToL2(clipToRender.id);\n if (!rendered) {\n // Resource conflict: don't mark as processed, will retry in next tick\n return;\n }\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n // completedCount from loop + 1 for the clip just rendered\n this.progressCallback(completedCount + 1, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n\n // Notify waiters of error in high priority mode\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.reject(error as Error));\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Render a complete clip to L2 cache\n * Creates dedicated pipeline that bypasses L1 window management\n */\n private async renderClipToL2(clipId: string): Promise<boolean> {\n const model = this.orchestrator.compositionModel;\n if (!model) return false;\n\n const clip = model.findClip(clipId);\n if (!clip) return false;\n\n // Start L2 rendering\n const rendered = await this.orchestrator.renderClipForL2(clipId);\n\n // Update counter\n const fps = model.fps || 30;\n const expectedFrames = Math.ceil((clip.durationUs / 1_000_000) * fps);\n this._framesRendered += expectedFrames;\n\n return rendered;\n }\n\n /**\n * Ensure all clips are in L2 cache\n * Switches to high priority mode and waits for completion\n */\n async ensureClipsInL2(options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }): Promise<void> {\n // Switch to high priority mode and wait for all clips to complete\n return new Promise<void>((resolve, reject) => {\n this.highPriorityMode = true;\n this.progressCallback = options?.onProgress || null;\n this.exportWaiters.push({ resolve, reject });\n\n // Cancel current idle callback and restart with RAF\n this.stop();\n this.start();\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PreRenderService.js","sources":["../../src/controllers/PreRenderService.ts"],"sourcesContent":["import type { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';\nimport type { TimeUs } from '../model/types';\nimport type { Orchestrator } from '../orchestrator';\nimport { hasResourceId } from '../model/types';\n\n/**\n * PreRenderService: Background pre-render for filling L2 cache\n *\n * Dual-mode strategy:\n * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview\n * 2. When idle: Continue rendering subsequent clips for L2 export cache\n *\n * Goals:\n * - Maintain smooth preview (2-Clip L1 cache)\n * - Build complete L2 cache for fast export\n * - Operate in background without blocking\n */\nexport class PreRenderService implements IPreRenderService {\n private orchestrator: Orchestrator;\n private _isRunning = false;\n private _isPaused = false;\n private renderLoopId: number | null = null;\n private _framesRendered = 0;\n private _currentTimeUs: TimeUs = 0;\n private isPlaybackActive = false;\n private strategy: RenderStrategy = {\n direction: 'forward',\n lookahead: 3_000_000,\n lookbehind: 2_000_000,\n keyframesOnly: false,\n };\n\n private isRendering = false;\n private highPriorityMode = false;\n private exportWaiters: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];\n private progressCallback: ((completed: number, total: number) => void) | null = null;\n\n constructor(orchestrator: Orchestrator, _eventBus: any) {\n this.orchestrator = orchestrator;\n }\n\n start(): void {\n if (this._isRunning) return;\n this._isRunning = true;\n this.scheduleNextRender();\n }\n\n async stop(): Promise<void> {\n this._isRunning = false;\n\n if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n }\n\n pause(): void {\n this._isPaused = true;\n }\n\n resume(): void {\n this._isPaused = false;\n if (this._isRunning && !this.renderLoopId) {\n this.scheduleNextRender();\n }\n }\n\n setWindow(size: TimeUs): void {\n this.strategy.lookahead = size;\n }\n\n setStrategy(strategy: Partial<RenderStrategy>): void {\n this.strategy = { ...this.strategy, ...strategy };\n }\n\n setPriority(_priority: 'low' | 'normal' | 'high'): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n updatePlaybackTime(timeUs: TimeUs): void {\n this._currentTimeUs = timeUs;\n }\n\n setPlaybackActive(active: boolean): void {\n this.isPlaybackActive = active;\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n get queueSize(): number {\n return 0;\n }\n\n get status(): PreRenderStatus {\n return {\n isRunning: this._isRunning,\n framesRendered: this._framesRendered,\n currentTimeUs: this._currentTimeUs,\n };\n }\n\n private scheduleNextRender(): void {\n if (!this._isRunning) return;\n\n if (this.highPriorityMode) {\n // High priority: use requestAnimationFrame for faster rendering\n this.renderLoopId = requestAnimationFrame(async () => {\n await this.renderTick();\n this.scheduleNextRender();\n }) as any;\n } else {\n // Normal priority: use requestIdleCallback\n this.renderLoopId = requestIdleCallback(\n async () => {\n await this.renderTick();\n this.scheduleNextRender();\n },\n { timeout: 100 }\n );\n }\n }\n\n private async renderTick(): Promise<void> {\n if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {\n return;\n }\n\n const model = this.orchestrator.compositionModel;\n\n if (this.isPlaybackActive) {\n // Playback mode: Do nothing, PlaybackController manages 2-clip window\n return;\n }\n\n // If already rendering, skip this tick\n if (this.isRendering) {\n return;\n }\n\n // Idle mode: Continue rendering clips beyond 2-Clip window for L2 cache\n // Only process main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n if (allClips.length === 0) {\n return;\n }\n // Find next clip to render, skip if resource is loading\n let clipToRender = null;\n let completedCount = 0;\n\n for (const clip of allClips) {\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n completedCount++;\n continue;\n }\n\n // Check if resource is being loaded by preview channel\n if (\n hasResourceId(clip) &&\n this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)\n ) {\n continue; // Preview priority: skip this clip, will retry in next tick\n }\n\n // Found available clip\n clipToRender = clip;\n break;\n }\n\n // Handle completion\n if (!clipToRender) {\n if (completedCount < allClips.length) {\n // All unprocessed clips are temporarily skipped due to resource conflicts\n // Continue loop, will retry in next tick\n return;\n }\n\n // Really done: all clips are in L2\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.resolve());\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n\n void this.stop();\n return;\n }\n\n // Render this clip completely with concurrency protection\n this.isRendering = true;\n try {\n const rendered = await this.renderClipToL2(clipToRender.id);\n if (!rendered) {\n // Resource conflict: don't mark as processed, will retry in next tick\n return;\n }\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n // completedCount from loop + 1 for the clip just rendered\n this.progressCallback(completedCount + 1, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n\n // Notify waiters of error in high priority mode\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.reject(error as Error));\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Render a complete clip to L2 cache\n * Creates dedicated pipeline that bypasses L1 window management\n */\n private async renderClipToL2(clipId: string): Promise<boolean> {\n const model = this.orchestrator.compositionModel;\n if (!model) return false;\n\n const clip = model.findClip(clipId);\n if (!clip) return false;\n\n // Start L2 rendering\n const rendered = await this.orchestrator.renderClipForL2(clipId);\n\n // Update counter\n const fps = model.fps || 30;\n const expectedFrames = Math.ceil((clip.durationUs / 1_000_000) * fps);\n this._framesRendered += expectedFrames;\n\n return rendered;\n }\n\n /**\n * Ensure all clips are in L2 cache\n * Switches to high priority mode and waits for completion\n */\n async ensureClipsInL2(options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }): Promise<void> {\n // Switch to high priority mode and wait for all clips to complete\n return new Promise<void>((resolve, reject) => {\n this.highPriorityMode = true;\n this.progressCallback = options?.onProgress || null;\n this.exportWaiters.push({ resolve, reject });\n\n // Cancel current idle callback and restart with RAF\n this.stop();\n this.start();\n });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -50,6 +50,7 @@ export declare class CompositionModel {
50
50
  incremental?: boolean;
51
51
  trackId?: string;
52
52
  clipId?: string;
53
+ clip?: Clip;
53
54
  operation?: 'add' | 'update' | 'remove';
54
55
  }): void;
55
56
  private fullRebuildIndexes;
@@ -1 +1 @@
1
- {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"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"}
@@ -174,7 +174,7 @@ ${errors.map((e) => `${e.path}: ${e.message}`).join("\n")}`
174
174
  const track = options?.trackId ? this.findTrack(options.trackId) : void 0;
175
175
  const isAttachmentTrack = track && track.kind !== "video" && track.kind !== "audio";
176
176
  if (options?.incremental && !isAttachmentTrack && options.clipId && options.operation) {
177
- const clip = this.clipMap.get(options.clipId);
177
+ const clip = options.clip ?? this.clipMap.get(options.clipId);
178
178
  if (options.operation === "add" && clip) {
179
179
  this.addClipToIndexes(clip);
180
180
  } else if (options.operation === "remove" && clip) {