@meframe/core 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +2 -2
- package/dist/Meframe.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.d.ts +1 -4
- package/dist/controllers/PreRenderService.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.js +7 -40
- package/dist/controllers/PreRenderService.js.map +1 -1
- package/dist/controllers/types.d.ts +0 -4
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +2 -0
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/patch.js +1 -25
- package/dist/model/patch.js.map +1 -1
- package/dist/model/types.d.ts +18 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +1 -0
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +36 -10
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +0 -1
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +2 -1
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +0 -2
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts +4 -7
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +1 -9
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/compose/VideoComposer.d.ts +1 -0
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/font-system/FontManager.d.ts +11 -0
- package/dist/stages/compose/font-system/FontManager.d.ts.map +1 -0
- package/dist/stages/compose/font-system/FontManager.js +69 -0
- package/dist/stages/compose/font-system/FontManager.js.map +1 -0
- package/dist/stages/compose/font-system/font-templates.d.ts +12 -0
- package/dist/stages/compose/font-system/font-templates.d.ts.map +1 -0
- package/dist/stages/compose/font-system/font-templates.js +384 -0
- package/dist/stages/compose/font-system/font-templates.js.map +1 -0
- package/dist/stages/compose/font-system/index.d.ts +5 -0
- package/dist/stages/compose/font-system/index.d.ts.map +1 -0
- package/dist/stages/compose/font-system/types.d.ts +60 -0
- package/dist/stages/compose/font-system/types.d.ts.map +1 -0
- package/dist/stages/compose/instructions.d.ts +50 -0
- package/dist/stages/compose/instructions.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/animation-utils.d.ts +16 -0
- package/dist/stages/compose/text-renderers/animation-utils.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +5 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +4 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/index.d.ts +6 -0
- package/dist/stages/compose/text-renderers/index.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +4 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +4 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -0
- package/dist/stages/compose/text-utils/index.d.ts +4 -0
- package/dist/stages/compose/text-utils/index.d.ts.map +1 -0
- package/dist/stages/compose/text-utils/locale-detector.d.ts +5 -0
- package/dist/stages/compose/text-utils/locale-detector.d.ts.map +1 -0
- package/dist/stages/compose/text-utils/text-metrics.d.ts +3 -0
- package/dist/stages/compose/text-utils/text-metrics.d.ts.map +1 -0
- package/dist/stages/compose/text-utils/text-wrapper.d.ts +4 -0
- package/dist/stages/compose/text-utils/text-wrapper.d.ts.map +1 -0
- package/dist/stages/compose/types.d.ts +51 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.js +10 -12
- package/dist/stages/mux/MuxManager.js.map +1 -1
- package/dist/workers/MP4Demuxer.js +8 -1
- package/dist/workers/MP4Demuxer.js.map +1 -1
- package/dist/workers/stages/compose/video-compose.worker.js +838 -78
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/stages/demux/aac-esds-extractor.d.ts +0 -7
- package/dist/stages/demux/aac-esds-extractor.d.ts.map +0 -1
package/dist/Meframe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,qBAAa,OAAO;IAClB,wDAAwD;IACxD,KAAK,EAAE,YAAY,CAAU;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2DAA2D;IAC3D,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACtC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAExE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,CAAsC;IAErD,OAAO;IAiBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQjE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,qBAAa,OAAO;IAClB,wDAAwD;IACxD,KAAK,EAAE,YAAY,CAAU;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2DAA2D;IAC3D,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACtC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAExE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,CAAsC;IAErD,OAAO;IAiBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQjE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD;;OAEG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,aAAa;IAoDjB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA4FnD;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAoCF;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB9B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAYhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAWpB"}
|
package/dist/Meframe.js
CHANGED
|
@@ -93,6 +93,7 @@ class Meframe {
|
|
|
93
93
|
}
|
|
94
94
|
await this.orchestrator.applyPatch(patch);
|
|
95
95
|
this.model = this.orchestrator.compositionModel;
|
|
96
|
+
this.preRenderService?.start();
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
98
99
|
* Start preview and return a handle for control
|
|
@@ -151,7 +152,6 @@ class Meframe {
|
|
|
151
152
|
});
|
|
152
153
|
if (!this.preRenderService) {
|
|
153
154
|
this.preRenderService = new PreRenderService(this.orchestrator, this.events);
|
|
154
|
-
this.preRenderService.start();
|
|
155
155
|
}
|
|
156
156
|
const abortController = new AbortController();
|
|
157
157
|
const timeoutMs = 5 * 60 * 1e3;
|
|
@@ -159,7 +159,7 @@ class Meframe {
|
|
|
159
159
|
abortController.abort();
|
|
160
160
|
}, timeoutMs);
|
|
161
161
|
try {
|
|
162
|
-
await this.preRenderService.ensureClipsInL2(
|
|
162
|
+
await this.preRenderService.ensureClipsInL2({
|
|
163
163
|
onProgress: (completed, total) => {
|
|
164
164
|
this.eventBus.emit(MeframeEvent.ExportProgress, {
|
|
165
165
|
progress: completed / total,
|
package/dist/Meframe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n }\n\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 this.preRenderService.start();\n }\n\n // Create AbortController for timeout control\n const abortController = new AbortController();\n const timeoutMs = 5 * 60 * 1000; // 5 minutes\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, timeoutMs);\n\n try {\n // Actively trigger missing clips L2 rendering\n await this.preRenderService.ensureClipsInL2(readiness.missingClips, {\n onProgress: (completed, total) => {\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: completed / total,\n stage: 'preparing',\n message: `Preparing cache: ${completed}/${total} clips`,\n });\n },\n signal: abortController.signal,\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`Export preparation timeout after ${timeoutMs / 1000}s`);\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // 2. Continue normal export flow\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n this.eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n // Delegate to orchestrator\n const blob = await this.orchestrator.export(model, options);\n\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 1,\n });\n\n return blob;\n } catch (error) {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.ExportError, {\n error: error as Error,\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Check export readiness\n * Returns coverage info for L2 cache\n */\n async getExportReadiness(): Promise<{\n ready: boolean;\n totalClips: number;\n cachedClips: number;\n missingClips: string[];\n coverage: number;\n }> {\n if (!this.model) {\n return {\n ready: false,\n totalClips: 0,\n cachedClips: 0,\n missingClips: [],\n coverage: 0,\n };\n }\n\n // Only check main track clips (attachments are already included)\n const mainTrack = this.model.findTrack(this.model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n const missingClips: string[] = [];\n let cachedCount = 0;\n\n for (const clip of allClips) {\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n cachedCount++;\n } else {\n missingClips.push(clip.id);\n }\n }\n\n return {\n ready: missingClips.length === 0,\n totalClips: allClips.length,\n cachedClips: cachedCount,\n missingClips,\n coverage: allClips.length > 0 ? cachedCount / allClips.length : 0,\n };\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clear all L1/L2 cache data\n * Useful for debugging or forcing re-render\n */\n async clearCache(): Promise<void> {\n this.ensureReady();\n\n // Stop playback first\n this.playbackController?.stop();\n\n // Stop pre-render service\n this.preRenderService?.stop();\n\n // Clear cache through CacheManager\n await this.orchestrator.cacheManager.clear();\n\n console.log('[Meframe] Cache cleared successfully');\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n\n this.preRenderService?.start();\n }\n\n /**\n * Start preview and return a handle for control\n */\n startPreview(options?: {\n canvas?: HTMLCanvasElement | OffscreenCanvas;\n startUs?: TimeUs;\n }): PreviewHandle {\n this.ensureReady();\n\n // Use provided canvas or the one from config\n const canvas = options?.canvas || this.canvas;\n if (!canvas) {\n throw new Error('Canvas is required for preview');\n }\n\n // Store canvas for later use\n this.canvas = canvas;\n\n // Create playback controller\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this.orchestrator as any, this.eventBus, {\n canvas,\n startUs: options?.startUs,\n });\n this.playbackController.setAudioSession(this.orchestrator.audioSession);\n }\n\n // Start pre-render service for background caching\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Connect playback controller to pre-render service\n // Update pre-render position when playback position changes\n this.playbackController.on(MeframeEvent.PlaybackTimeUpdate, (payload: any) => {\n this.preRenderService?.updatePlaybackTime(payload.timeUs);\n });\n\n // Update playback active state\n this.playbackController.on(MeframeEvent.PlaybackPlay, () => {\n this.preRenderService?.setPlaybackActive(true);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackPause, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackStop, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n this.playbackController?.renderCurrentFrame(0);\n\n // Return preview handle\n return this.playbackController;\n }\n\n /**\n * Export the composition\n * Uses L2 cache for fast export, muxes in main thread\n */\n async export(options: ExportOptions): Promise<Blob> {\n this.ensureReady();\n this.setState('exporting');\n\n try {\n const model = this.model;\n if (!model) {\n throw new Error('No composition model set');\n }\n\n // 1. Check L2 cache readiness\n const readiness = await this.getExportReadiness();\n\n if (!readiness.ready) {\n // Emit preparing event\n this.eventBus.emit(MeframeEvent.ExportPreparing, {\n totalClips: readiness.totalClips,\n cachedClips: readiness.cachedClips,\n missingClips: readiness.missingClips,\n });\n\n // Ensure PreRenderService is started\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n }\n\n // Create AbortController for timeout control\n const abortController = new AbortController();\n const timeoutMs = 5 * 60 * 1000; // 5 minutes\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, timeoutMs);\n\n try {\n // Actively trigger missing clips L2 rendering\n await this.preRenderService.ensureClipsInL2({\n onProgress: (completed, total) => {\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: completed / total,\n stage: 'preparing',\n message: `Preparing cache: ${completed}/${total} clips`,\n });\n },\n signal: abortController.signal,\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`Export preparation timeout after ${timeoutMs / 1000}s`);\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // 2. Continue normal export flow\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n this.eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n // Delegate to orchestrator\n const blob = await this.orchestrator.export(model, options);\n\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: 1,\n });\n\n return blob;\n } catch (error) {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.ExportError, {\n error: error as Error,\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Check export readiness\n * Returns coverage info for L2 cache\n */\n async getExportReadiness(): Promise<{\n ready: boolean;\n totalClips: number;\n cachedClips: number;\n missingClips: string[];\n coverage: number;\n }> {\n if (!this.model) {\n return {\n ready: false,\n totalClips: 0,\n cachedClips: 0,\n missingClips: [],\n coverage: 0,\n };\n }\n\n // Only check main track clips (attachments are already included)\n const mainTrack = this.model.findTrack(this.model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n const missingClips: string[] = [];\n let cachedCount = 0;\n\n for (const clip of allClips) {\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n cachedCount++;\n } else {\n missingClips.push(clip.id);\n }\n }\n\n return {\n ready: missingClips.length === 0,\n totalClips: allClips.length,\n cachedClips: cachedCount,\n missingClips,\n coverage: allClips.length > 0 ? cachedCount / allClips.length : 0,\n };\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clear all L1/L2 cache data\n * Useful for debugging or forcing re-render\n */\n async clearCache(): Promise<void> {\n this.ensureReady();\n\n // Stop playback first\n this.playbackController?.stop();\n\n // Stop pre-render service\n this.preRenderService?.stop();\n\n // Clear cache through CacheManager\n await this.orchestrator.cacheManager.clear();\n\n console.log('[Meframe] Cache cleared successfully');\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export interface MeframeConfig {
|
|
|
31
31
|
workerPath?: string;
|
|
32
32
|
/** Worker file extension (.ts in dev, .js in prod) */
|
|
33
33
|
workerExtension?: string;
|
|
34
|
+
fonts?: Array<{
|
|
35
|
+
family: string;
|
|
36
|
+
url: string;
|
|
37
|
+
}>;
|
|
34
38
|
};
|
|
35
39
|
/** Resource loading stage - fetch → ReadableStream */
|
|
36
40
|
load?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;QACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,mEAAmE;QACnE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,sDAAsD;QACtD,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,7 +21,6 @@ export declare class PreRenderService implements IPreRenderService {
|
|
|
21
21
|
private renderLoopId;
|
|
22
22
|
private _framesRendered;
|
|
23
23
|
private _currentTimeUs;
|
|
24
|
-
private processedClips;
|
|
25
24
|
private isPlaybackActive;
|
|
26
25
|
private strategy;
|
|
27
26
|
private isRendering;
|
|
@@ -36,8 +35,6 @@ export declare class PreRenderService implements IPreRenderService {
|
|
|
36
35
|
setWindow(size: TimeUs): void;
|
|
37
36
|
setStrategy(strategy: Partial<RenderStrategy>): void;
|
|
38
37
|
setPriority(_priority: 'low' | 'normal' | 'high'): void;
|
|
39
|
-
clearQueue(): void;
|
|
40
|
-
queueRange(_startUs: TimeUs, _endUs: TimeUs, _priority?: number): void;
|
|
41
38
|
updatePlaybackTime(timeUs: TimeUs): void;
|
|
42
39
|
setPlaybackActive(active: boolean): void;
|
|
43
40
|
get isRunning(): boolean;
|
|
@@ -54,7 +51,7 @@ export declare class PreRenderService implements IPreRenderService {
|
|
|
54
51
|
* Ensure all clips are in L2 cache
|
|
55
52
|
* Switches to high priority mode and waits for completion
|
|
56
53
|
*/
|
|
57
|
-
ensureClipsInL2(
|
|
54
|
+
ensureClipsInL2(options?: {
|
|
58
55
|
onProgress?: (completed: number, total: number) => void;
|
|
59
56
|
signal?: AbortSignal;
|
|
60
57
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PreRenderService.d.ts","sourceRoot":"","sources":["../../src/controllers/PreRenderService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"PreRenderService.d.ts","sourceRoot":"","sources":["../../src/controllers/PreRenderService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,QAAQ,CAKd;IAEF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,aAAa,CAAoE;IACzF,OAAO,CAAC,gBAAgB,CAA6D;gBAEzE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG;IAItD,KAAK,IAAI,IAAI;IAMP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAOd,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI7B,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAIpD,WAAW,CAAC,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI;IAIvD,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAIxC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,MAAM,IAAI,eAAe,CAM5B;IAED,OAAO,CAAC,kBAAkB;YAqBZ,UAAU;IAoGxB;;;OAGG;YACW,cAAc;IAkB5B;;;OAGG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAC9B,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QACxD,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;CAYlB"}
|
|
@@ -6,7 +6,6 @@ class PreRenderService {
|
|
|
6
6
|
renderLoopId = null;
|
|
7
7
|
_framesRendered = 0;
|
|
8
8
|
_currentTimeUs = 0;
|
|
9
|
-
processedClips = /* @__PURE__ */ new Set();
|
|
10
9
|
isPlaybackActive = false;
|
|
11
10
|
strategy = {
|
|
12
11
|
direction: "forward",
|
|
@@ -50,10 +49,6 @@ class PreRenderService {
|
|
|
50
49
|
}
|
|
51
50
|
setPriority(_priority) {
|
|
52
51
|
}
|
|
53
|
-
clearQueue() {
|
|
54
|
-
}
|
|
55
|
-
queueRange(_startUs, _endUs, _priority) {
|
|
56
|
-
}
|
|
57
52
|
updatePlaybackTime(timeUs) {
|
|
58
53
|
this._currentTimeUs = timeUs;
|
|
59
54
|
}
|
|
@@ -70,7 +65,6 @@ class PreRenderService {
|
|
|
70
65
|
return {
|
|
71
66
|
isRunning: this._isRunning,
|
|
72
67
|
framesRendered: this._framesRendered,
|
|
73
|
-
queueSize: 0,
|
|
74
68
|
currentTimeUs: this._currentTimeUs
|
|
75
69
|
};
|
|
76
70
|
}
|
|
@@ -108,17 +102,13 @@ class PreRenderService {
|
|
|
108
102
|
return;
|
|
109
103
|
}
|
|
110
104
|
let clipToRender = null;
|
|
111
|
-
let
|
|
105
|
+
let completedCount = 0;
|
|
112
106
|
for (const clip of allClips) {
|
|
113
|
-
if (this.processedClips.has(clip.id)) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
107
|
const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, "video");
|
|
117
108
|
if (inL2) {
|
|
118
|
-
|
|
109
|
+
completedCount++;
|
|
119
110
|
continue;
|
|
120
111
|
}
|
|
121
|
-
hasUnprocessedClips = true;
|
|
122
112
|
if (hasResourceId(clip) && this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)) {
|
|
123
113
|
continue;
|
|
124
114
|
}
|
|
@@ -126,7 +116,7 @@ class PreRenderService {
|
|
|
126
116
|
break;
|
|
127
117
|
}
|
|
128
118
|
if (!clipToRender) {
|
|
129
|
-
if (
|
|
119
|
+
if (completedCount < allClips.length) {
|
|
130
120
|
return;
|
|
131
121
|
}
|
|
132
122
|
if (this.highPriorityMode) {
|
|
@@ -144,15 +134,12 @@ class PreRenderService {
|
|
|
144
134
|
if (!rendered) {
|
|
145
135
|
return;
|
|
146
136
|
}
|
|
147
|
-
this.processedClips.add(clipToRender.id);
|
|
148
137
|
if (this.highPriorityMode && this.progressCallback) {
|
|
149
138
|
const totalClips = allClips.length;
|
|
150
|
-
|
|
151
|
-
this.progressCallback(completedCount, totalClips);
|
|
139
|
+
this.progressCallback(completedCount + 1, totalClips);
|
|
152
140
|
}
|
|
153
141
|
} catch (error) {
|
|
154
142
|
console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);
|
|
155
|
-
this.processedClips.add(clipToRender.id);
|
|
156
143
|
if (this.highPriorityMode) {
|
|
157
144
|
this.exportWaiters.forEach((w) => w.reject(error));
|
|
158
145
|
this.exportWaiters = [];
|
|
@@ -182,33 +169,13 @@ class PreRenderService {
|
|
|
182
169
|
* Ensure all clips are in L2 cache
|
|
183
170
|
* Switches to high priority mode and waits for completion
|
|
184
171
|
*/
|
|
185
|
-
async ensureClipsInL2(
|
|
186
|
-
const model = this.orchestrator.compositionModel;
|
|
187
|
-
if (!model) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const mainTrack = model.findTrack(model.mainTrackId);
|
|
191
|
-
const allClips = mainTrack?.clips ?? [];
|
|
192
|
-
let allComplete = true;
|
|
193
|
-
for (const clip of allClips) {
|
|
194
|
-
const complete = await this.orchestrator.cacheManager.hasClipInL2(clip.id, "video");
|
|
195
|
-
if (!complete) {
|
|
196
|
-
allComplete = false;
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (allComplete) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
172
|
+
async ensureClipsInL2(options) {
|
|
203
173
|
return new Promise((resolve, reject) => {
|
|
204
174
|
this.highPriorityMode = true;
|
|
205
175
|
this.progressCallback = options?.onProgress || null;
|
|
206
176
|
this.exportWaiters.push({ resolve, reject });
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
this.renderLoopId = null;
|
|
210
|
-
}
|
|
211
|
-
this.scheduleNextRender();
|
|
177
|
+
this.stop();
|
|
178
|
+
this.start();
|
|
212
179
|
});
|
|
213
180
|
}
|
|
214
181
|
}
|
|
@@ -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 processedClips = new Set<string>();\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 clearQueue(): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n queueRange(_startUs: TimeUs, _endUs: TimeUs, _priority?: number): 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 queueSize: 0,\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 hasUnprocessedClips = false;\n\n for (const clip of allClips) {\n // Already processed\n if (this.processedClips.has(clip.id)) {\n continue;\n }\n\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n this.processedClips.add(clip.id);\n continue;\n }\n\n hasUnprocessedClips = true;\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 (hasUnprocessedClips) {\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 this.processedClips.add(clipToRender.id);\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n const completedCount = this.processedClips.size;\n this.progressCallback(completedCount, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n this.processedClips.add(clipToRender.id);\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(\n _clipIds: string[],\n options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }\n ): Promise<void> {\n const model = this.orchestrator.compositionModel;\n if (!model) {\n return;\n }\n\n // Only check main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n let allComplete = true;\n for (const clip of allClips) {\n const complete = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (!complete) {\n allComplete = false;\n break;\n }\n }\n\n if (allComplete) {\n return;\n }\n\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 if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n this.scheduleNextRender();\n });\n }\n}\n"],"names":[],"mappings":";AAiBO,MAAM,iBAA8C;AAAA,EACjD;AAAA,EACA,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,eAA8B;AAAA,EAC9B,kBAAkB;AAAA,EAClB,iBAAyB;AAAA,EACzB,qCAAqB,IAAA;AAAA,EACrB,mBAAmB;AAAA,EACnB,WAA2B;AAAA,IACjC,WAAW;AAAA,IACX,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,eAAe;AAAA,EAAA;AAAA,EAGT,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,gBAA8E,CAAA;AAAA,EAC9E,mBAAwE;AAAA,EAEhF,YAAY,cAA4B,WAAgB;AACtD,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,mBAAA;AAAA,EACP;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,aAAa;AAElB,QAAI,KAAK,iBAAiB,MAAM;AAC9B,yBAAmB,KAAK,YAAY;AACpC,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,SAAe;AACb,SAAK,YAAY;AACjB,QAAI,KAAK,cAAc,CAAC,KAAK,cAAc;AACzC,WAAK,mBAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,UAAU,MAAoB;AAC5B,SAAK,SAAS,YAAY;AAAA,EAC5B;AAAA,EAEA,YAAY,UAAyC;AACnD,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,GAAG,SAAA;AAAA,EACzC;AAAA,EAEA,YAAY,WAA4C;AAAA,EAExD;AAAA,EAEA,aAAmB;AAAA,EAEnB;AAAA,EAEA,WAAW,UAAkB,QAAgB,WAA0B;AAAA,EAEvE;AAAA,EAEA,mBAAmB,QAAsB;AACvC,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,kBAAkB,QAAuB;AACvC,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAA0B;AAC5B,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,gBAAgB,KAAK;AAAA,MACrB,WAAW;AAAA,MACX,eAAe,KAAK;AAAA,IAAA;AAAA,EAExB;AAAA,EAEQ,qBAA2B;AACjC,QAAI,CAAC,KAAK,WAAY;AAEtB,QAAI,KAAK,kBAAkB;AAEzB,WAAK,eAAe,sBAAsB,YAAY;AACpD,cAAM,KAAK,WAAA;AACX,aAAK,mBAAA;AAAA,MACP,CAAC;AAAA,IACH,OAAO;AAEL,WAAK,eAAe;AAAA,QAClB,YAAY;AACV,gBAAM,KAAK,WAAA;AACX,eAAK,mBAAA;AAAA,QACP;AAAA,QACA,EAAE,SAAS,IAAA;AAAA,MAAI;AAAA,IAEnB;AAAA,EACF;AAAA,EAEA,MAAc,aAA4B;AACxC,QAAI,CAAC,KAAK,cAAc,KAAK,aAAa,CAAC,KAAK,aAAa,kBAAkB;AAC7E;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,aAAa;AAEhC,QAAI,KAAK,kBAAkB;AAEzB;AAAA,IACF;AAGA,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAIA,UAAM,YAAY,MAAM,UAAU,MAAM,WAAW;AACnD,UAAM,WAAW,WAAW,SAAS,CAAA;AAErC,QAAI,SAAS,WAAW,GAAG;AACzB;AAAA,IACF;AAEA,QAAI,eAAe;AACnB,QAAI,sBAAsB;AAE1B,eAAW,QAAQ,UAAU;AAE3B,UAAI,KAAK,eAAe,IAAI,KAAK,EAAE,GAAG;AACpC;AAAA,MACF;AAGA,YAAM,OAAO,MAAM,KAAK,aAAa,aAAa,YAAY,KAAK,IAAI,OAAO;AAC9E,UAAI,MAAM;AACR,aAAK,eAAe,IAAI,KAAK,EAAE;AAC/B;AAAA,MACF;AAEA,4BAAsB;AAGtB,UACE,cAAc,IAAI,KAClB,KAAK,aAAa,eAAe,kBAAkB,KAAK,UAAU,GAClE;AACA;AAAA,MACF;AAGA,qBAAe;AACf;AAAA,IACF;AAGA,QAAI,CAAC,cAAc;AACjB,UAAI,qBAAqB;AAGvB;AAAA,MACF;AAGA,UAAI,KAAK,kBAAkB;AACzB,aAAK,cAAc,QAAQ,CAAC,MAAM,EAAE,SAAS;AAC7C,aAAK,gBAAgB,CAAA;AACrB,aAAK,mBAAmB;AACxB,aAAK,mBAAmB;AAAA,MAC1B;AAEA,WAAK,KAAK,KAAA;AACV;AAAA,IACF;AAGA,SAAK,cAAc;AACnB,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,eAAe,aAAa,EAAE;AAC1D,UAAI,CAAC,UAAU;AAEb;AAAA,MACF;AACA,WAAK,eAAe,IAAI,aAAa,EAAE;AAGvC,UAAI,KAAK,oBAAoB,KAAK,kBAAkB;AAClD,cAAM,aAAa,SAAS;AAC5B,cAAM,iBAAiB,KAAK,eAAe;AAC3C,aAAK,iBAAiB,gBAAgB,UAAU;AAAA,MAClD;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,4CAA4C,aAAa,EAAE,KAAK,KAAK;AACnF,WAAK,eAAe,IAAI,aAAa,EAAE;AAGvC,UAAI,KAAK,kBAAkB;AACzB,aAAK,cAAc,QAAQ,CAAC,MAAM,EAAE,OAAO,KAAc,CAAC;AAC1D,aAAK,gBAAgB,CAAA;AACrB,aAAK,mBAAmB;AACxB,aAAK,mBAAmB;AAAA,MAC1B;AAAA,IACF,UAAA;AACE,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,eAAe,QAAkC;AAC7D,UAAM,QAAQ,KAAK,aAAa;AAChC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,MAAM,SAAS,MAAM;AAClC,QAAI,CAAC,KAAM,QAAO;AAGlB,UAAM,WAAW,MAAM,KAAK,aAAa,gBAAgB,MAAM;AAG/D,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,iBAAiB,KAAK,KAAM,KAAK,aAAa,MAAa,GAAG;AACpE,SAAK,mBAAmB;AAExB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBACJ,UACA,SAIe;AACf,UAAM,QAAQ,KAAK,aAAa;AAChC,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAGA,UAAM,YAAY,MAAM,UAAU,MAAM,WAAW;AACnD,UAAM,WAAW,WAAW,SAAS,CAAA;AAErC,QAAI,cAAc;AAClB,eAAW,QAAQ,UAAU;AAC3B,YAAM,WAAW,MAAM,KAAK,aAAa,aAAa,YAAY,KAAK,IAAI,OAAO;AAClF,UAAI,CAAC,UAAU;AACb,sBAAc;AACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa;AACf;AAAA,IACF;AAGA,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,mBAAmB;AACxB,WAAK,mBAAmB,SAAS,cAAc;AAC/C,WAAK,cAAc,KAAK,EAAE,SAAS,QAAQ;AAG3C,UAAI,KAAK,iBAAiB,MAAM;AAC9B,2BAAmB,KAAK,YAAY;AACpC,aAAK,eAAe;AAAA,MACtB;AACA,WAAK,mBAAA;AAAA,IACP,CAAC;AAAA,EACH;AACF;"}
|
|
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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"}
|
|
@@ -11,10 +11,7 @@ export interface IPreRenderService {
|
|
|
11
11
|
setWindow(size: TimeUs): void;
|
|
12
12
|
setPriority(priority: 'low' | 'normal' | 'high'): void;
|
|
13
13
|
setStrategy(strategy: RenderStrategy): void;
|
|
14
|
-
clearQueue(): void;
|
|
15
|
-
queueRange(startUs: TimeUs, endUs: TimeUs, priority?: number): void;
|
|
16
14
|
readonly status: PreRenderStatus;
|
|
17
|
-
readonly queueSize: number;
|
|
18
15
|
}
|
|
19
16
|
export interface RenderStrategy {
|
|
20
17
|
direction: 'forward' | 'backward' | 'bidirectional';
|
|
@@ -25,7 +22,6 @@ export interface RenderStrategy {
|
|
|
25
22
|
export interface PreRenderStatus {
|
|
26
23
|
isRunning: boolean;
|
|
27
24
|
framesRendered: number;
|
|
28
|
-
queueSize: number;
|
|
29
25
|
currentTimeUs: TimeUs;
|
|
30
26
|
}
|
|
31
27
|
export interface RenderTask {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/controllers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,MAAM,WAAW,iBAAiB;IAEhC,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IAGf,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IACvD,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAG5C,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/controllers/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EAGP,MAAM,SAAS,CAAC;AAIjB,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAG,MAAM,CAAC;IAC3B,SAAgB,WAAW,EAAE,MAAM,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,CAAC;IACvB,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IA6BtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKpE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAYlD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI;IAOvF,kBAAkB,IAAI,QAAQ,EAAE;IAahC,WAAW,IAAI,MAAM;IAIrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUzC,OAAO,CAAC,YAAY;IA0DpB;;OAEG;IACH,uBAAuB,CACrB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,IAAI;IA2BP;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAY3B,OAAO,CAAC,oBAAoB;
|
|
1
|
+
{"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EAGP,MAAM,SAAS,CAAC;AAIjB,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAG,MAAM,CAAC;IAC3B,SAAgB,WAAW,EAAE,MAAM,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,CAAC;IACvB,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IA6BtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKpE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAYlD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI;IAOvF,kBAAkB,IAAI,QAAQ,EAAE;IAahC,WAAW,IAAI,MAAM;IAIrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUzC,OAAO,CAAC,YAAY;IA0DpB;;OAEG;IACH,uBAAuB,CACrB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,IAAI;IA2BP;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAY3B,OAAO,CAAC,oBAAoB;IAqF5B,OAAO,CAAC,cAAc;CAoBvB"}
|
|
@@ -252,10 +252,12 @@ ${errors.map((e) => `${e.path}: ${e.message}`).join("\n")}`
|
|
|
252
252
|
for (const track of this.tracks) {
|
|
253
253
|
if (track.id === this.mainTrackId) {
|
|
254
254
|
mainTrack = track;
|
|
255
|
+
track.clips.sort((a, b) => a.startUs - b.startUs);
|
|
255
256
|
realTracks.push(track);
|
|
256
257
|
continue;
|
|
257
258
|
}
|
|
258
259
|
if (track.kind === "audio") {
|
|
260
|
+
track.clips.sort((a, b) => a.startUs - b.startUs);
|
|
259
261
|
realTracks.push(track);
|
|
260
262
|
continue;
|
|
261
263
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositionModel.js","sources":["../../src/model/CompositionModel.ts"],"sourcesContent":["import {\n CompositionModelData,\n Track,\n Clip,\n Resource,\n TimeUs,\n AnimationEffect,\n hasResourceId,\n} from './types';\nimport { binarySearchRange, binarySearchOverlapping } from '../utils/binary-search';\nimport { validateCompositionStructure } from './validation';\n\nexport class CompositionModel {\n public readonly version = '1.0' as const;\n public readonly fps: 24 | 25 | 30 | 60;\n public durationUs!: TimeUs; // Assigned in buildIndexes()\n public readonly mainTrackId: string;\n public tracks: Track[];\n public readonly resources: Map<string, Resource>;\n\n private readonly trackMap: Map<string, Track>;\n private readonly clipMap: Map<string, Clip>;\n private readonly resourceRefCount: Map<string, number>;\n\n public readonly renderConfig?: {\n width: number;\n height: number;\n backgroundColor?: string;\n };\n\n public readonly ext?: Record<string, unknown>;\n\n constructor(data: CompositionModelData) {\n const errors = validateCompositionStructure(data);\n if (errors.length > 0) {\n throw new Error(\n `Validation failed:\\n${errors.map((e) => `${e.path}: ${e.message}`).join('\\n')}`\n );\n }\n\n this.fps = data.fps;\n this.mainTrackId = data.mainTrackId ?? 'main';\n this.tracks = data.tracks;\n this.resources = new Map(Object.entries(data.resources));\n this.renderConfig = data.renderConfig;\n this.ext = data.ext;\n\n // Build indexes\n this.trackMap = new Map();\n this.clipMap = new Map();\n this.resourceRefCount = new Map();\n\n this.buildIndexes();\n\n console.log(\n 'CompositionModel constructed',\n this.tracks.find((t) => t.id === this.mainTrackId)?.clips[0]\n );\n }\n\n // Track operations\n findTrack(id: string): Track | null {\n return this.trackMap.get(id) || null;\n }\n\n getTracksByKind(kind: 'video' | 'audio' | 'caption' | 'fx'): Track[] {\n return this.tracks.filter((track) => track.kind === kind);\n }\n\n // Clip operations with binary search optimization\n findClip(id: string): Clip | null {\n return this.clipMap.get(id) || null;\n }\n\n getClipsAtTime(timeUs: TimeUs, trackId?: string): Clip[] {\n const tracks = trackId ? [this.findTrack(trackId)] : this.tracks;\n const clips: Clip[] = [];\n\n for (const track of tracks) {\n if (!track) continue;\n // Use binary search for single point lookup\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n if (clip) {\n clips.push(clip);\n }\n }\n\n return clips;\n }\n\n getActiveClips(startUs: TimeUs, endUs: TimeUs): Clip[] {\n const clips: Clip[] = [];\n\n for (const track of this.tracks) {\n // Use binary search for range overlap\n const overlappingClips = binarySearchOverlapping(track.clips, startUs, endUs, (clip) => ({\n start: clip.startUs,\n end: clip.startUs + clip.durationUs,\n }));\n\n clips.push(...overlappingClips);\n }\n\n return clips;\n }\n\n getClipIdsByResourceId(resourceId: string): string[] {\n const resource = this.resources.get(resourceId);\n return resource?.clipIds || [];\n }\n\n getClipIdAtTime(trackId: string, timeUs: TimeUs): string | undefined {\n const track = this.findTrack(trackId);\n if (!track) {\n return undefined;\n }\n\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n return clip?.id;\n }\n\n /**\n * Get neighboring clips (Prev/Current/Next) at a specific time for video tracks\n * Returns prev, current, and next clip IDs\n */\n getNeighboringClips(timeUs: TimeUs): { prev?: string; current?: string; next?: string } {\n const videoTracks = this.getTracksByKind('video');\n const result: { prev?: string; current?: string; next?: string } = {};\n\n for (const track of videoTracks) {\n const clips = track.clips;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n if (!clip) continue;\n\n const clipEndUs = clip.startUs + clip.durationUs;\n\n if (clip.startUs <= timeUs && timeUs < clipEndUs) {\n if (!result.current) {\n result.current = clip.id;\n }\n\n if (i > 0 && !result.prev) {\n const prevClip = clips[i - 1];\n if (prevClip) {\n result.prev = prevClip.id;\n }\n }\n\n if (i < clips.length - 1 && !result.next) {\n const nextClip = clips[i + 1];\n if (nextClip) {\n result.next = nextClip.id;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get all clip IDs that should be cached using 2-Clip strategy\n * Returns array of unique clip IDs (Prev/Current/Next)\n */\n getClipsToCacheAtTime(timeUs: TimeUs): Set<string> {\n const { current, next } = this.getNeighboringClips(timeUs);\n const clipIds = new Set<string>();\n\n if (current) clipIds.add(current);\n if (next) clipIds.add(next);\n // if (prev) clipIds.push(prev);\n\n return clipIds;\n }\n\n // Resource operations\n getResource(id: string): Resource | null {\n return this.resources.get(id) || null;\n }\n\n updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void {\n const resource = this.resources.get(id);\n if (resource) {\n resource.state = state;\n }\n }\n\n getUnusedResources(): Resource[] {\n const unused: Resource[] = [];\n\n for (const [id, resource] of this.resources) {\n if (!this.resourceRefCount.has(id) || this.resourceRefCount.get(id) === 0) {\n unused.push(resource);\n }\n }\n\n return unused;\n }\n\n // Time operations\n getDuration(): TimeUs {\n return this.durationUs;\n }\n\n getTrackDuration(trackId: string): TimeUs {\n const track = this.findTrack(trackId);\n if (!track || track.clips.length === 0) return 0;\n\n // Since clips are sorted, last clip determines duration\n const lastClip = track.clips[track.clips.length - 1];\n return (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n // Private methods\n private buildIndexes(): void {\n // Clear existing indexes\n this.trackMap.clear();\n this.clipMap.clear();\n this.resourceRefCount.clear();\n\n // Step 1: Sink attachment tracks to main track first\n this.sinkAttachmentTracks();\n\n let maxEndUs = 0;\n\n // Step 2: Build all indexes in one pass (track, clip, resource)\n for (const track of this.tracks) {\n this.trackMap.set(track.id, track);\n\n for (const clip of track.clips) {\n (clip as Clip).trackId = track.id;\n (clip as Clip).trackKind = track.kind;\n this.clipMap.set(clip.id, clip);\n\n // Main track resource index (only for clips with resourceId)\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n resource.clipIds = [...(resource.clipIds || []), clip.id];\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n\n // Attachment resource indexes (attachments are already sunk)\n const attachments = clip.attachments ?? [];\n for (const attachment of attachments) {\n const attachmentResourceId = attachment.data?.resourceId;\n if (attachmentResourceId && typeof attachmentResourceId === 'string') {\n const attachmentResource = this.resources.get(attachmentResourceId);\n if (attachmentResource) {\n const clipIds = attachmentResource.clipIds || [];\n if (!clipIds.includes(clip.id)) {\n attachmentResource.clipIds = [...clipIds, clip.id];\n }\n }\n const attachmentCount = this.resourceRefCount.get(attachmentResourceId) || 0;\n this.resourceRefCount.set(attachmentResourceId, attachmentCount + 1);\n }\n }\n\n // Calculate max end time\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clipEndUs > maxEndUs) {\n maxEndUs = clipEndUs;\n }\n }\n }\n\n this.durationUs = maxEndUs;\n }\n\n /**\n * Incrementally update resource index when clip's resourceId changes\n */\n updateClipResourceIndex(\n clipId: string,\n oldResourceId: string | undefined,\n newResourceId: string | undefined\n ): void {\n // Remove from old resource\n if (oldResourceId) {\n const oldResource = this.resources.get(oldResourceId);\n if (oldResource?.clipIds) {\n oldResource.clipIds = oldResource.clipIds.filter((id) => id !== clipId);\n }\n const oldCount = this.resourceRefCount.get(oldResourceId) || 0;\n this.resourceRefCount.set(oldResourceId, Math.max(0, oldCount - 1));\n }\n\n // Add to new resource\n if (newResourceId) {\n const newResource = this.resources.get(newResourceId);\n if (newResource) {\n if (!newResource.clipIds) {\n newResource.clipIds = [];\n }\n if (!newResource.clipIds.includes(clipId)) {\n newResource.clipIds.push(clipId);\n }\n }\n const newCount = this.resourceRefCount.get(newResourceId) || 0;\n this.resourceRefCount.set(newResourceId, newCount + 1);\n }\n }\n\n /**\n * Recalculate total duration based on main track\n */\n recalculateDuration(): void {\n const mainTrack = this.findTrack(this.mainTrackId);\n if (!mainTrack || mainTrack.clips.length === 0) {\n this.durationUs = 0;\n return;\n }\n\n // Since clips are sorted, last clip determines duration\n const lastClip = mainTrack.clips[mainTrack.clips.length - 1];\n this.durationUs = (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n private sinkAttachmentTracks(): void {\n let mainTrack: Track | undefined;\n const attachmentTracks = [];\n const realTracks = [];\n // Find all tracks to sink: non-main, non-audio tracks (includes caption, overlay, etc.)\n for (const track of this.tracks) {\n if (track.id === this.mainTrackId) {\n mainTrack = track;\n realTracks.push(track);\n continue;\n }\n if (track.kind === 'audio') {\n realTracks.push(track);\n continue;\n }\n attachmentTracks.push(track);\n }\n\n if (!mainTrack) {\n throw new Error('Main track not found');\n }\n\n this.tracks = realTracks;\n\n for (const attachmentTrack of attachmentTracks) {\n for (const attachmentClip of attachmentTrack.clips) {\n // Get purpose: use metadata.purpose, or infer from track kind\n let purpose: 'caption' | 'overlay' | 'mask' =\n attachmentClip.metadata?.purpose ??\n (attachmentTrack.kind === 'caption' ? 'caption' : 'overlay');\n\n // Ensure purpose is valid\n if (purpose !== 'caption' && purpose !== 'overlay' && purpose !== 'mask') {\n purpose = 'overlay';\n }\n\n for (const mainClip of mainTrack.clips) {\n const overlap = this.getTimeOverlap(attachmentClip, mainClip);\n if (!overlap) continue;\n\n if (!mainClip.attachments) {\n mainClip.attachments = [];\n }\n\n // Extract animation effect\n const animationEffect = attachmentClip.effects?.find(\n (e) => e.effectType === 'animation'\n ) as AnimationEffect | undefined;\n\n // Extract target dimensions from metadata\n const targetWidth = attachmentClip.metadata?.targetWidth as number | undefined;\n const targetHeight = attachmentClip.metadata?.targetHeight as number | undefined;\n\n const attachmentData: Record<string, unknown> = {\n animation: animationEffect?.params,\n overlayClipStartUs: attachmentClip.startUs,\n mainClipStartUs: mainClip.startUs,\n ...(targetWidth !== undefined && { targetWidth }),\n ...(targetHeight !== undefined && { targetHeight }),\n };\n\n // Add resourceId if exists (trackKind not yet set, check field directly)\n if ('resourceId' in attachmentClip && attachmentClip.resourceId) {\n attachmentData.resourceId = attachmentClip.resourceId;\n }\n\n // Add text if exists (trackKind not yet set, check field directly)\n if ('text' in attachmentClip && attachmentClip.text) {\n attachmentData.text = attachmentClip.text;\n }\n\n mainClip.attachments.push({\n id: `${purpose}-${attachmentClip.id}-${mainClip.id}`,\n kind: purpose,\n startUs: overlap.clipRelativeStart,\n durationUs: overlap.duration,\n data: attachmentData,\n });\n }\n }\n }\n }\n\n private getTimeOverlap(\n attachmentClip: Clip,\n mainClip: Clip\n ): { clipRelativeStart: number; duration: number } | null {\n const attachmentStart = attachmentClip.startUs;\n const attachmentEnd = attachmentClip.startUs + attachmentClip.durationUs;\n const mainStart = mainClip.startUs;\n const mainEnd = mainClip.startUs + mainClip.durationUs;\n\n if (attachmentEnd <= mainStart || attachmentStart >= mainEnd) {\n return null;\n }\n\n const overlapStart = Math.max(attachmentStart, mainStart);\n const overlapEnd = Math.min(attachmentEnd, mainEnd);\n const duration = overlapEnd - overlapStart;\n const clipRelativeStart = overlapStart - mainStart;\n\n return { clipRelativeStart, duration };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompositionModel.js","sources":["../../src/model/CompositionModel.ts"],"sourcesContent":["import {\n CompositionModelData,\n Track,\n Clip,\n Resource,\n TimeUs,\n AnimationEffect,\n hasResourceId,\n} from './types';\nimport { binarySearchRange, binarySearchOverlapping } from '../utils/binary-search';\nimport { validateCompositionStructure } from './validation';\n\nexport class CompositionModel {\n public readonly version = '1.0' as const;\n public readonly fps: 24 | 25 | 30 | 60;\n public durationUs!: TimeUs; // Assigned in buildIndexes()\n public readonly mainTrackId: string;\n public tracks: Track[];\n public readonly resources: Map<string, Resource>;\n\n private readonly trackMap: Map<string, Track>;\n private readonly clipMap: Map<string, Clip>;\n private readonly resourceRefCount: Map<string, number>;\n\n public readonly renderConfig?: {\n width: number;\n height: number;\n backgroundColor?: string;\n };\n\n public readonly ext?: Record<string, unknown>;\n\n constructor(data: CompositionModelData) {\n const errors = validateCompositionStructure(data);\n if (errors.length > 0) {\n throw new Error(\n `Validation failed:\\n${errors.map((e) => `${e.path}: ${e.message}`).join('\\n')}`\n );\n }\n\n this.fps = data.fps;\n this.mainTrackId = data.mainTrackId ?? 'main';\n this.tracks = data.tracks;\n this.resources = new Map(Object.entries(data.resources));\n this.renderConfig = data.renderConfig;\n this.ext = data.ext;\n\n // Build indexes\n this.trackMap = new Map();\n this.clipMap = new Map();\n this.resourceRefCount = new Map();\n\n this.buildIndexes();\n\n console.log(\n 'CompositionModel constructed',\n this.tracks.find((t) => t.id === this.mainTrackId)?.clips[0]\n );\n }\n\n // Track operations\n findTrack(id: string): Track | null {\n return this.trackMap.get(id) || null;\n }\n\n getTracksByKind(kind: 'video' | 'audio' | 'caption' | 'fx'): Track[] {\n return this.tracks.filter((track) => track.kind === kind);\n }\n\n // Clip operations with binary search optimization\n findClip(id: string): Clip | null {\n return this.clipMap.get(id) || null;\n }\n\n getClipsAtTime(timeUs: TimeUs, trackId?: string): Clip[] {\n const tracks = trackId ? [this.findTrack(trackId)] : this.tracks;\n const clips: Clip[] = [];\n\n for (const track of tracks) {\n if (!track) continue;\n // Use binary search for single point lookup\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n if (clip) {\n clips.push(clip);\n }\n }\n\n return clips;\n }\n\n getActiveClips(startUs: TimeUs, endUs: TimeUs): Clip[] {\n const clips: Clip[] = [];\n\n for (const track of this.tracks) {\n // Use binary search for range overlap\n const overlappingClips = binarySearchOverlapping(track.clips, startUs, endUs, (clip) => ({\n start: clip.startUs,\n end: clip.startUs + clip.durationUs,\n }));\n\n clips.push(...overlappingClips);\n }\n\n return clips;\n }\n\n getClipIdsByResourceId(resourceId: string): string[] {\n const resource = this.resources.get(resourceId);\n return resource?.clipIds || [];\n }\n\n getClipIdAtTime(trackId: string, timeUs: TimeUs): string | undefined {\n const track = this.findTrack(trackId);\n if (!track) {\n return undefined;\n }\n\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n return clip?.id;\n }\n\n /**\n * Get neighboring clips (Prev/Current/Next) at a specific time for video tracks\n * Returns prev, current, and next clip IDs\n */\n getNeighboringClips(timeUs: TimeUs): { prev?: string; current?: string; next?: string } {\n const videoTracks = this.getTracksByKind('video');\n const result: { prev?: string; current?: string; next?: string } = {};\n\n for (const track of videoTracks) {\n const clips = track.clips;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n if (!clip) continue;\n\n const clipEndUs = clip.startUs + clip.durationUs;\n\n if (clip.startUs <= timeUs && timeUs < clipEndUs) {\n if (!result.current) {\n result.current = clip.id;\n }\n\n if (i > 0 && !result.prev) {\n const prevClip = clips[i - 1];\n if (prevClip) {\n result.prev = prevClip.id;\n }\n }\n\n if (i < clips.length - 1 && !result.next) {\n const nextClip = clips[i + 1];\n if (nextClip) {\n result.next = nextClip.id;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get all clip IDs that should be cached using 2-Clip strategy\n * Returns array of unique clip IDs (Prev/Current/Next)\n */\n getClipsToCacheAtTime(timeUs: TimeUs): Set<string> {\n const { current, next } = this.getNeighboringClips(timeUs);\n const clipIds = new Set<string>();\n\n if (current) clipIds.add(current);\n if (next) clipIds.add(next);\n // if (prev) clipIds.push(prev);\n\n return clipIds;\n }\n\n // Resource operations\n getResource(id: string): Resource | null {\n return this.resources.get(id) || null;\n }\n\n updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void {\n const resource = this.resources.get(id);\n if (resource) {\n resource.state = state;\n }\n }\n\n getUnusedResources(): Resource[] {\n const unused: Resource[] = [];\n\n for (const [id, resource] of this.resources) {\n if (!this.resourceRefCount.has(id) || this.resourceRefCount.get(id) === 0) {\n unused.push(resource);\n }\n }\n\n return unused;\n }\n\n // Time operations\n getDuration(): TimeUs {\n return this.durationUs;\n }\n\n getTrackDuration(trackId: string): TimeUs {\n const track = this.findTrack(trackId);\n if (!track || track.clips.length === 0) return 0;\n\n // Since clips are sorted, last clip determines duration\n const lastClip = track.clips[track.clips.length - 1];\n return (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n // Private methods\n private buildIndexes(): void {\n // Clear existing indexes\n this.trackMap.clear();\n this.clipMap.clear();\n this.resourceRefCount.clear();\n\n // Step 1: Sink attachment tracks to main track first\n this.sinkAttachmentTracks();\n\n let maxEndUs = 0;\n\n // Step 2: Build all indexes in one pass (track, clip, resource)\n for (const track of this.tracks) {\n this.trackMap.set(track.id, track);\n\n for (const clip of track.clips) {\n (clip as Clip).trackId = track.id;\n (clip as Clip).trackKind = track.kind;\n this.clipMap.set(clip.id, clip);\n\n // Main track resource index (only for clips with resourceId)\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n resource.clipIds = [...(resource.clipIds || []), clip.id];\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n\n // Attachment resource indexes (attachments are already sunk)\n const attachments = clip.attachments ?? [];\n for (const attachment of attachments) {\n const attachmentResourceId = attachment.data?.resourceId;\n if (attachmentResourceId && typeof attachmentResourceId === 'string') {\n const attachmentResource = this.resources.get(attachmentResourceId);\n if (attachmentResource) {\n const clipIds = attachmentResource.clipIds || [];\n if (!clipIds.includes(clip.id)) {\n attachmentResource.clipIds = [...clipIds, clip.id];\n }\n }\n const attachmentCount = this.resourceRefCount.get(attachmentResourceId) || 0;\n this.resourceRefCount.set(attachmentResourceId, attachmentCount + 1);\n }\n }\n\n // Calculate max end time\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clipEndUs > maxEndUs) {\n maxEndUs = clipEndUs;\n }\n }\n }\n\n this.durationUs = maxEndUs;\n }\n\n /**\n * Incrementally update resource index when clip's resourceId changes\n */\n updateClipResourceIndex(\n clipId: string,\n oldResourceId: string | undefined,\n newResourceId: string | undefined\n ): void {\n // Remove from old resource\n if (oldResourceId) {\n const oldResource = this.resources.get(oldResourceId);\n if (oldResource?.clipIds) {\n oldResource.clipIds = oldResource.clipIds.filter((id) => id !== clipId);\n }\n const oldCount = this.resourceRefCount.get(oldResourceId) || 0;\n this.resourceRefCount.set(oldResourceId, Math.max(0, oldCount - 1));\n }\n\n // Add to new resource\n if (newResourceId) {\n const newResource = this.resources.get(newResourceId);\n if (newResource) {\n if (!newResource.clipIds) {\n newResource.clipIds = [];\n }\n if (!newResource.clipIds.includes(clipId)) {\n newResource.clipIds.push(clipId);\n }\n }\n const newCount = this.resourceRefCount.get(newResourceId) || 0;\n this.resourceRefCount.set(newResourceId, newCount + 1);\n }\n }\n\n /**\n * Recalculate total duration based on main track\n */\n recalculateDuration(): void {\n const mainTrack = this.findTrack(this.mainTrackId);\n if (!mainTrack || mainTrack.clips.length === 0) {\n this.durationUs = 0;\n return;\n }\n\n // Since clips are sorted, last clip determines duration\n const lastClip = mainTrack.clips[mainTrack.clips.length - 1];\n this.durationUs = (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n private sinkAttachmentTracks(): void {\n let mainTrack: Track | undefined;\n const attachmentTracks = [];\n const realTracks = [];\n // Find all tracks to sink: non-main, non-audio tracks (includes caption, overlay, etc.)\n for (const track of this.tracks) {\n if (track.id === this.mainTrackId) {\n mainTrack = track;\n track.clips.sort((a, b) => a.startUs - b.startUs);\n realTracks.push(track);\n continue;\n }\n if (track.kind === 'audio') {\n track.clips.sort((a, b) => a.startUs - b.startUs);\n realTracks.push(track);\n continue;\n }\n attachmentTracks.push(track);\n }\n\n if (!mainTrack) {\n throw new Error('Main track not found');\n }\n\n this.tracks = realTracks;\n\n for (const attachmentTrack of attachmentTracks) {\n for (const attachmentClip of attachmentTrack.clips) {\n // Get purpose: use metadata.purpose, or infer from track kind\n let purpose: 'caption' | 'overlay' | 'mask' =\n attachmentClip.metadata?.purpose ??\n (attachmentTrack.kind === 'caption' ? 'caption' : 'overlay');\n\n // Ensure purpose is valid\n if (purpose !== 'caption' && purpose !== 'overlay' && purpose !== 'mask') {\n purpose = 'overlay';\n }\n\n for (const mainClip of mainTrack.clips) {\n const overlap = this.getTimeOverlap(attachmentClip, mainClip);\n if (!overlap) continue;\n\n if (!mainClip.attachments) {\n mainClip.attachments = [];\n }\n\n // Extract animation effect\n const animationEffect = attachmentClip.effects?.find(\n (e) => e.effectType === 'animation'\n ) as AnimationEffect | undefined;\n\n // Extract target dimensions from metadata\n const targetWidth = attachmentClip.metadata?.targetWidth as number | undefined;\n const targetHeight = attachmentClip.metadata?.targetHeight as number | undefined;\n\n const attachmentData: Record<string, unknown> = {\n animation: animationEffect?.params,\n overlayClipStartUs: attachmentClip.startUs,\n mainClipStartUs: mainClip.startUs,\n ...(targetWidth !== undefined && { targetWidth }),\n ...(targetHeight !== undefined && { targetHeight }),\n };\n\n // Add resourceId if exists (trackKind not yet set, check field directly)\n if ('resourceId' in attachmentClip && attachmentClip.resourceId) {\n attachmentData.resourceId = attachmentClip.resourceId;\n }\n\n // Add text if exists (trackKind not yet set, check field directly)\n if ('text' in attachmentClip && attachmentClip.text) {\n attachmentData.text = attachmentClip.text;\n }\n\n mainClip.attachments.push({\n id: `${purpose}-${attachmentClip.id}-${mainClip.id}`,\n kind: purpose,\n startUs: overlap.clipRelativeStart,\n durationUs: overlap.duration,\n data: attachmentData,\n });\n }\n }\n }\n }\n\n private getTimeOverlap(\n attachmentClip: Clip,\n mainClip: Clip\n ): { clipRelativeStart: number; duration: number } | null {\n const attachmentStart = attachmentClip.startUs;\n const attachmentEnd = attachmentClip.startUs + attachmentClip.durationUs;\n const mainStart = mainClip.startUs;\n const mainEnd = mainClip.startUs + mainClip.durationUs;\n\n if (attachmentEnd <= mainStart || attachmentStart >= mainEnd) {\n return null;\n }\n\n const overlapStart = Math.max(attachmentStart, mainStart);\n const overlapEnd = Math.min(attachmentEnd, mainEnd);\n const duration = overlapEnd - overlapStart;\n const clipRelativeStart = overlapStart - mainStart;\n\n return { clipRelativeStart, duration };\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/model/patch.js
CHANGED
|
@@ -23,8 +23,7 @@ function collectAffectedClips(model, op, affectedClips) {
|
|
|
23
23
|
}
|
|
24
24
|
case "addClip":
|
|
25
25
|
case "removeClip":
|
|
26
|
-
case "updateClip":
|
|
27
|
-
case "moveClip": {
|
|
26
|
+
case "updateClip": {
|
|
28
27
|
const clipOp = op;
|
|
29
28
|
if (clipOp.clipId) {
|
|
30
29
|
affectedClips.add(clipOp.clipId);
|
|
@@ -102,9 +101,6 @@ function applyOperation(model, op) {
|
|
|
102
101
|
case "updateClip":
|
|
103
102
|
updateClip(model, op);
|
|
104
103
|
break;
|
|
105
|
-
case "moveClip":
|
|
106
|
-
moveClip(model, op);
|
|
107
|
-
break;
|
|
108
104
|
case "addResource":
|
|
109
105
|
addResource(model, op);
|
|
110
106
|
break;
|
|
@@ -257,26 +253,6 @@ function updateClip(model, op) {
|
|
|
257
253
|
model.recalculateDuration();
|
|
258
254
|
}
|
|
259
255
|
}
|
|
260
|
-
function moveClip(model, op) {
|
|
261
|
-
if (!op.clipId || !op.targetTrackId) return;
|
|
262
|
-
const sourceTrack = model.findTrack(op.trackId);
|
|
263
|
-
const targetTrack = model.findTrack(op.targetTrackId);
|
|
264
|
-
const clip = model.findClip(op.clipId);
|
|
265
|
-
if (!sourceTrack || !targetTrack || !clip) return;
|
|
266
|
-
const clipIndex = sourceTrack.clips.findIndex((c) => c.id === op.clipId);
|
|
267
|
-
if (clipIndex === -1) return;
|
|
268
|
-
sourceTrack.clips.splice(clipIndex, 1);
|
|
269
|
-
if (op.targetStartUs !== void 0) {
|
|
270
|
-
clip.startUs = op.targetStartUs;
|
|
271
|
-
}
|
|
272
|
-
const insertIndex = targetTrack.clips.findIndex((c) => c.startUs > clip.startUs);
|
|
273
|
-
if (insertIndex === -1) {
|
|
274
|
-
targetTrack.clips.push(clip);
|
|
275
|
-
} else {
|
|
276
|
-
targetTrack.clips.splice(insertIndex, 0, clip);
|
|
277
|
-
}
|
|
278
|
-
rebuildIndexes(model);
|
|
279
|
-
}
|
|
280
256
|
function addResource(model, op) {
|
|
281
257
|
if (!op.resource) return;
|
|
282
258
|
const newResource = {
|