@meframe/core 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +3 -3
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +10 -24
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +134 -182
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/cache/l1/VideoL1Cache.d.ts +4 -2
  9. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  10. package/dist/cache/l1/VideoL1Cache.js +17 -3
  11. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  12. package/dist/controllers/PlaybackController.d.ts +3 -1
  13. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  14. package/dist/controllers/PlaybackController.js +43 -25
  15. package/dist/controllers/PlaybackController.js.map +1 -1
  16. package/dist/controllers/PreRenderService.d.ts.map +1 -1
  17. package/dist/controllers/PreRenderService.js +6 -5
  18. package/dist/controllers/PreRenderService.js.map +1 -1
  19. package/dist/model/CompositionModel.d.ts.map +1 -1
  20. package/dist/model/CompositionModel.js +23 -14
  21. package/dist/model/CompositionModel.js.map +1 -1
  22. package/dist/model/dirty-range.d.ts.map +1 -1
  23. package/dist/model/patch.d.ts.map +1 -1
  24. package/dist/model/patch.js +60 -14
  25. package/dist/model/patch.js.map +1 -1
  26. package/dist/model/types.d.ts +23 -3
  27. package/dist/model/types.d.ts.map +1 -1
  28. package/dist/model/types.js +15 -0
  29. package/dist/model/types.js.map +1 -0
  30. package/dist/model/validation.d.ts.map +1 -1
  31. package/dist/model/validation.js +21 -4
  32. package/dist/model/validation.js.map +1 -1
  33. package/dist/orchestrator/ClipSessionManager.d.ts +2 -6
  34. package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
  35. package/dist/orchestrator/ClipSessionManager.js +3 -9
  36. package/dist/orchestrator/ClipSessionManager.js.map +1 -1
  37. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  38. package/dist/orchestrator/CompositionPlanner.js +9 -3
  39. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  40. package/dist/orchestrator/Orchestrator.d.ts +7 -2
  41. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  42. package/dist/orchestrator/Orchestrator.js +82 -14
  43. package/dist/orchestrator/Orchestrator.js.map +1 -1
  44. package/dist/orchestrator/VideoClipSession.d.ts +3 -0
  45. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  46. package/dist/orchestrator/VideoClipSession.js +31 -14
  47. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  48. package/dist/orchestrator/types.d.ts +1 -0
  49. package/dist/orchestrator/types.d.ts.map +1 -1
  50. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  51. package/dist/stages/compose/GlobalAudioSession.js +10 -2
  52. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  53. package/dist/stages/decode/BaseDecoder.js +130 -0
  54. package/dist/stages/decode/BaseDecoder.js.map +1 -0
  55. package/dist/stages/decode/VideoChunkDecoder.js +199 -0
  56. package/dist/stages/decode/VideoChunkDecoder.js.map +1 -0
  57. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  58. package/dist/stages/load/ResourceLoader.js +15 -17
  59. package/dist/stages/load/ResourceLoader.js.map +1 -1
  60. package/dist/utils/errors.d.ts +12 -0
  61. package/dist/utils/errors.d.ts.map +1 -0
  62. package/dist/utils/errors.js +11 -0
  63. package/dist/utils/errors.js.map +1 -0
  64. package/dist/worker/WorkerPool.d.ts +2 -2
  65. package/dist/worker/WorkerPool.d.ts.map +1 -1
  66. package/dist/worker/WorkerPool.js +7 -3
  67. package/dist/worker/WorkerPool.js.map +1 -1
  68. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
  69. package/dist/workers/stages/demux/video-demux.worker.js +1 -1
  70. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
  71. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sources":["../../src/model/validation.ts"],"sourcesContent":["import { Track, Resource, ValidationError } from './types';\n\n/**\n * Validate composition model structure\n */\nexport function validateCompositionStructure(data: any): ValidationError[] {\n const errors: ValidationError[] = [];\n\n // Validate root structure\n if (!data || typeof data !== 'object') {\n errors.push({ path: '/', message: 'Invalid root structure', value: data });\n }\n\n if (data.version !== '1.0') {\n errors.push({ path: '/version', message: 'Invalid version', value: data.version });\n }\n\n if (![24, 25, 30, 60].includes(data.fps)) {\n errors.push({ path: '/fps', message: 'Invalid fps', value: data.fps });\n }\n\n if (!Array.isArray(data.tracks)) {\n errors.push({ path: '/tracks', message: 'Tracks must be an array', value: data.tracks });\n }\n\n if (!data.resources || typeof data.resources !== 'object') {\n errors.push({ path: '/resources', message: 'Invalid resources', value: data.resources });\n }\n\n // Validate tracks\n if (Array.isArray(data.tracks)) {\n data.tracks.forEach((track: any, i: number) => {\n const trackPath = `/tracks[${i}]`;\n\n if (!track.id || typeof track.id !== 'string') {\n errors.push({ path: `${trackPath}/id`, message: 'Invalid track id', value: track.id });\n }\n\n if (!['video', 'audio', 'caption', 'fx'].includes(track.kind)) {\n errors.push({\n path: `${trackPath}/kind`,\n message: 'Invalid track kind',\n value: track.kind,\n });\n }\n\n if (!Array.isArray(track.clips)) {\n errors.push({\n path: `${trackPath}/clips`,\n message: 'Clips must be an array',\n value: track.clips,\n });\n }\n\n // Validate clips\n if (Array.isArray(track.clips)) {\n track.clips.forEach((clip: any, j: number) => {\n const clipPath = `${trackPath}/clips[${j}]`;\n\n if (!clip.id || typeof clip.id !== 'string') {\n errors.push({ path: `${clipPath}/id`, message: 'Invalid clip id', value: clip.id });\n }\n\n if (!clip.resourceId || typeof clip.resourceId !== 'string') {\n errors.push({\n path: `${clipPath}/resourceId`,\n message: 'Invalid resourceId',\n value: clip.resourceId,\n });\n }\n\n if (typeof clip.startUs !== 'number' || clip.startUs < 0) {\n errors.push({\n path: `${clipPath}/startUs`,\n message: 'Invalid startUs',\n value: clip.startUs,\n });\n }\n\n if (typeof clip.durationUs !== 'number' || clip.durationUs <= 0) {\n errors.push({\n path: `${clipPath}/durationUs`,\n message: 'Invalid durationUs',\n value: clip.durationUs,\n });\n }\n });\n }\n });\n }\n\n return errors;\n}\n\n/**\n * Check if there are time overlaps in tracks\n */\nexport function checkTimeOverlaps(tracks: Track[]): boolean {\n for (const track of tracks) {\n for (let i = 0; i < track.clips.length - 1; i++) {\n const currentClip = track.clips[i];\n const nextClip = track.clips[i + 1];\n if (!currentClip || !nextClip) continue;\n const currentEndUs = currentClip.startUs + currentClip.durationUs;\n\n if (currentEndUs > nextClip.startUs) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Validate that all resource references exist\n */\nexport function validateResourceReferences(\n tracks: Track[],\n resources: Map<string, Resource>\n): boolean {\n for (const track of tracks) {\n for (const clip of track.clips) {\n if (!resources.has(clip.resourceId)) {\n return false;\n }\n }\n }\n\n return true;\n}\n"],"names":[],"mappings":"AAKO,SAAS,6BAA6B,MAA8B;AACzE,QAAM,SAA4B,CAAA;AAGlC,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO,KAAK,EAAE,MAAM,KAAK,SAAS,0BAA0B,OAAO,MAAM;AAAA,EAC3E;AAEA,MAAI,KAAK,YAAY,OAAO;AAC1B,WAAO,KAAK,EAAE,MAAM,YAAY,SAAS,mBAAmB,OAAO,KAAK,SAAS;AAAA,EACnF;AAEA,MAAI,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,SAAS,KAAK,GAAG,GAAG;AACxC,WAAO,KAAK,EAAE,MAAM,QAAQ,SAAS,eAAe,OAAO,KAAK,KAAK;AAAA,EACvE;AAEA,MAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC/B,WAAO,KAAK,EAAE,MAAM,WAAW,SAAS,2BAA2B,OAAO,KAAK,QAAQ;AAAA,EACzF;AAEA,MAAI,CAAC,KAAK,aAAa,OAAO,KAAK,cAAc,UAAU;AACzD,WAAO,KAAK,EAAE,MAAM,cAAc,SAAS,qBAAqB,OAAO,KAAK,WAAW;AAAA,EACzF;AAGA,MAAI,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC9B,SAAK,OAAO,QAAQ,CAAC,OAAY,MAAc;AAC7C,YAAM,YAAY,WAAW,CAAC;AAE9B,UAAI,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,UAAU;AAC7C,eAAO,KAAK,EAAE,MAAM,GAAG,SAAS,OAAO,SAAS,oBAAoB,OAAO,MAAM,GAAA,CAAI;AAAA,MACvF;AAEA,UAAI,CAAC,CAAC,SAAS,SAAS,WAAW,IAAI,EAAE,SAAS,MAAM,IAAI,GAAG;AAC7D,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,SAAS;AAAA,UAClB,SAAS;AAAA,UACT,OAAO,MAAM;AAAA,QAAA,CACd;AAAA,MACH;AAEA,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC/B,eAAO,KAAK;AAAA,UACV,MAAM,GAAG,SAAS;AAAA,UAClB,SAAS;AAAA,UACT,OAAO,MAAM;AAAA,QAAA,CACd;AAAA,MACH;AAGA,UAAI,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC9B,cAAM,MAAM,QAAQ,CAAC,MAAW,MAAc;AAC5C,gBAAM,WAAW,GAAG,SAAS,UAAU,CAAC;AAExC,cAAI,CAAC,KAAK,MAAM,OAAO,KAAK,OAAO,UAAU;AAC3C,mBAAO,KAAK,EAAE,MAAM,GAAG,QAAQ,OAAO,SAAS,mBAAmB,OAAO,KAAK,GAAA,CAAI;AAAA,UACpF;AAEA,cAAI,CAAC,KAAK,cAAc,OAAO,KAAK,eAAe,UAAU;AAC3D,mBAAO,KAAK;AAAA,cACV,MAAM,GAAG,QAAQ;AAAA,cACjB,SAAS;AAAA,cACT,OAAO,KAAK;AAAA,YAAA,CACb;AAAA,UACH;AAEA,cAAI,OAAO,KAAK,YAAY,YAAY,KAAK,UAAU,GAAG;AACxD,mBAAO,KAAK;AAAA,cACV,MAAM,GAAG,QAAQ;AAAA,cACjB,SAAS;AAAA,cACT,OAAO,KAAK;AAAA,YAAA,CACb;AAAA,UACH;AAEA,cAAI,OAAO,KAAK,eAAe,YAAY,KAAK,cAAc,GAAG;AAC/D,mBAAO,KAAK;AAAA,cACV,MAAM,GAAG,QAAQ;AAAA,cACjB,SAAS;AAAA,cACT,OAAO,KAAK;AAAA,YAAA,CACb;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"validation.js","sources":["../../src/model/validation.ts"],"sourcesContent":["import { Track, Resource, ValidationError } from './types';\n\n/**\n * Validate composition model structure\n */\nexport function validateCompositionStructure(data: any): ValidationError[] {\n const errors: ValidationError[] = [];\n\n // Validate root structure\n if (!data || typeof data !== 'object') {\n errors.push({ path: '/', message: 'Invalid root structure', value: data });\n }\n\n if (data.version !== '1.0') {\n errors.push({ path: '/version', message: 'Invalid version', value: data.version });\n }\n\n if (![24, 25, 30, 60].includes(data.fps)) {\n errors.push({ path: '/fps', message: 'Invalid fps', value: data.fps });\n }\n\n if (!Array.isArray(data.tracks)) {\n errors.push({ path: '/tracks', message: 'Tracks must be an array', value: data.tracks });\n }\n\n if (!data.resources || typeof data.resources !== 'object') {\n errors.push({ path: '/resources', message: 'Invalid resources', value: data.resources });\n }\n\n // Validate tracks\n if (Array.isArray(data.tracks)) {\n data.tracks.forEach((track: any, i: number) => {\n const trackPath = `/tracks[${i}]`;\n\n if (!track.id || typeof track.id !== 'string') {\n errors.push({ path: `${trackPath}/id`, message: 'Invalid track id', value: track.id });\n }\n\n if (!['video', 'audio', 'caption', 'fx'].includes(track.kind)) {\n errors.push({\n path: `${trackPath}/kind`,\n message: 'Invalid track kind',\n value: track.kind,\n });\n }\n\n if (!Array.isArray(track.clips)) {\n errors.push({\n path: `${trackPath}/clips`,\n message: 'Clips must be an array',\n value: track.clips,\n });\n }\n\n // Validate clips\n if (Array.isArray(track.clips)) {\n track.clips.forEach((clip: any, j: number) => {\n const clipPath = `${trackPath}/clips[${j}]`;\n\n if (!clip.id || typeof clip.id !== 'string') {\n errors.push({ path: `${clipPath}/id`, message: 'Invalid clip id', value: clip.id });\n }\n\n // Validate trackKind matches track kind\n if (clip.trackKind && clip.trackKind !== track.kind) {\n errors.push({\n path: `${clipPath}/trackKind`,\n message: `Clip trackKind (${clip.trackKind}) does not match track kind (${track.kind})`,\n value: clip.trackKind,\n });\n }\n\n // Validate based on track kind\n if (track.kind === 'video' || track.kind === 'audio') {\n // video/audio clips must have resourceId\n if (!clip.resourceId || typeof clip.resourceId !== 'string') {\n errors.push({\n path: `${clipPath}/resourceId`,\n message: `${track.kind} clip must have resourceId`,\n value: clip.resourceId,\n });\n }\n } else if (track.kind === 'caption') {\n // caption clips must have text\n if (!clip.text || typeof clip.text !== 'string') {\n errors.push({\n path: `${clipPath}/text`,\n message: 'Caption clip must have text',\n value: clip.text,\n });\n }\n }\n // fx clips don't require additional fields\n\n if (typeof clip.startUs !== 'number' || clip.startUs < 0) {\n errors.push({\n path: `${clipPath}/startUs`,\n message: 'Invalid startUs',\n value: clip.startUs,\n });\n }\n\n if (typeof clip.durationUs !== 'number' || clip.durationUs <= 0) {\n errors.push({\n path: `${clipPath}/durationUs`,\n message: 'Invalid durationUs',\n value: clip.durationUs,\n });\n }\n });\n }\n });\n }\n\n return errors;\n}\n\n/**\n * Check if there are time overlaps in tracks\n */\nexport function checkTimeOverlaps(tracks: Track[]): boolean {\n for (const track of tracks) {\n for (let i = 0; i < track.clips.length - 1; i++) {\n const currentClip = track.clips[i];\n const nextClip = track.clips[i + 1];\n if (!currentClip || !nextClip) continue;\n const currentEndUs = currentClip.startUs + currentClip.durationUs;\n\n if (currentEndUs > nextClip.startUs) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Validate that all resource references exist\n */\nexport function validateResourceReferences(\n tracks: Track[],\n resources: Map<string, Resource>\n): boolean {\n for (const track of tracks) {\n for (const clip of track.clips) {\n // Only check clips that have resourceId (video/audio clips)\n if ('resourceId' in clip && clip.resourceId) {\n if (!resources.has(clip.resourceId)) {\n return false;\n }\n }\n }\n }\n\n return true;\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,4 @@
1
1
  import { VideoClipSession } from './VideoClipSession';
2
- import { CompositionModel } from '../model';
3
- import { TimeUs } from '../model/types';
4
2
  import { ClipUpdateResult } from './CompositionPlanner';
5
3
  import { CacheManager } from '../cache/CacheManager';
6
4
 
@@ -21,19 +19,17 @@ export declare class ClipSessionManager {
21
19
  private inflightCount;
22
20
  private readonly maxConcurrent;
23
21
  private readonly factory;
24
- private readonly model;
25
22
  private readonly cacheManager;
26
23
  constructor(config: {
27
24
  maxConcurrent?: number;
28
25
  factory: ClipSessionFactory;
29
- model: () => CompositionModel | null;
30
26
  cacheManager: CacheManager;
31
27
  });
32
28
  /**
33
- * Ensure clips are active (Prev, Current, Next)
29
+ * Ensure clips are active (Current, Next)
34
30
  * Deactivates clips outside the set
35
31
  */
36
- ensureClips(timeUs: TimeUs): Promise<void>;
32
+ ensureClips(clipIds: string[]): Promise<void>;
37
33
  handlePlannerUpdate(clipId: string, update: ClipUpdateResult): Promise<void>;
38
34
  /**
39
35
  * Get session for a clip
@@ -1 +1 @@
1
- {"version":3,"file":"ClipSessionManager.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ClipSessionManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAW3E;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAEhC,MAAM,EAAE;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,kBAAkB,CAAC;QAC5B,KAAK,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;QACrC,YAAY,EAAE,YAAY,CAAC;KAC5B;IAOD;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC1C,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlF;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIxD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKrC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAI9C;;OAEG;IACH,UAAU;;;;;;;;;IAcV;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,iBAAiB;YAiCjB,YAAY;YA4BZ,cAAc;CAe7B"}
1
+ {"version":3,"file":"ClipSessionManager.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ClipSessionManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAW3E;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAEhC,MAAM,EAAE;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,kBAAkB,CAAC;QAC5B,YAAY,EAAE,YAAY,CAAC;KAC5B;IAMD;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7C,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlF;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIxD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKrC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAI9C;;OAEG;IACH,UAAU;;;;;;;;;IAcV;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,iBAAiB;YAiCjB,YAAY;YA4BZ,cAAc;CAY7B"}
@@ -4,22 +4,17 @@ class ClipSessionManager {
4
4
  inflightCount = 0;
5
5
  maxConcurrent;
6
6
  factory;
7
- model;
8
7
  cacheManager;
9
8
  constructor(config) {
10
- this.maxConcurrent = config.maxConcurrent ?? 3;
9
+ this.maxConcurrent = config.maxConcurrent ?? 4;
11
10
  this.factory = config.factory;
12
- this.model = config.model;
13
11
  this.cacheManager = config.cacheManager;
14
12
  }
15
13
  /**
16
- * Ensure clips are active (Prev, Current, Next)
14
+ * Ensure clips are active (Current, Next)
17
15
  * Deactivates clips outside the set
18
16
  */
19
- async ensureClips(timeUs) {
20
- const model = this.model();
21
- if (!model) return;
22
- const clipIds = model.getClipsToCacheAtTime(timeUs);
17
+ async ensureClips(clipIds) {
23
18
  const targetSet = new Set(clipIds);
24
19
  for (const [clipId, entry] of this.entries) {
25
20
  if (!targetSet.has(clipId) && entry.state === "active") {
@@ -150,7 +145,6 @@ class ClipSessionManager {
150
145
  await entry.session.dispose();
151
146
  entry.session = void 0;
152
147
  }
153
- this.cacheManager.evictClip(clipId);
154
148
  entry.state = "idle";
155
149
  this.entries.delete(clipId);
156
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ClipSessionManager.js","sources":["../../src/orchestrator/ClipSessionManager.ts"],"sourcesContent":["/**\n * ClipSessionManager: Manages VideoClipSession lifecycle with 2-Clip strategy\n *\n * Responsibilities:\n * - Maintains Prev/Current/Next clip sessions\n * - Controls concurrent clip activation (max 2)\n * - Evicts clips outside the active window\n */\n\nimport type { VideoClipSession } from './VideoClipSession';\nimport type { CompositionModel } from '../model';\nimport type { TimeUs } from '../model/types';\nimport { ClipUpdateResult } from './CompositionPlanner';\n\nimport type { CacheManager } from '../cache/CacheManager';\n\nexport interface ClipSessionFactory {\n createSession(clipId: string): Promise<VideoClipSession>;\n}\n\nexport type ClipSessionState = 'idle' | 'activating' | 'active' | 'failed';\n\ninterface SessionEntry {\n clipId: string;\n session?: VideoClipSession;\n state: ClipSessionState;\n activationPromise?: Promise<void>;\n lastAccess: number;\n retryCount: number;\n}\n\n/**\n * ClipSessionManager manages VideoClipSession instances using 2-Clip strategy\n *\n * At any time, maintains:\n * - Current clip (being played)\n * - Next clip (for forward playback)\n */\nexport class ClipSessionManager {\n private entries = new Map<string, SessionEntry>();\n private activeSet = new Set<string>();\n private inflightCount = 0;\n private readonly maxConcurrent: number;\n private readonly factory: ClipSessionFactory;\n private readonly model: () => CompositionModel | null;\n private readonly cacheManager: CacheManager;\n\n constructor(config: {\n maxConcurrent?: number;\n factory: ClipSessionFactory;\n model: () => CompositionModel | null;\n cacheManager: CacheManager;\n }) {\n this.maxConcurrent = config.maxConcurrent ?? 3;\n this.factory = config.factory;\n this.model = config.model;\n this.cacheManager = config.cacheManager;\n }\n\n /**\n * Ensure clips are active (Prev, Current, Next)\n * Deactivates clips outside the set\n */\n async ensureClips(timeUs: TimeUs): Promise<void> {\n const model = this.model();\n if (!model) return;\n\n const clipIds = model.getClipsToCacheAtTime(timeUs);\n const targetSet = new Set(clipIds);\n\n // Deactivate clips not in target set\n for (const [clipId, entry] of this.entries) {\n if (!targetSet.has(clipId) && entry.state === 'active') {\n await this.deactivateClip(clipId);\n this.cacheManager.evictClip(clipId);\n }\n }\n\n // Update active set\n this.activeSet = targetSet;\n\n // Activate missing clips\n const toActivate: string[] = [];\n for (const clipId of clipIds) {\n const entry = this.entries.get(clipId);\n if (!entry || entry.state === 'idle' || entry.state === 'failed') {\n toActivate.push(clipId);\n }\n }\n // Start activation with concurrency control\n for (const clipId of toActivate) {\n await this.enqueueActivation(clipId);\n }\n }\n\n async handlePlannerUpdate(clipId: string, update: ClipUpdateResult): Promise<void> {\n const session = this.getSession(clipId);\n if (!session) return;\n await session.handlePlannerUpdate(update);\n }\n\n /**\n * Get session for a clip\n */\n getSession(clipId: string): VideoClipSession | undefined {\n return this.entries.get(clipId)?.session;\n }\n\n /**\n * Check if a clip is active\n */\n isClipActive(clipId: string): boolean {\n const entry = this.entries.get(clipId);\n return entry?.state === 'active' || false;\n }\n\n /**\n * Get clip state\n */\n getClipState(clipId: string): ClipSessionState {\n return this.entries.get(clipId)?.state ?? 'idle';\n }\n\n /**\n * Get metrics for monitoring\n */\n getMetrics() {\n const states = { idle: 0, activating: 0, active: 0, failed: 0 };\n for (const entry of this.entries.values()) {\n states[entry.state]++;\n }\n\n return {\n total: this.entries.size,\n inflight: this.inflightCount,\n activeSessions: this.activeSet.size,\n ...states,\n };\n }\n\n /**\n * Clear all sessions\n */\n async clear(): Promise<void> {\n for (const clipId of this.entries.keys()) {\n await this.deactivateClip(clipId);\n }\n this.entries.clear();\n this.activeSet.clear();\n }\n\n /**\n * Dispose and cleanup\n */\n async dispose(): Promise<void> {\n await this.clear();\n }\n\n private async enqueueActivation(clipId: string): Promise<void> {\n let entry = this.entries.get(clipId);\n\n if (!entry) {\n entry = {\n clipId,\n state: 'idle',\n lastAccess: Date.now(),\n retryCount: 0,\n };\n this.entries.set(clipId, entry);\n }\n\n // Skip if already activating or active\n if (entry.state === 'activating' || entry.state === 'active') {\n return entry.activationPromise;\n }\n\n // Wait if max concurrent reached\n while (this.inflightCount >= this.maxConcurrent) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n entry.state = 'activating';\n entry.lastAccess = Date.now();\n this.inflightCount++;\n\n const activationPromise = this.activateClip(entry);\n entry.activationPromise = activationPromise;\n\n return activationPromise;\n }\n\n private async activateClip(entry: SessionEntry): Promise<void> {\n try {\n const session = await this.factory.createSession(entry.clipId);\n await session.activate();\n entry.session = session;\n entry.state = 'active';\n entry.retryCount = 0;\n } catch (error) {\n console.error(`[ClipSessionManager] Failed to activate clip ${entry.clipId}:`, error);\n entry.state = 'failed';\n entry.retryCount++;\n\n // Retry with exponential backoff (max 3 retries)\n if (entry.retryCount < 3) {\n const delay = Math.min(1000 * Math.pow(2, entry.retryCount), 5000);\n setTimeout(() => {\n if (this.activeSet.has(entry.clipId)) {\n entry.state = 'idle';\n void this.enqueueActivation(entry.clipId);\n }\n }, delay);\n }\n } finally {\n this.inflightCount--;\n entry.activationPromise = undefined;\n }\n }\n\n private async deactivateClip(clipId: string): Promise<void> {\n const entry = this.entries.get(clipId);\n if (!entry) return;\n\n if (entry.session) {\n await entry.session.dispose();\n entry.session = undefined;\n }\n\n // Evict clip from L1 cache to free memory\n this.cacheManager.evictClip(clipId);\n\n entry.state = 'idle';\n this.entries.delete(clipId);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClipSessionManager.js","sources":["../../src/orchestrator/ClipSessionManager.ts"],"sourcesContent":["/**\n * ClipSessionManager: Manages VideoClipSession lifecycle with 2-Clip strategy\n *\n * Responsibilities:\n * - Maintains Prev/Current/Next clip sessions\n * - Controls concurrent clip activation (max 2)\n * - Evicts clips outside the active window\n */\n\nimport type { VideoClipSession } from './VideoClipSession';\nimport { ClipUpdateResult } from './CompositionPlanner';\n\nimport type { CacheManager } from '../cache/CacheManager';\n\nexport interface ClipSessionFactory {\n createSession(clipId: string): Promise<VideoClipSession>;\n}\n\nexport type ClipSessionState = 'idle' | 'activating' | 'active' | 'failed';\n\ninterface SessionEntry {\n clipId: string;\n session?: VideoClipSession;\n state: ClipSessionState;\n activationPromise?: Promise<void>;\n lastAccess: number;\n retryCount: number;\n}\n\n/**\n * ClipSessionManager manages VideoClipSession instances using 2-Clip strategy\n *\n * At any time, maintains:\n * - Current clip (being played)\n * - Next clip (for forward playback)\n */\nexport class ClipSessionManager {\n private entries = new Map<string, SessionEntry>();\n private activeSet = new Set<string>();\n private inflightCount = 0;\n private readonly maxConcurrent: number;\n private readonly factory: ClipSessionFactory;\n private readonly cacheManager: CacheManager;\n\n constructor(config: {\n maxConcurrent?: number;\n factory: ClipSessionFactory;\n cacheManager: CacheManager;\n }) {\n this.maxConcurrent = config.maxConcurrent ?? 4;\n this.factory = config.factory;\n this.cacheManager = config.cacheManager;\n }\n\n /**\n * Ensure clips are active (Current, Next)\n * Deactivates clips outside the set\n */\n async ensureClips(clipIds: string[]): Promise<void> {\n const targetSet = new Set(clipIds);\n\n // Deactivate clips not in target set\n for (const [clipId, entry] of this.entries) {\n if (!targetSet.has(clipId) && entry.state === 'active') {\n await this.deactivateClip(clipId);\n this.cacheManager.evictClip(clipId);\n }\n }\n\n // Update active set\n this.activeSet = targetSet;\n\n // Activate missing clips\n const toActivate: string[] = [];\n for (const clipId of clipIds) {\n const entry = this.entries.get(clipId);\n if (!entry || entry.state === 'idle' || entry.state === 'failed') {\n toActivate.push(clipId);\n }\n }\n // Start activation with concurrency control\n for (const clipId of toActivate) {\n await this.enqueueActivation(clipId);\n }\n }\n\n async handlePlannerUpdate(clipId: string, update: ClipUpdateResult): Promise<void> {\n const session = this.getSession(clipId);\n if (!session) return;\n await session.handlePlannerUpdate(update);\n }\n\n /**\n * Get session for a clip\n */\n getSession(clipId: string): VideoClipSession | undefined {\n return this.entries.get(clipId)?.session;\n }\n\n /**\n * Check if a clip is active\n */\n isClipActive(clipId: string): boolean {\n const entry = this.entries.get(clipId);\n return entry?.state === 'active' || false;\n }\n\n /**\n * Get clip state\n */\n getClipState(clipId: string): ClipSessionState {\n return this.entries.get(clipId)?.state ?? 'idle';\n }\n\n /**\n * Get metrics for monitoring\n */\n getMetrics() {\n const states = { idle: 0, activating: 0, active: 0, failed: 0 };\n for (const entry of this.entries.values()) {\n states[entry.state]++;\n }\n\n return {\n total: this.entries.size,\n inflight: this.inflightCount,\n activeSessions: this.activeSet.size,\n ...states,\n };\n }\n\n /**\n * Clear all sessions\n */\n async clear(): Promise<void> {\n for (const clipId of this.entries.keys()) {\n await this.deactivateClip(clipId);\n }\n this.entries.clear();\n this.activeSet.clear();\n }\n\n /**\n * Dispose and cleanup\n */\n async dispose(): Promise<void> {\n await this.clear();\n }\n\n private async enqueueActivation(clipId: string): Promise<void> {\n let entry = this.entries.get(clipId);\n\n if (!entry) {\n entry = {\n clipId,\n state: 'idle',\n lastAccess: Date.now(),\n retryCount: 0,\n };\n this.entries.set(clipId, entry);\n }\n\n // Skip if already activating or active\n if (entry.state === 'activating' || entry.state === 'active') {\n return entry.activationPromise;\n }\n\n // Wait if max concurrent reached\n while (this.inflightCount >= this.maxConcurrent) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n entry.state = 'activating';\n entry.lastAccess = Date.now();\n this.inflightCount++;\n\n const activationPromise = this.activateClip(entry);\n entry.activationPromise = activationPromise;\n\n return activationPromise;\n }\n\n private async activateClip(entry: SessionEntry): Promise<void> {\n try {\n const session = await this.factory.createSession(entry.clipId);\n await session.activate();\n entry.session = session;\n entry.state = 'active';\n entry.retryCount = 0;\n } catch (error) {\n console.error(`[ClipSessionManager] Failed to activate clip ${entry.clipId}:`, error);\n entry.state = 'failed';\n entry.retryCount++;\n\n // Retry with exponential backoff (max 3 retries)\n if (entry.retryCount < 3) {\n const delay = Math.min(1000 * Math.pow(2, entry.retryCount), 5000);\n setTimeout(() => {\n if (this.activeSet.has(entry.clipId)) {\n entry.state = 'idle';\n void this.enqueueActivation(entry.clipId);\n }\n }, delay);\n }\n } finally {\n this.inflightCount--;\n entry.activationPromise = undefined;\n }\n }\n\n private async deactivateClip(clipId: string): Promise<void> {\n const entry = this.entries.get(clipId);\n if (!entry) return;\n\n if (entry.session) {\n await entry.session.dispose();\n entry.session = undefined;\n }\n\n entry.state = 'idle';\n this.entries.delete(clipId);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CompositionPlanner.d.ts","sourceRoot":"","sources":["../../src/orchestrator/CompositionPlanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,IAAI,EAKL,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EACV,kBAAkB,EAQnB,MAAM,gCAAgC,CAAC;AAExC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAcD,UAAU,oBAAoB;IAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAKvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAwB1D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAsBpD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,EAAE;IA0DtF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,QAAQ;IAqBlE,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,oBAAoB;IAuB5B,OAAO,CAAC,qBAAqB;IAgC7B,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,sBAAsB;IAmE9B,OAAO,CAAC,8BAA8B;IAetC,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,wBAAwB;IAIhC,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;CAmBzB"}
1
+ {"version":3,"file":"CompositionPlanner.d.ts","sourceRoot":"","sources":["../../src/orchestrator/CompositionPlanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,IAAI,EAKL,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EACV,kBAAkB,EAQnB,MAAM,gCAAgC,CAAC;AAExC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAcD,UAAU,oBAAoB;IAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAKvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAwB1D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAsBpD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,EAAE;IA0DtF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,QAAQ;IAqBlE,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,oBAAoB;IA0B5B,OAAO,CAAC,qBAAqB;IAgC7B,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,sBAAsB;IAmE9B,OAAO,CAAC,8BAA8B;IAetC,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,wBAAwB;IAIhC,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;CAqBzB"}
@@ -1,3 +1,4 @@
1
+ import { isVideoClip, hasResourceId } from "../model/types.js";
1
2
  const DEFAULT_COMPOSITION_WIDTH = 1280;
2
3
  const DEFAULT_COMPOSITION_HEIGHT = 720;
3
4
  const DEFAULT_COMPOSITION_FPS = 30;
@@ -187,6 +188,9 @@ class CompositionPlanner {
187
188
  return { layers, status: overallStatus, resources };
188
189
  }
189
190
  createBaseVideoLayer(clip, resources) {
191
+ if (!isVideoClip(clip)) {
192
+ throw new Error(`Clip ${clip.id} is not a video clip`);
193
+ }
190
194
  const resourceState = this.getResourceState(clip.resourceId);
191
195
  const status = resourceState === "ready" ? "ready" : "pending";
192
196
  this.registerResourceUsage(clip.resourceId, status, resources);
@@ -384,9 +388,11 @@ class CompositionPlanner {
384
388
  }
385
389
  needsPlanRefresh(clip, plan) {
386
390
  const baseLayer = plan.instructions.layers.find((layer) => layer.type === "video");
387
- const currentBaseState = this.getResourceState(clip.resourceId);
388
- if (baseLayer && baseLayer.status !== (currentBaseState === "ready" ? "ready" : "pending")) {
389
- return true;
391
+ if (hasResourceId(clip)) {
392
+ const currentBaseState = this.getResourceState(clip.resourceId);
393
+ if (baseLayer && baseLayer.status !== (currentBaseState === "ready" ? "ready" : "pending")) {
394
+ return true;
395
+ }
390
396
  }
391
397
  if (plan.resources.pending.size === 0) {
392
398
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"CompositionPlanner.js","sources":["../../src/orchestrator/CompositionPlanner.ts"],"sourcesContent":["import type {\n CompositionModel,\n CompositionPatch,\n Clip,\n Attachment,\n Transition,\n TimeUs,\n Resource,\n} from '../model';\nimport type { VideoComposeConfig } from '../stages/compose/types';\nimport type {\n ClipInstructionSet,\n SerializedLayerPlan,\n SerializedTransitionPlan,\n SerializedTextLayerPayload,\n SerializedImageLayerPayload,\n SerializedMaskLayerPayload,\n SerializedEffectLayerPayload,\n ClipInstructionStatus,\n} from '../stages/compose/instructions';\n\nexport type ClipUpdateType = 'update' | 'remove';\n\nexport interface ClipUpdateResult {\n clipId: string;\n trackId: string;\n revision: number;\n type: ClipUpdateType;\n instructions?: ClipInstructionSet;\n}\n\nconst DEFAULT_COMPOSITION_WIDTH = 1280;\nconst DEFAULT_COMPOSITION_HEIGHT = 720;\nconst DEFAULT_COMPOSITION_FPS = 30;\n\nconst ATTACHMENT_TYPE_MAP: Record<string, SerializedLayerPlan['type']> = {\n caption: 'text',\n overlay: 'image',\n mask: 'mask',\n};\n\nconst IMAGE_RESOURCE_TYPES = new Set(['image', 'sticker', 'mask']);\n\ninterface ClipPlanResourceRefs {\n pending: Set<string>;\n ready: Set<string>;\n}\n\ninterface ClipPlan {\n clipId: string;\n trackId: string;\n revision: number;\n instructions: ClipInstructionSet;\n resources: ClipPlanResourceRefs;\n}\n\nexport class CompositionPlanner {\n private model: CompositionModel | null = null;\n private readonly clipPlans = new Map<string, ClipPlan>();\n\n setModel(model: CompositionModel): void {\n this.model = model;\n this.clipPlans.clear();\n }\n\n getInstructions(clipId: string): ClipInstructionSet | null {\n const plan = this.clipPlans.get(clipId);\n if (plan) {\n const clip = this.model?.findClip(clipId);\n if (!clip) {\n return plan.instructions;\n }\n if (this.needsPlanRefresh(clip, plan)) {\n const refreshed = this.buildClipPlan(clip, { cache: true });\n return refreshed.instructions;\n }\n return plan.instructions;\n }\n if (!this.model) {\n return null;\n }\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n const newPlan = this.buildClipPlan(clip, { cache: true });\n return newPlan.instructions;\n }\n\n releaseClip(clipId: string): void {\n this.clipPlans.delete(clipId);\n }\n\n refreshClip(clipId: string): ClipUpdateResult | null {\n if (!this.model) {\n return null;\n }\n\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n\n const plan = this.buildClipPlan(clip, { cache: true });\n this.clipPlans.set(clipId, plan);\n\n return {\n clipId,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n };\n }\n\n /**\n * Apply patch and rebuild instructions for affected clips\n * Simplified for 2-Clip strategy - any change requires pipeline restart\n */\n applyPatch(_patch: CompositionPatch, affectedClipIds: Set<string>): ClipUpdateResult[] {\n if (!this.model) {\n return [];\n }\n const results: ClipUpdateResult[] = [];\n\n // Rebuild instructions for affected clips\n for (const clipId of affectedClipIds) {\n const clip = this.model.findClip(clipId);\n if (!clip) {\n // Clip was removed\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n continue;\n }\n\n // Rebuild plan for existing clip (any change = pipeline restart)\n const plan = this.buildClipPlan(clip, { cache: false });\n this.clipPlans.set(clip.id, plan);\n\n results.push({\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n });\n }\n\n // Check for orphaned clip plans (clips removed but not in affectedClipIds)\n for (const clipId of this.clipPlans.keys()) {\n if (!this.model.findClip(clipId) && !affectedClipIds.has(clipId)) {\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n }\n }\n\n return results;\n }\n\n buildClipPlan(clip: Clip, options?: { cache?: boolean }): ClipPlan {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const cache = options?.cache ?? true;\n const previous = this.clipPlans.get(clip.id);\n const revision = (previous?.revision ?? 0) + 1;\n const instructionContext = this.createInstructionSet(clip, revision);\n const plan: ClipPlan = {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n instructions: instructionContext.instructions,\n resources: instructionContext.resources,\n };\n if (cache) {\n this.clipPlans.set(clip.id, plan);\n }\n return plan;\n }\n\n private createInstructionSet(\n clip: Clip,\n revision: number\n ): { instructions: ClipInstructionSet; resources: ClipPlanResourceRefs } {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const baseConfig = this.buildBaseConfig(clip);\n const layerResult = this.buildLayerPlans(clip);\n const transitions = this.buildTransitionPlans(clip);\n const status = this.computeInstructionStatus(layerResult.layers);\n return {\n instructions: {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n baseConfig,\n layers: layerResult.layers,\n transitions,\n status,\n },\n resources: layerResult.resources,\n };\n }\n\n private buildBaseConfig(clip: Clip): VideoComposeConfig {\n const renderConfig = this.model?.renderConfig;\n return {\n width: renderConfig?.width ?? DEFAULT_COMPOSITION_WIDTH,\n height: renderConfig?.height ?? DEFAULT_COMPOSITION_HEIGHT,\n fps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n backgroundColor: renderConfig?.backgroundColor ?? '#000000',\n timeline: {\n clipId: clip.id,\n trackId: clip.trackId ?? 'main',\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n },\n };\n }\n\n private buildLayerPlans(clip: Clip): {\n layers: SerializedLayerPlan[];\n status: ClipInstructionStatus;\n resources: ClipPlanResourceRefs;\n } {\n const layers: SerializedLayerPlan[] = [];\n const resources: ClipPlanResourceRefs = {\n pending: new Set<string>(),\n ready: new Set<string>(),\n };\n const baseLayer = this.createBaseVideoLayer(clip, resources);\n layers.push(baseLayer);\n const attachments = clip.attachments ?? [];\n for (let index = 0; index < attachments.length; index += 1) {\n const attachment = attachments[index];\n if (attachment) {\n const layer = this.attachmentToLayerPlan(clip, attachment, index + 1, resources);\n layers.push(layer);\n }\n }\n const overallStatus = this.computeInstructionStatus(layers);\n return { layers, status: overallStatus, resources };\n }\n\n private createBaseVideoLayer(clip: Clip, resources: ClipPlanResourceRefs): SerializedLayerPlan {\n const resourceState = this.getResourceState(clip.resourceId);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n this.registerResourceUsage(clip.resourceId, status, resources);\n return {\n layerId: `${clip.id}-base-video`,\n type: 'video',\n activeRanges: [\n {\n startUs: 0,\n endUs: clip.durationUs,\n },\n ],\n payload: {\n resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n },\n status,\n zIndex: 0,\n };\n }\n\n private attachmentToLayerPlan(\n clip: Clip,\n attachment: Attachment,\n zIndex: number,\n resources: ClipPlanResourceRefs\n ): SerializedLayerPlan {\n const clipDuration = clip.durationUs;\n const startUs = Math.max(0, attachment.startUs);\n const endUs = Math.min(clipDuration, startUs + attachment.durationUs);\n const type = this.resolveAttachmentLayerType(attachment);\n const payload = this.buildAttachmentPayload(attachment, type);\n const resourceState = this.resolveAttachmentResourceState(attachment, type);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n const resourceId = (payload as any).resourceId;\n if (resourceId && typeof resourceId === 'string') {\n this.registerResourceUsage(resourceId, status, resources);\n }\n return {\n layerId: `${clip.id}-attachment-${attachment.id}`,\n type,\n activeRanges: [\n {\n startUs,\n endUs,\n },\n ],\n payload,\n status,\n zIndex,\n } as SerializedLayerPlan;\n }\n\n private resolveAttachmentLayerType(attachment: Attachment): SerializedLayerPlan['type'] {\n const mappedType = ATTACHMENT_TYPE_MAP[attachment.kind];\n if (mappedType) {\n return mappedType;\n }\n if (typeof attachment.data.text === 'string') {\n return 'text';\n }\n if (attachment.data.resourceId) {\n const resource = this.model?.getResource(attachment.data.resourceId as string);\n if (resource && IMAGE_RESOURCE_TYPES.has(resource.type)) {\n return 'image';\n }\n }\n return 'effect';\n }\n\n private buildAttachmentPayload(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): SerializedLayerPlan['payload'] {\n const basePayload: Record<string, unknown> = {\n ...attachment.data,\n attachmentId: attachment.id,\n };\n if (type === 'text') {\n // Apply default subtitle styles matching SubtitleComposer defaults\n return {\n text: this.getStringField(attachment.data, 'text') || '',\n fontFamily: this.getStringField(attachment.data, 'fontFamily') || 'Arial, sans-serif',\n fontSize: this.getNumberField(attachment.data, 'fontSize') ?? 40,\n fontWeight: this.getStringField(attachment.data, 'fontWeight') || '400',\n color: this.getStringField(attachment.data, 'color') || '#FFFFFF',\n strokeColor: this.getStringField(attachment.data, 'strokeColor') || '#000000',\n strokeWidth: this.getNumberField(attachment.data, 'strokeWidth') ?? 8,\n lineHeight: this.getNumberField(attachment.data, 'lineHeight') ?? 1.2,\n align: (this.getStringField(attachment.data, 'align') as any) || 'center',\n ...basePayload,\n } as SerializedTextLayerPayload;\n }\n if (type === 'image') {\n const imagePayload: SerializedImageLayerPayload = {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId') || '',\n } as SerializedImageLayerPayload;\n\n // Add target dimensions if specified (supports both number and string for percentages)\n if (attachment.data.targetWidth !== undefined) {\n const targetWidth = attachment.data.targetWidth;\n imagePayload.targetWidth =\n typeof targetWidth === 'string'\n ? targetWidth\n : (this.getNumberField(attachment.data, 'targetWidth') ?? undefined);\n }\n if (attachment.data.targetHeight !== undefined) {\n const targetHeight = attachment.data.targetHeight;\n imagePayload.targetHeight =\n typeof targetHeight === 'string'\n ? targetHeight\n : (this.getNumberField(attachment.data, 'targetHeight') ?? undefined);\n }\n\n // Add animation config for overlay attachments\n if (attachment.kind === 'overlay' && attachment.data.animation) {\n imagePayload.animation = {\n ...attachment.data.animation,\n overlayClipStartUs: attachment.data.overlayClipStartUs,\n mainClipStartUs: attachment.data.mainClipStartUs,\n } as any;\n }\n\n return imagePayload;\n }\n if (type === 'mask') {\n return {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId'),\n } as SerializedMaskLayerPayload;\n }\n return {\n ...basePayload,\n } as SerializedEffectLayerPayload;\n }\n\n private resolveAttachmentResourceState(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): 'ready' | 'pending' {\n if (type === 'text') {\n return 'ready';\n }\n const resourceId = this.getStringField(attachment.data, 'resourceId');\n if (!resourceId) {\n return 'pending';\n }\n const resourceState = this.getResourceState(resourceId);\n return resourceState === 'ready' ? 'ready' : 'pending';\n }\n\n private registerResourceUsage(\n identifier: string,\n status: ClipInstructionStatus,\n resources: ClipPlanResourceRefs\n ): void {\n if (!identifier) {\n return;\n }\n if (status === 'ready') {\n resources.ready.add(identifier);\n } else {\n resources.pending.add(identifier);\n }\n }\n\n private getResourceState(resourceId: string): Resource['state'] | 'pending' {\n const resource = this.model?.getResource(resourceId);\n return resource?.state ?? 'pending';\n }\n\n private computeInstructionStatus(layers: SerializedLayerPlan[]): ClipInstructionStatus {\n return layers.some((layer) => layer.status === 'pending') ? 'pending' : 'ready';\n }\n\n private buildTransitionPlans(clip: Clip): SerializedTransitionPlan[] {\n const transitions: SerializedTransitionPlan[] = [];\n const track = clip.trackId ? this.model?.findTrack(clip.trackId) : null;\n if (clip.transitionIn) {\n transitions.push(this.transitionToPlan(clip.transitionIn, 0, clip.durationUs));\n }\n if (clip.transitionOut) {\n const startUs = Math.max(0, clip.durationUs - clip.transitionOut.durationUs);\n transitions.push(this.transitionToPlan(clip.transitionOut, startUs, clip.durationUs));\n }\n if (track?.effects?.length) {\n for (const effect of track.effects) {\n transitions.push({\n transitionId: effect.id,\n range: {\n startUs: 0,\n endUs: clip.durationUs,\n },\n params: {\n type: effect.effectType,\n easing: effect.params?.easing as string | undefined,\n durationUs: effect.params?.durationUs as TimeUs | undefined,\n payload: effect.params,\n },\n });\n }\n }\n return transitions;\n }\n\n private transitionToPlan(\n transition: Transition,\n startUs: TimeUs,\n clipDurationUs: TimeUs\n ): SerializedTransitionPlan {\n const duration = Math.min(transition.durationUs, clipDurationUs);\n const clampedStart = Math.max(0, Math.min(startUs, clipDurationUs));\n const clampedEnd = Math.min(clampedStart + duration, clipDurationUs);\n return {\n transitionId: transition.id,\n range: {\n startUs: clampedStart,\n endUs: clampedEnd,\n },\n params: {\n type: transition.transitionType,\n ...transition.params,\n },\n };\n }\n\n private getStringField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key];\n return typeof value === 'string' ? value : undefined;\n }\n\n private getNumberField(data: Record<string, unknown>, key: string): number | undefined {\n const value = data[key];\n return typeof value === 'number' ? value : undefined;\n }\n\n private needsPlanRefresh(clip: Clip, plan: ClipPlan): boolean {\n const baseLayer = plan.instructions.layers.find((layer) => layer.type === 'video');\n const currentBaseState = this.getResourceState(clip.resourceId);\n if (baseLayer && baseLayer.status !== (currentBaseState === 'ready' ? 'ready' : 'pending')) {\n return true;\n }\n\n if (plan.resources.pending.size === 0) {\n return false;\n }\n\n for (const resourceId of plan.resources.pending) {\n if (this.getResourceState(resourceId) === 'ready') {\n return true;\n }\n }\n\n return false;\n }\n}\n"],"names":["clip","plan"],"mappings":"AA+BA,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AACnC,MAAM,0BAA0B;AAEhC,MAAM,sBAAmE;AAAA,EACvE,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AACR;AAEA,MAAM,uBAAuB,oBAAI,IAAI,CAAC,SAAS,WAAW,MAAM,CAAC;AAe1D,MAAM,mBAAmB;AAAA,EACtB,QAAiC;AAAA,EACxB,gCAAgB,IAAA;AAAA,EAEjC,SAAS,OAA+B;AACtC,SAAK,QAAQ;AACb,SAAK,UAAU,MAAA;AAAA,EACjB;AAAA,EAEA,gBAAgB,QAA2C;AACzD,UAAM,OAAO,KAAK,UAAU,IAAI,MAAM;AACtC,QAAI,MAAM;AACR,YAAMA,QAAO,KAAK,OAAO,SAAS,MAAM;AACxC,UAAI,CAACA,OAAM;AACT,eAAO,KAAK;AAAA,MACd;AACA,UAAI,KAAK,iBAAiBA,OAAM,IAAI,GAAG;AACrC,cAAM,YAAY,KAAK,cAAcA,OAAM,EAAE,OAAO,MAAM;AAC1D,eAAO,UAAU;AAAA,MACnB;AACA,aAAO,KAAK;AAAA,IACd;AACA,QAAI,CAAC,KAAK,OAAO;AACf,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,MAAM,SAAS,MAAM;AACvC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AACA,UAAM,UAAU,KAAK,cAAc,MAAM,EAAE,OAAO,MAAM;AACxD,WAAO,QAAQ;AAAA,EACjB;AAAA,EAEA,YAAY,QAAsB;AAChC,SAAK,UAAU,OAAO,MAAM;AAAA,EAC9B;AAAA,EAEA,YAAY,QAAyC;AACnD,QAAI,CAAC,KAAK,OAAO;AACf,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,KAAK,MAAM,SAAS,MAAM;AACvC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,KAAK,cAAc,MAAM,EAAE,OAAO,MAAM;AACrD,SAAK,UAAU,IAAI,QAAQ,IAAI;AAE/B,WAAO;AAAA,MACL;AAAA,MACA,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,MAAM;AAAA,MACN,cAAc,KAAK;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,QAA0B,iBAAkD;AACrF,QAAI,CAAC,KAAK,OAAO;AACf,aAAO,CAAA;AAAA,IACT;AACA,UAAM,UAA8B,CAAA;AAGpC,eAAW,UAAU,iBAAiB;AACpC,YAAM,OAAO,KAAK,MAAM,SAAS,MAAM;AACvC,UAAI,CAAC,MAAM;AAET,cAAMC,QAAO,KAAK,UAAU,IAAI,MAAM;AACtC,aAAK,UAAU,OAAO,MAAM;AAC5B,YAAIA,OAAM;AACR,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA,SAASA,MAAK;AAAA,YACd,UAAUA,MAAK,WAAW;AAAA,YAC1B,MAAM;AAAA,YACN,cAAc;AAAA,UAAA,CACf;AAAA,QACH;AACA;AAAA,MACF;AAGA,YAAM,OAAO,KAAK,cAAc,MAAM,EAAE,OAAO,OAAO;AACtD,WAAK,UAAU,IAAI,KAAK,IAAI,IAAI;AAEhC,cAAQ,KAAK;AAAA,QACX,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK;AAAA,QACd,UAAU,KAAK;AAAA,QACf,MAAM;AAAA,QACN,cAAc,KAAK;AAAA,MAAA,CACpB;AAAA,IACH;AAGA,eAAW,UAAU,KAAK,UAAU,KAAA,GAAQ;AAC1C,UAAI,CAAC,KAAK,MAAM,SAAS,MAAM,KAAK,CAAC,gBAAgB,IAAI,MAAM,GAAG;AAChE,cAAM,OAAO,KAAK,UAAU,IAAI,MAAM;AACtC,aAAK,UAAU,OAAO,MAAM;AAC5B,YAAI,MAAM;AACR,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA,SAAS,KAAK;AAAA,YACd,UAAU,KAAK,WAAW;AAAA,YAC1B,MAAM;AAAA,YACN,cAAc;AAAA,UAAA,CACf;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,cAAc,MAAY,SAAyC;AACjE,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,UAAM,QAAQ,SAAS,SAAS;AAChC,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK,EAAE;AAC3C,UAAM,YAAY,UAAU,YAAY,KAAK;AAC7C,UAAM,qBAAqB,KAAK,qBAAqB,MAAM,QAAQ;AACnE,UAAM,OAAiB;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,MACd;AAAA,MACA,cAAc,mBAAmB;AAAA,MACjC,WAAW,mBAAmB;AAAA,IAAA;AAEhC,QAAI,OAAO;AACT,WAAK,UAAU,IAAI,KAAK,IAAI,IAAI;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,qBACN,MACA,UACuE;AACvE,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,UAAM,aAAa,KAAK,gBAAgB,IAAI;AAC5C,UAAM,cAAc,KAAK,gBAAgB,IAAI;AAC7C,UAAM,cAAc,KAAK,qBAAqB,IAAI;AAClD,UAAM,SAAS,KAAK,yBAAyB,YAAY,MAAM;AAC/D,WAAO;AAAA,MACL,cAAc;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK;AAAA,QACd;AAAA,QACA;AAAA,QACA,QAAQ,YAAY;AAAA,QACpB;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,WAAW,YAAY;AAAA,IAAA;AAAA,EAE3B;AAAA,EAEQ,gBAAgB,MAAgC;AACtD,UAAM,eAAe,KAAK,OAAO;AACjC,WAAO;AAAA,MACL,OAAO,cAAc,SAAS;AAAA,MAC9B,QAAQ,cAAc,UAAU;AAAA,MAChC,KAAK,KAAK,OAAO,OAAO;AAAA,MACxB,iBAAiB,cAAc,mBAAmB;AAAA,MAClD,UAAU;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK,WAAW;AAAA,QACzB,aAAa,KAAK;AAAA,QAClB,gBAAgB,KAAK;AAAA,QACrB,gBAAgB,KAAK,OAAO,OAAO;AAAA,MAAA;AAAA,IACrC;AAAA,EAEJ;AAAA,EAEQ,gBAAgB,MAItB;AACA,UAAM,SAAgC,CAAA;AACtC,UAAM,YAAkC;AAAA,MACtC,6BAAa,IAAA;AAAA,MACb,2BAAW,IAAA;AAAA,IAAY;AAEzB,UAAM,YAAY,KAAK,qBAAqB,MAAM,SAAS;AAC3D,WAAO,KAAK,SAAS;AACrB,UAAM,cAAc,KAAK,eAAe,CAAA;AACxC,aAAS,QAAQ,GAAG,QAAQ,YAAY,QAAQ,SAAS,GAAG;AAC1D,YAAM,aAAa,YAAY,KAAK;AACpC,UAAI,YAAY;AACd,cAAM,QAAQ,KAAK,sBAAsB,MAAM,YAAY,QAAQ,GAAG,SAAS;AAC/E,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AACA,UAAM,gBAAgB,KAAK,yBAAyB,MAAM;AAC1D,WAAO,EAAE,QAAQ,QAAQ,eAAe,UAAA;AAAA,EAC1C;AAAA,EAEQ,qBAAqB,MAAY,WAAsD;AAC7F,UAAM,gBAAgB,KAAK,iBAAiB,KAAK,UAAU;AAC3D,UAAM,SAAgC,kBAAkB,UAAU,UAAU;AAC5E,SAAK,sBAAsB,KAAK,YAAY,QAAQ,SAAS;AAC7D,WAAO;AAAA,MACL,SAAS,GAAG,KAAK,EAAE;AAAA,MACnB,MAAM;AAAA,MACN,cAAc;AAAA,QACZ;AAAA,UACE,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,QAAA;AAAA,MACd;AAAA,MAEF,SAAS;AAAA,QACP,YAAY,KAAK;AAAA,QACjB,aAAa,KAAK,eAAe;AAAA,QACjC,YAAY,KAAK;AAAA,MAAA;AAAA,MAEnB;AAAA,MACA,QAAQ;AAAA,IAAA;AAAA,EAEZ;AAAA,EAEQ,sBACN,MACA,YACA,QACA,WACqB;AACrB,UAAM,eAAe,KAAK;AAC1B,UAAM,UAAU,KAAK,IAAI,GAAG,WAAW,OAAO;AAC9C,UAAM,QAAQ,KAAK,IAAI,cAAc,UAAU,WAAW,UAAU;AACpE,UAAM,OAAO,KAAK,2BAA2B,UAAU;AACvD,UAAM,UAAU,KAAK,uBAAuB,YAAY,IAAI;AAC5D,UAAM,gBAAgB,KAAK,+BAA+B,YAAY,IAAI;AAC1E,UAAM,SAAgC,kBAAkB,UAAU,UAAU;AAC5E,UAAM,aAAc,QAAgB;AACpC,QAAI,cAAc,OAAO,eAAe,UAAU;AAChD,WAAK,sBAAsB,YAAY,QAAQ,SAAS;AAAA,IAC1D;AACA,WAAO;AAAA,MACL,SAAS,GAAG,KAAK,EAAE,eAAe,WAAW,EAAE;AAAA,MAC/C;AAAA,MACA,cAAc;AAAA,QACZ;AAAA,UACE;AAAA,UACA;AAAA,QAAA;AAAA,MACF;AAAA,MAEF;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEQ,2BAA2B,YAAqD;AACtF,UAAM,aAAa,oBAAoB,WAAW,IAAI;AACtD,QAAI,YAAY;AACd,aAAO;AAAA,IACT;AACA,QAAI,OAAO,WAAW,KAAK,SAAS,UAAU;AAC5C,aAAO;AAAA,IACT;AACA,QAAI,WAAW,KAAK,YAAY;AAC9B,YAAM,WAAW,KAAK,OAAO,YAAY,WAAW,KAAK,UAAoB;AAC7E,UAAI,YAAY,qBAAqB,IAAI,SAAS,IAAI,GAAG;AACvD,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,uBACN,YACA,MACgC;AAChC,UAAM,cAAuC;AAAA,MAC3C,GAAG,WAAW;AAAA,MACd,cAAc,WAAW;AAAA,IAAA;AAE3B,QAAI,SAAS,QAAQ;AAEnB,aAAO;AAAA,QACL,MAAM,KAAK,eAAe,WAAW,MAAM,MAAM,KAAK;AAAA,QACtD,YAAY,KAAK,eAAe,WAAW,MAAM,YAAY,KAAK;AAAA,QAClE,UAAU,KAAK,eAAe,WAAW,MAAM,UAAU,KAAK;AAAA,QAC9D,YAAY,KAAK,eAAe,WAAW,MAAM,YAAY,KAAK;AAAA,QAClE,OAAO,KAAK,eAAe,WAAW,MAAM,OAAO,KAAK;AAAA,QACxD,aAAa,KAAK,eAAe,WAAW,MAAM,aAAa,KAAK;AAAA,QACpE,aAAa,KAAK,eAAe,WAAW,MAAM,aAAa,KAAK;AAAA,QACpE,YAAY,KAAK,eAAe,WAAW,MAAM,YAAY,KAAK;AAAA,QAClE,OAAQ,KAAK,eAAe,WAAW,MAAM,OAAO,KAAa;AAAA,QACjE,GAAG;AAAA,MAAA;AAAA,IAEP;AACA,QAAI,SAAS,SAAS;AACpB,YAAM,eAA4C;AAAA,QAChD,GAAG;AAAA,QACH,YAAY,KAAK,eAAe,WAAW,MAAM,YAAY,KAAK;AAAA,MAAA;AAIpE,UAAI,WAAW,KAAK,gBAAgB,QAAW;AAC7C,cAAM,cAAc,WAAW,KAAK;AACpC,qBAAa,cACX,OAAO,gBAAgB,WACnB,cACC,KAAK,eAAe,WAAW,MAAM,aAAa,KAAK;AAAA,MAChE;AACA,UAAI,WAAW,KAAK,iBAAiB,QAAW;AAC9C,cAAM,eAAe,WAAW,KAAK;AACrC,qBAAa,eACX,OAAO,iBAAiB,WACpB,eACC,KAAK,eAAe,WAAW,MAAM,cAAc,KAAK;AAAA,MACjE;AAGA,UAAI,WAAW,SAAS,aAAa,WAAW,KAAK,WAAW;AAC9D,qBAAa,YAAY;AAAA,UACvB,GAAG,WAAW,KAAK;AAAA,UACnB,oBAAoB,WAAW,KAAK;AAAA,UACpC,iBAAiB,WAAW,KAAK;AAAA,QAAA;AAAA,MAErC;AAEA,aAAO;AAAA,IACT;AACA,QAAI,SAAS,QAAQ;AACnB,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY,KAAK,eAAe,WAAW,MAAM,YAAY;AAAA,MAAA;AAAA,IAEjE;AACA,WAAO;AAAA,MACL,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA,EAEQ,+BACN,YACA,MACqB;AACrB,QAAI,SAAS,QAAQ;AACnB,aAAO;AAAA,IACT;AACA,UAAM,aAAa,KAAK,eAAe,WAAW,MAAM,YAAY;AACpE,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AACA,UAAM,gBAAgB,KAAK,iBAAiB,UAAU;AACtD,WAAO,kBAAkB,UAAU,UAAU;AAAA,EAC/C;AAAA,EAEQ,sBACN,YACA,QACA,WACM;AACN,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AACA,QAAI,WAAW,SAAS;AACtB,gBAAU,MAAM,IAAI,UAAU;AAAA,IAChC,OAAO;AACL,gBAAU,QAAQ,IAAI,UAAU;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,iBAAiB,YAAmD;AAC1E,UAAM,WAAW,KAAK,OAAO,YAAY,UAAU;AACnD,WAAO,UAAU,SAAS;AAAA,EAC5B;AAAA,EAEQ,yBAAyB,QAAsD;AACrF,WAAO,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,SAAS,IAAI,YAAY;AAAA,EAC1E;AAAA,EAEQ,qBAAqB,MAAwC;AACnE,UAAM,cAA0C,CAAA;AAChD,UAAM,QAAQ,KAAK,UAAU,KAAK,OAAO,UAAU,KAAK,OAAO,IAAI;AACnE,QAAI,KAAK,cAAc;AACrB,kBAAY,KAAK,KAAK,iBAAiB,KAAK,cAAc,GAAG,KAAK,UAAU,CAAC;AAAA,IAC/E;AACA,QAAI,KAAK,eAAe;AACtB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,aAAa,KAAK,cAAc,UAAU;AAC3E,kBAAY,KAAK,KAAK,iBAAiB,KAAK,eAAe,SAAS,KAAK,UAAU,CAAC;AAAA,IACtF;AACA,QAAI,OAAO,SAAS,QAAQ;AAC1B,iBAAW,UAAU,MAAM,SAAS;AAClC,oBAAY,KAAK;AAAA,UACf,cAAc,OAAO;AAAA,UACrB,OAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,KAAK;AAAA,UAAA;AAAA,UAEd,QAAQ;AAAA,YACN,MAAM,OAAO;AAAA,YACb,QAAQ,OAAO,QAAQ;AAAA,YACvB,YAAY,OAAO,QAAQ;AAAA,YAC3B,SAAS,OAAO;AAAA,UAAA;AAAA,QAClB,CACD;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBACN,YACA,SACA,gBAC0B;AAC1B,UAAM,WAAW,KAAK,IAAI,WAAW,YAAY,cAAc;AAC/D,UAAM,eAAe,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,cAAc,CAAC;AAClE,UAAM,aAAa,KAAK,IAAI,eAAe,UAAU,cAAc;AACnE,WAAO;AAAA,MACL,cAAc,WAAW;AAAA,MACzB,OAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,MAET,QAAQ;AAAA,QACN,MAAM,WAAW;AAAA,QACjB,GAAG,WAAW;AAAA,MAAA;AAAA,IAChB;AAAA,EAEJ;AAAA,EAEQ,eAAe,MAA+B,KAAiC;AACrF,UAAM,QAAQ,KAAK,GAAG;AACtB,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAAA,EAEQ,eAAe,MAA+B,KAAiC;AACrF,UAAM,QAAQ,KAAK,GAAG;AACtB,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAAA,EAEQ,iBAAiB,MAAY,MAAyB;AAC5D,UAAM,YAAY,KAAK,aAAa,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,OAAO;AACjF,UAAM,mBAAmB,KAAK,iBAAiB,KAAK,UAAU;AAC9D,QAAI,aAAa,UAAU,YAAY,qBAAqB,UAAU,UAAU,YAAY;AAC1F,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,UAAU,QAAQ,SAAS,GAAG;AACrC,aAAO;AAAA,IACT;AAEA,eAAW,cAAc,KAAK,UAAU,SAAS;AAC/C,UAAI,KAAK,iBAAiB,UAAU,MAAM,SAAS;AACjD,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"CompositionPlanner.js","sources":["../../src/orchestrator/CompositionPlanner.ts"],"sourcesContent":["import type {\n CompositionModel,\n CompositionPatch,\n Clip,\n Attachment,\n Transition,\n TimeUs,\n Resource,\n} from '../model';\nimport { hasResourceId, isVideoClip } from '../model/types';\nimport type { VideoComposeConfig } from '../stages/compose/types';\nimport type {\n ClipInstructionSet,\n SerializedLayerPlan,\n SerializedTransitionPlan,\n SerializedTextLayerPayload,\n SerializedImageLayerPayload,\n SerializedMaskLayerPayload,\n SerializedEffectLayerPayload,\n ClipInstructionStatus,\n} from '../stages/compose/instructions';\n\nexport type ClipUpdateType = 'update' | 'remove';\n\nexport interface ClipUpdateResult {\n clipId: string;\n trackId: string;\n revision: number;\n type: ClipUpdateType;\n instructions?: ClipInstructionSet;\n}\n\nconst DEFAULT_COMPOSITION_WIDTH = 1280;\nconst DEFAULT_COMPOSITION_HEIGHT = 720;\nconst DEFAULT_COMPOSITION_FPS = 30;\n\nconst ATTACHMENT_TYPE_MAP: Record<string, SerializedLayerPlan['type']> = {\n caption: 'text',\n overlay: 'image',\n mask: 'mask',\n};\n\nconst IMAGE_RESOURCE_TYPES = new Set(['image', 'sticker', 'mask']);\n\ninterface ClipPlanResourceRefs {\n pending: Set<string>;\n ready: Set<string>;\n}\n\ninterface ClipPlan {\n clipId: string;\n trackId: string;\n revision: number;\n instructions: ClipInstructionSet;\n resources: ClipPlanResourceRefs;\n}\n\nexport class CompositionPlanner {\n private model: CompositionModel | null = null;\n private readonly clipPlans = new Map<string, ClipPlan>();\n\n setModel(model: CompositionModel): void {\n this.model = model;\n this.clipPlans.clear();\n }\n\n getInstructions(clipId: string): ClipInstructionSet | null {\n const plan = this.clipPlans.get(clipId);\n if (plan) {\n const clip = this.model?.findClip(clipId);\n if (!clip) {\n return plan.instructions;\n }\n if (this.needsPlanRefresh(clip, plan)) {\n const refreshed = this.buildClipPlan(clip, { cache: true });\n return refreshed.instructions;\n }\n return plan.instructions;\n }\n if (!this.model) {\n return null;\n }\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n const newPlan = this.buildClipPlan(clip, { cache: true });\n return newPlan.instructions;\n }\n\n releaseClip(clipId: string): void {\n this.clipPlans.delete(clipId);\n }\n\n refreshClip(clipId: string): ClipUpdateResult | null {\n if (!this.model) {\n return null;\n }\n\n const clip = this.model.findClip(clipId);\n if (!clip) {\n return null;\n }\n\n const plan = this.buildClipPlan(clip, { cache: true });\n this.clipPlans.set(clipId, plan);\n\n return {\n clipId,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n };\n }\n\n /**\n * Apply patch and rebuild instructions for affected clips\n * Simplified for 2-Clip strategy - any change requires pipeline restart\n */\n applyPatch(_patch: CompositionPatch, affectedClipIds: Set<string>): ClipUpdateResult[] {\n if (!this.model) {\n return [];\n }\n const results: ClipUpdateResult[] = [];\n\n // Rebuild instructions for affected clips\n for (const clipId of affectedClipIds) {\n const clip = this.model.findClip(clipId);\n if (!clip) {\n // Clip was removed\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n continue;\n }\n\n // Rebuild plan for existing clip (any change = pipeline restart)\n const plan = this.buildClipPlan(clip, { cache: false });\n this.clipPlans.set(clip.id, plan);\n\n results.push({\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision: plan.revision,\n type: 'update',\n instructions: plan.instructions,\n });\n }\n\n // Check for orphaned clip plans (clips removed but not in affectedClipIds)\n for (const clipId of this.clipPlans.keys()) {\n if (!this.model.findClip(clipId) && !affectedClipIds.has(clipId)) {\n const plan = this.clipPlans.get(clipId);\n this.clipPlans.delete(clipId);\n if (plan) {\n results.push({\n clipId,\n trackId: plan.trackId,\n revision: plan.revision + 1,\n type: 'remove',\n instructions: undefined,\n });\n }\n }\n }\n\n return results;\n }\n\n buildClipPlan(clip: Clip, options?: { cache?: boolean }): ClipPlan {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const cache = options?.cache ?? true;\n const previous = this.clipPlans.get(clip.id);\n const revision = (previous?.revision ?? 0) + 1;\n const instructionContext = this.createInstructionSet(clip, revision);\n const plan: ClipPlan = {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n instructions: instructionContext.instructions,\n resources: instructionContext.resources,\n };\n if (cache) {\n this.clipPlans.set(clip.id, plan);\n }\n return plan;\n }\n\n private createInstructionSet(\n clip: Clip,\n revision: number\n ): { instructions: ClipInstructionSet; resources: ClipPlanResourceRefs } {\n if (!this.model) {\n throw new Error('No composition model set');\n }\n const baseConfig = this.buildBaseConfig(clip);\n const layerResult = this.buildLayerPlans(clip);\n const transitions = this.buildTransitionPlans(clip);\n const status = this.computeInstructionStatus(layerResult.layers);\n return {\n instructions: {\n clipId: clip.id,\n trackId: clip.trackId as string,\n revision,\n baseConfig,\n layers: layerResult.layers,\n transitions,\n status,\n },\n resources: layerResult.resources,\n };\n }\n\n private buildBaseConfig(clip: Clip): VideoComposeConfig {\n const renderConfig = this.model?.renderConfig;\n return {\n width: renderConfig?.width ?? DEFAULT_COMPOSITION_WIDTH,\n height: renderConfig?.height ?? DEFAULT_COMPOSITION_HEIGHT,\n fps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n backgroundColor: renderConfig?.backgroundColor ?? '#000000',\n timeline: {\n clipId: clip.id,\n trackId: clip.trackId ?? 'main',\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n compositionFps: this.model?.fps ?? DEFAULT_COMPOSITION_FPS,\n },\n };\n }\n\n private buildLayerPlans(clip: Clip): {\n layers: SerializedLayerPlan[];\n status: ClipInstructionStatus;\n resources: ClipPlanResourceRefs;\n } {\n const layers: SerializedLayerPlan[] = [];\n const resources: ClipPlanResourceRefs = {\n pending: new Set<string>(),\n ready: new Set<string>(),\n };\n const baseLayer = this.createBaseVideoLayer(clip, resources);\n layers.push(baseLayer);\n const attachments = clip.attachments ?? [];\n for (let index = 0; index < attachments.length; index += 1) {\n const attachment = attachments[index];\n if (attachment) {\n const layer = this.attachmentToLayerPlan(clip, attachment, index + 1, resources);\n layers.push(layer);\n }\n }\n const overallStatus = this.computeInstructionStatus(layers);\n return { layers, status: overallStatus, resources };\n }\n\n private createBaseVideoLayer(clip: Clip, resources: ClipPlanResourceRefs): SerializedLayerPlan {\n if (!isVideoClip(clip)) {\n throw new Error(`Clip ${clip.id} is not a video clip`);\n }\n const resourceState = this.getResourceState(clip.resourceId);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n this.registerResourceUsage(clip.resourceId, status, resources);\n return {\n layerId: `${clip.id}-base-video`,\n type: 'video',\n activeRanges: [\n {\n startUs: 0,\n endUs: clip.durationUs,\n },\n ],\n payload: {\n resourceId: clip.resourceId,\n trimStartUs: clip.trimStartUs ?? 0,\n durationUs: clip.durationUs,\n },\n status,\n zIndex: 0,\n };\n }\n\n private attachmentToLayerPlan(\n clip: Clip,\n attachment: Attachment,\n zIndex: number,\n resources: ClipPlanResourceRefs\n ): SerializedLayerPlan {\n const clipDuration = clip.durationUs;\n const startUs = Math.max(0, attachment.startUs);\n const endUs = Math.min(clipDuration, startUs + attachment.durationUs);\n const type = this.resolveAttachmentLayerType(attachment);\n const payload = this.buildAttachmentPayload(attachment, type);\n const resourceState = this.resolveAttachmentResourceState(attachment, type);\n const status: ClipInstructionStatus = resourceState === 'ready' ? 'ready' : 'pending';\n const resourceId = (payload as any).resourceId;\n if (resourceId && typeof resourceId === 'string') {\n this.registerResourceUsage(resourceId, status, resources);\n }\n return {\n layerId: `${clip.id}-attachment-${attachment.id}`,\n type,\n activeRanges: [\n {\n startUs,\n endUs,\n },\n ],\n payload,\n status,\n zIndex,\n } as SerializedLayerPlan;\n }\n\n private resolveAttachmentLayerType(attachment: Attachment): SerializedLayerPlan['type'] {\n const mappedType = ATTACHMENT_TYPE_MAP[attachment.kind];\n if (mappedType) {\n return mappedType;\n }\n if (typeof attachment.data.text === 'string') {\n return 'text';\n }\n if (attachment.data.resourceId) {\n const resource = this.model?.getResource(attachment.data.resourceId as string);\n if (resource && IMAGE_RESOURCE_TYPES.has(resource.type)) {\n return 'image';\n }\n }\n return 'effect';\n }\n\n private buildAttachmentPayload(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): SerializedLayerPlan['payload'] {\n const basePayload: Record<string, unknown> = {\n ...attachment.data,\n attachmentId: attachment.id,\n };\n if (type === 'text') {\n // Apply default subtitle styles matching SubtitleComposer defaults\n return {\n text: this.getStringField(attachment.data, 'text') || '',\n fontFamily: this.getStringField(attachment.data, 'fontFamily') || 'Arial, sans-serif',\n fontSize: this.getNumberField(attachment.data, 'fontSize') ?? 40,\n fontWeight: this.getStringField(attachment.data, 'fontWeight') || '400',\n color: this.getStringField(attachment.data, 'color') || '#FFFFFF',\n strokeColor: this.getStringField(attachment.data, 'strokeColor') || '#000000',\n strokeWidth: this.getNumberField(attachment.data, 'strokeWidth') ?? 8,\n lineHeight: this.getNumberField(attachment.data, 'lineHeight') ?? 1.2,\n align: (this.getStringField(attachment.data, 'align') as any) || 'center',\n ...basePayload,\n } as SerializedTextLayerPayload;\n }\n if (type === 'image') {\n const imagePayload: SerializedImageLayerPayload = {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId') || '',\n } as SerializedImageLayerPayload;\n\n // Add target dimensions if specified (supports both number and string for percentages)\n if (attachment.data.targetWidth !== undefined) {\n const targetWidth = attachment.data.targetWidth;\n imagePayload.targetWidth =\n typeof targetWidth === 'string'\n ? targetWidth\n : (this.getNumberField(attachment.data, 'targetWidth') ?? undefined);\n }\n if (attachment.data.targetHeight !== undefined) {\n const targetHeight = attachment.data.targetHeight;\n imagePayload.targetHeight =\n typeof targetHeight === 'string'\n ? targetHeight\n : (this.getNumberField(attachment.data, 'targetHeight') ?? undefined);\n }\n\n // Add animation config for overlay attachments\n if (attachment.kind === 'overlay' && attachment.data.animation) {\n imagePayload.animation = {\n ...attachment.data.animation,\n overlayClipStartUs: attachment.data.overlayClipStartUs,\n mainClipStartUs: attachment.data.mainClipStartUs,\n } as any;\n }\n\n return imagePayload;\n }\n if (type === 'mask') {\n return {\n ...basePayload,\n resourceId: this.getStringField(attachment.data, 'resourceId'),\n } as SerializedMaskLayerPayload;\n }\n return {\n ...basePayload,\n } as SerializedEffectLayerPayload;\n }\n\n private resolveAttachmentResourceState(\n attachment: Attachment,\n type: SerializedLayerPlan['type']\n ): 'ready' | 'pending' {\n if (type === 'text') {\n return 'ready';\n }\n const resourceId = this.getStringField(attachment.data, 'resourceId');\n if (!resourceId) {\n return 'pending';\n }\n const resourceState = this.getResourceState(resourceId);\n return resourceState === 'ready' ? 'ready' : 'pending';\n }\n\n private registerResourceUsage(\n identifier: string,\n status: ClipInstructionStatus,\n resources: ClipPlanResourceRefs\n ): void {\n if (!identifier) {\n return;\n }\n if (status === 'ready') {\n resources.ready.add(identifier);\n } else {\n resources.pending.add(identifier);\n }\n }\n\n private getResourceState(resourceId: string): Resource['state'] | 'pending' {\n const resource = this.model?.getResource(resourceId);\n return resource?.state ?? 'pending';\n }\n\n private computeInstructionStatus(layers: SerializedLayerPlan[]): ClipInstructionStatus {\n return layers.some((layer) => layer.status === 'pending') ? 'pending' : 'ready';\n }\n\n private buildTransitionPlans(clip: Clip): SerializedTransitionPlan[] {\n const transitions: SerializedTransitionPlan[] = [];\n const track = clip.trackId ? this.model?.findTrack(clip.trackId) : null;\n if (clip.transitionIn) {\n transitions.push(this.transitionToPlan(clip.transitionIn, 0, clip.durationUs));\n }\n if (clip.transitionOut) {\n const startUs = Math.max(0, clip.durationUs - clip.transitionOut.durationUs);\n transitions.push(this.transitionToPlan(clip.transitionOut, startUs, clip.durationUs));\n }\n if (track?.effects?.length) {\n for (const effect of track.effects) {\n transitions.push({\n transitionId: effect.id,\n range: {\n startUs: 0,\n endUs: clip.durationUs,\n },\n params: {\n type: effect.effectType,\n easing: effect.params?.easing as string | undefined,\n durationUs: effect.params?.durationUs as TimeUs | undefined,\n payload: effect.params,\n },\n });\n }\n }\n return transitions;\n }\n\n private transitionToPlan(\n transition: Transition,\n startUs: TimeUs,\n clipDurationUs: TimeUs\n ): SerializedTransitionPlan {\n const duration = Math.min(transition.durationUs, clipDurationUs);\n const clampedStart = Math.max(0, Math.min(startUs, clipDurationUs));\n const clampedEnd = Math.min(clampedStart + duration, clipDurationUs);\n return {\n transitionId: transition.id,\n range: {\n startUs: clampedStart,\n endUs: clampedEnd,\n },\n params: {\n type: transition.transitionType,\n ...transition.params,\n },\n };\n }\n\n private getStringField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key];\n return typeof value === 'string' ? value : undefined;\n }\n\n private getNumberField(data: Record<string, unknown>, key: string): number | undefined {\n const value = data[key];\n return typeof value === 'number' ? value : undefined;\n }\n\n private needsPlanRefresh(clip: Clip, plan: ClipPlan): boolean {\n const baseLayer = plan.instructions.layers.find((layer) => layer.type === 'video');\n if (hasResourceId(clip)) {\n const currentBaseState = this.getResourceState(clip.resourceId);\n if (baseLayer && baseLayer.status !== (currentBaseState === 'ready' ? 'ready' : 'pending')) {\n return true;\n }\n }\n\n if (plan.resources.pending.size === 0) {\n return false;\n }\n\n for (const resourceId of plan.resources.pending) {\n if (this.getResourceState(resourceId) === 'ready') {\n return true;\n }\n }\n\n return false;\n }\n}\n"],"names":["clip","plan"],"mappings":"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"}
@@ -25,6 +25,8 @@ export declare class Orchestrator implements IOrchestrator {
25
25
  private config;
26
26
  private clipSessionManager;
27
27
  private currentClipId;
28
+ private ensureCacheDebounceTimer;
29
+ private readonly ensureCacheDebounceDelay;
28
30
  readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;
29
31
  constructor(config: OrchestratorConfig);
30
32
  get workerStatus(): WorkerStatus;
@@ -37,11 +39,14 @@ export declare class Orchestrator implements IOrchestrator {
37
39
  private handleResourceStateChange;
38
40
  restartWorker(type: WorkerType, clipId?: string): Promise<void>;
39
41
  renderFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null>;
42
+ private decodeFromL2;
40
43
  /**
41
44
  * Ensure clips are cached using 2-Clip strategy
42
- * Called by PlaybackController
45
+ * Debounced to avoid excessive session activation during fast seek
46
+ * @param timeUs - Target time for cache window
47
+ * @param immediate - Skip debounce if true (used for initial load)
43
48
  */
44
- ensureClipCache(timeUs: TimeUs): Promise<void>;
49
+ ensureClipCache(timeUs: TimeUs, immediate?: boolean): Promise<void>;
45
50
  /**
46
51
  * Wait for clip cache to be ready for playback
47
52
  * Returns true if minimum cache is ready, false if timeout
@@ -1 +1 @@
1
- {"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"}
@@ -4,12 +4,15 @@ import { applyPatch } from "../model/patch.js";
4
4
  import { ResourceLoader, ResourceConflictError } from "../stages/load/ResourceLoader.js";
5
5
  import { CacheManager } from "../cache/CacheManager.js";
6
6
  import { ConfigLoader } from "../config/ConfigLoader.js";
7
+ import { hasResourceId } from "../model/types.js";
7
8
  import { MeframeEvent } from "../event/events.js";
8
9
  import { CompositionPlanner } from "./CompositionPlanner.js";
9
10
  import { VideoClipSession } from "./VideoClipSession.js";
10
11
  import { ClipSessionManager } from "./ClipSessionManager.js";
11
12
  import { GlobalAudioSession } from "../stages/compose/GlobalAudioSession.js";
12
13
  import { MuxManager } from "../stages/mux/MuxManager.js";
14
+ import { VideoChunkDecoder } from "../stages/decode/VideoChunkDecoder.js";
15
+ import { quantizeTimestampToFrame } from "../utils/time-utils.js";
13
16
  class Orchestrator {
14
17
  workers;
15
18
  eventBus;
@@ -24,6 +27,8 @@ class Orchestrator {
24
27
  config = ConfigLoader.getInstance().getConfig();
25
28
  clipSessionManager;
26
29
  currentClipId = null;
30
+ ensureCacheDebounceTimer = null;
31
+ ensureCacheDebounceDelay = 150;
27
32
  events;
28
33
  constructor(config) {
29
34
  this.eventBus = config.eventBus || new EventBus();
@@ -64,7 +69,6 @@ class Orchestrator {
64
69
  factory: {
65
70
  createSession: (clipId) => this.createSession(clipId)
66
71
  },
67
- model: () => this.compositionModel,
68
72
  cacheManager: this.cacheManager
69
73
  });
70
74
  this.audioSession = new GlobalAudioSession({
@@ -127,7 +131,6 @@ class Orchestrator {
127
131
  durationUs: model.durationUs
128
132
  });
129
133
  await this.audioSession.activateAllAudioClips();
130
- this.ensureClipCache(0);
131
134
  }
132
135
  async applyPatch(patch) {
133
136
  if (!this.compositionModel) {
@@ -203,7 +206,7 @@ class Orchestrator {
203
206
  throw new Error(`clipId required for restarting ${type} worker`);
204
207
  }
205
208
  this.workers.terminate(type, clipId);
206
- const worker = await this.workers.get(type, clipId);
209
+ const worker = await this.workers.getOrCreate(type, clipId);
207
210
  this.eventBus.emit(MeframeEvent.WorkerRestarted, {
208
211
  type,
209
212
  workerId: worker.getWorkerId(),
@@ -218,6 +221,7 @@ class Orchestrator {
218
221
  }
219
222
  async renderFrame(timeUs, options) {
220
223
  const signal = options?.signal;
224
+ const immediate = options?.immediate ?? true;
221
225
  if (!this.compositionModel) {
222
226
  throw new Error("No composition model set");
223
227
  }
@@ -227,10 +231,12 @@ class Orchestrator {
227
231
  }
228
232
  if (this.currentClipId !== clip.id) {
229
233
  this.currentClipId = clip.id;
230
- void this.ensureClipCache(timeUs);
234
+ void this.ensureClipCache(timeUs, immediate);
231
235
  }
232
- const relativeTimeUs = options?.relativeTimeUs ?? timeUs - clip.startUs;
233
- const cachedFrame = await this.cacheManager.getFrame(relativeTimeUs, clip.id);
236
+ let relativeTimeUs = options?.relativeTimeUs ?? timeUs - clip.startUs;
237
+ relativeTimeUs = quantizeTimestampToFrame(relativeTimeUs, 0, this.compositionModel.fps);
238
+ relativeTimeUs = Math.min(relativeTimeUs, clip.durationUs - 1);
239
+ const cachedFrame = this.cacheManager.getFrame(relativeTimeUs, clip.id);
234
240
  if (cachedFrame) {
235
241
  this.eventBus.emit(MeframeEvent.CacheHit, {
236
242
  timeUs,
@@ -239,7 +245,6 @@ class Orchestrator {
239
245
  });
240
246
  return cachedFrame;
241
247
  }
242
- void this.ensureClipCache(timeUs);
243
248
  this.eventBus.emit(MeframeEvent.CacheMiss, {
244
249
  timeUs,
245
250
  level: "L1",
@@ -248,17 +253,74 @@ class Orchestrator {
248
253
  if (signal?.aborted) {
249
254
  throw new DOMException("Render aborted", "AbortError");
250
255
  }
256
+ const l2Frame = await this.decodeFromL2(relativeTimeUs, clip);
257
+ if (l2Frame) {
258
+ return l2Frame;
259
+ }
251
260
  return null;
252
261
  }
262
+ async decodeFromL2(timeUs, clip) {
263
+ const { id, trackId, startUs } = clip;
264
+ const [chunkStream, metadata] = await Promise.all([
265
+ this.cacheManager.l2Cache.createReadStream(id, "video"),
266
+ this.cacheManager.l2Cache.getClipMetadata(id, "video")
267
+ ]);
268
+ if (!chunkStream || !metadata?.codec) {
269
+ return null;
270
+ }
271
+ const decoder = new VideoChunkDecoder(`l2-temp-${id}`, {
272
+ codec: metadata.codec,
273
+ width: metadata.codedWidth,
274
+ height: metadata.codedHeight,
275
+ description: metadata.description,
276
+ hardwareAcceleration: metadata.hardwareAcceleration || "no-preference"
277
+ });
278
+ try {
279
+ const decodeStream = chunkStream.pipeThrough(decoder.createStream());
280
+ let targetFrame = null;
281
+ await this.cacheManager.receiveComposedFrames(decodeStream, {
282
+ clipId: id,
283
+ trackId: trackId || this.compositionModel?.mainTrackId || "main",
284
+ fps: this.compositionModel?.fps ?? 30,
285
+ clipStartUs: startUs,
286
+ onFrame: (info) => {
287
+ if (info.timeUs === timeUs) {
288
+ targetFrame = this.cacheManager.getFrame(timeUs, id);
289
+ }
290
+ }
291
+ });
292
+ return targetFrame;
293
+ } finally {
294
+ await decoder.close();
295
+ }
296
+ }
253
297
  /**
254
298
  * Ensure clips are cached using 2-Clip strategy
255
- * Called by PlaybackController
299
+ * Debounced to avoid excessive session activation during fast seek
300
+ * @param timeUs - Target time for cache window
301
+ * @param immediate - Skip debounce if true (used for initial load)
256
302
  */
257
- async ensureClipCache(timeUs) {
258
- if (!this.compositionModel) {
259
- return;
303
+ async ensureClipCache(timeUs, immediate = false) {
304
+ const executeCache = async () => {
305
+ if (!this.compositionModel) return;
306
+ const clipIds = this.compositionModel.getClipsToCacheAtTime(timeUs);
307
+ if (clipIds.length === 0) return;
308
+ await this.clipSessionManager.ensureClips(clipIds);
309
+ };
310
+ if (this.ensureCacheDebounceTimer !== null) {
311
+ clearTimeout(this.ensureCacheDebounceTimer);
312
+ this.ensureCacheDebounceTimer = null;
260
313
  }
261
- await this.clipSessionManager.ensureClips(timeUs);
314
+ if (immediate) {
315
+ return executeCache();
316
+ }
317
+ return new Promise((resolve) => {
318
+ this.ensureCacheDebounceTimer = setTimeout(async () => {
319
+ this.ensureCacheDebounceTimer = null;
320
+ await executeCache();
321
+ resolve();
322
+ }, this.ensureCacheDebounceDelay);
323
+ });
262
324
  }
263
325
  /**
264
326
  * Wait for clip cache to be ready for playback
@@ -277,8 +339,9 @@ class Orchestrator {
277
339
  return true;
278
340
  }
279
341
  return this.cacheManager.waitForClipReady(currentClip.id, {
280
- minFrameCount: options?.minFrameCount ?? 30,
342
+ minFrameCount: options?.minFrameCount ?? 5,
281
343
  timeoutMs: options?.timeoutMs ?? 5e3
344
+ // Don't pass startTimeUs - count all frames in the clip
282
345
  });
283
346
  }
284
347
  /**
@@ -332,6 +395,7 @@ class Orchestrator {
332
395
  cacheManager: this.cacheManager,
333
396
  compositionModel: this.compositionModel,
334
397
  workerConfigs: this.buildWorkerConfigs(),
398
+ resourceLoader: this.resourceLoader,
335
399
  callbacks: {
336
400
  onComposedStreamReady: (stream, fps) => {
337
401
  if (options?.forL2Only) {
@@ -373,7 +437,7 @@ class Orchestrator {
373
437
  },
374
438
  onPipelineReady: async (attachmentResourceIds) => {
375
439
  const clip2 = this.compositionModel?.findClip(clipId);
376
- if (clip2?.resourceId) {
440
+ if (clip2 && hasResourceId(clip2)) {
377
441
  await this.resourceLoader.fetch(clip2.resourceId, {
378
442
  priority: options?.forL2Only ? "low" : "high",
379
443
  sessionId,
@@ -397,6 +461,10 @@ class Orchestrator {
397
461
  return session;
398
462
  }
399
463
  async dispose() {
464
+ if (this.ensureCacheDebounceTimer !== null) {
465
+ clearTimeout(this.ensureCacheDebounceTimer);
466
+ this.ensureCacheDebounceTimer = null;
467
+ }
400
468
  this.resourceLoader.dispose();
401
469
  await this.clipSessionManager.dispose();
402
470
  await this.cacheManager.clear();