@meframe/core 0.0.13 → 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.
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +3 -3
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +7 -20
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +129 -152
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +3 -1
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +16 -2
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +3 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +43 -24
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/PreRenderService.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.js +6 -5
- package/dist/controllers/PreRenderService.js.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +23 -14
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/dirty-range.d.ts.map +1 -1
- package/dist/model/patch.d.ts.map +1 -1
- package/dist/model/patch.js +60 -14
- package/dist/model/patch.js.map +1 -1
- package/dist/model/types.d.ts +23 -3
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js +15 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validation.d.ts.map +1 -1
- package/dist/model/validation.js +21 -4
- package/dist/model/validation.js.map +1 -1
- package/dist/orchestrator/ClipSessionManager.d.ts +1 -1
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
- package/dist/orchestrator/ClipSessionManager.js +2 -3
- package/dist/orchestrator/ClipSessionManager.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +9 -3
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +6 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +40 -25
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +3 -0
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +31 -14
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/types.d.ts +1 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +10 -2
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +15 -17
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/utils/errors.d.ts +12 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +11 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/worker/WorkerPool.d.ts +2 -2
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +7 -3
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
- package/dist/workers/stages/demux/video-demux.worker.js +1 -1
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
- 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
|
|
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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"}
|
|
@@ -26,7 +26,7 @@ export declare class ClipSessionManager {
|
|
|
26
26
|
cacheManager: CacheManager;
|
|
27
27
|
});
|
|
28
28
|
/**
|
|
29
|
-
* Ensure clips are active (
|
|
29
|
+
* Ensure clips are active (Current, Next)
|
|
30
30
|
* Deactivates clips outside the set
|
|
31
31
|
*/
|
|
32
32
|
ensureClips(clipIds: string[]): Promise<void>;
|
|
@@ -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,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;
|
|
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"}
|
|
@@ -6,12 +6,12 @@ class ClipSessionManager {
|
|
|
6
6
|
factory;
|
|
7
7
|
cacheManager;
|
|
8
8
|
constructor(config) {
|
|
9
|
-
this.maxConcurrent = config.maxConcurrent ??
|
|
9
|
+
this.maxConcurrent = config.maxConcurrent ?? 4;
|
|
10
10
|
this.factory = config.factory;
|
|
11
11
|
this.cacheManager = config.cacheManager;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Ensure clips are active (
|
|
14
|
+
* Ensure clips are active (Current, Next)
|
|
15
15
|
* Deactivates clips outside the set
|
|
16
16
|
*/
|
|
17
17
|
async ensureClips(clipIds) {
|
|
@@ -145,7 +145,6 @@ class ClipSessionManager {
|
|
|
145
145
|
await entry.session.dispose();
|
|
146
146
|
entry.session = void 0;
|
|
147
147
|
}
|
|
148
|
-
this.cacheManager.evictClip(clipId);
|
|
149
148
|
entry.state = "idle";
|
|
150
149
|
this.entries.delete(clipId);
|
|
151
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 { 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 ??
|
|
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;
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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":"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"}
|
|
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;
|
|
@@ -40,9 +42,11 @@ export declare class Orchestrator implements IOrchestrator {
|
|
|
40
42
|
private decodeFromL2;
|
|
41
43
|
/**
|
|
42
44
|
* Ensure clips are cached using 2-Clip strategy
|
|
43
|
-
*
|
|
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)
|
|
44
48
|
*/
|
|
45
|
-
ensureClipCache(timeUs: TimeUs): Promise<void>;
|
|
49
|
+
ensureClipCache(timeUs: TimeUs, immediate?: boolean): Promise<void>;
|
|
46
50
|
/**
|
|
47
51
|
* Wait for clip cache to be ready for playback
|
|
48
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAyB,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAY,MAAM,EAAE,OAAO,EAAQ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAyB,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAY,MAAM,EAAE,OAAO,EAAQ,MAAM,UAAU,CAAC;AAE/F,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIxC,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACjD,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC;IAEvB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,wBAAwB,CAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAO;IAChD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;gBAE5D,MAAM,EAAE,kBAAkB;IAqEtC,IAAI,YAAY,IAAI,YAAY,CAsB/B;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC,EAAE,CAAC,CAAC,SAAS,MAAM,eAAe,EAChC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAIP,GAAG,CAAC,CAAC,SAAS,MAAM,eAAe,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAIP,IAAI,CAAC,CAAC,SAAS,MAAM,eAAe,EAClC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAID,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3D,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCxD,OAAO,CAAC,yBAAyB;IAiD3B,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YA0D1E,YAAY;IA0C1B;;;;;OAKG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BvE;;;OAGG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,OAAO,CAAC;IAyBnB;;;OAGG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmCvD;;OAEG;YACW,aAAa;IAkGrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B,OAAO,CAAC,kBAAkB;IA0CpB,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7E"}
|
|
@@ -4,6 +4,7 @@ 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";
|
|
@@ -11,6 +12,7 @@ import { ClipSessionManager } from "./ClipSessionManager.js";
|
|
|
11
12
|
import { GlobalAudioSession } from "../stages/compose/GlobalAudioSession.js";
|
|
12
13
|
import { MuxManager } from "../stages/mux/MuxManager.js";
|
|
13
14
|
import { VideoChunkDecoder } from "../stages/decode/VideoChunkDecoder.js";
|
|
15
|
+
import { quantizeTimestampToFrame } from "../utils/time-utils.js";
|
|
14
16
|
class Orchestrator {
|
|
15
17
|
workers;
|
|
16
18
|
eventBus;
|
|
@@ -25,6 +27,8 @@ class Orchestrator {
|
|
|
25
27
|
config = ConfigLoader.getInstance().getConfig();
|
|
26
28
|
clipSessionManager;
|
|
27
29
|
currentClipId = null;
|
|
30
|
+
ensureCacheDebounceTimer = null;
|
|
31
|
+
ensureCacheDebounceDelay = 150;
|
|
28
32
|
events;
|
|
29
33
|
constructor(config) {
|
|
30
34
|
this.eventBus = config.eventBus || new EventBus();
|
|
@@ -127,7 +131,6 @@ class Orchestrator {
|
|
|
127
131
|
durationUs: model.durationUs
|
|
128
132
|
});
|
|
129
133
|
await this.audioSession.activateAllAudioClips();
|
|
130
|
-
this.renderFrame(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.
|
|
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,9 +231,11 @@ 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
|
-
|
|
236
|
+
let relativeTimeUs = options?.relativeTimeUs ?? timeUs - clip.startUs;
|
|
237
|
+
relativeTimeUs = quantizeTimestampToFrame(relativeTimeUs, 0, this.compositionModel.fps);
|
|
238
|
+
relativeTimeUs = Math.min(relativeTimeUs, clip.durationUs - 1);
|
|
233
239
|
const cachedFrame = this.cacheManager.getFrame(relativeTimeUs, clip.id);
|
|
234
240
|
if (cachedFrame) {
|
|
235
241
|
this.eventBus.emit(MeframeEvent.CacheHit, {
|
|
@@ -251,7 +257,6 @@ class Orchestrator {
|
|
|
251
257
|
if (l2Frame) {
|
|
252
258
|
return l2Frame;
|
|
253
259
|
}
|
|
254
|
-
void this.ensureClipCache(timeUs);
|
|
255
260
|
return null;
|
|
256
261
|
}
|
|
257
262
|
async decodeFromL2(timeUs, clip) {
|
|
@@ -291,27 +296,31 @@ class Orchestrator {
|
|
|
291
296
|
}
|
|
292
297
|
/**
|
|
293
298
|
* Ensure clips are cached using 2-Clip strategy
|
|
294
|
-
*
|
|
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)
|
|
295
302
|
*/
|
|
296
|
-
async ensureClipCache(timeUs) {
|
|
297
|
-
|
|
298
|
-
return;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
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;
|
|
307
313
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
).then((results) => results.every((result) => result));
|
|
311
|
-
if (hasL2) {
|
|
312
|
-
return;
|
|
314
|
+
if (immediate) {
|
|
315
|
+
return executeCache();
|
|
313
316
|
}
|
|
314
|
-
|
|
317
|
+
return new Promise((resolve) => {
|
|
318
|
+
this.ensureCacheDebounceTimer = setTimeout(async () => {
|
|
319
|
+
this.ensureCacheDebounceTimer = null;
|
|
320
|
+
await executeCache();
|
|
321
|
+
resolve();
|
|
322
|
+
}, this.ensureCacheDebounceDelay);
|
|
323
|
+
});
|
|
315
324
|
}
|
|
316
325
|
/**
|
|
317
326
|
* Wait for clip cache to be ready for playback
|
|
@@ -330,8 +339,9 @@ class Orchestrator {
|
|
|
330
339
|
return true;
|
|
331
340
|
}
|
|
332
341
|
return this.cacheManager.waitForClipReady(currentClip.id, {
|
|
333
|
-
minFrameCount: options?.minFrameCount ??
|
|
342
|
+
minFrameCount: options?.minFrameCount ?? 5,
|
|
334
343
|
timeoutMs: options?.timeoutMs ?? 5e3
|
|
344
|
+
// Don't pass startTimeUs - count all frames in the clip
|
|
335
345
|
});
|
|
336
346
|
}
|
|
337
347
|
/**
|
|
@@ -385,6 +395,7 @@ class Orchestrator {
|
|
|
385
395
|
cacheManager: this.cacheManager,
|
|
386
396
|
compositionModel: this.compositionModel,
|
|
387
397
|
workerConfigs: this.buildWorkerConfigs(),
|
|
398
|
+
resourceLoader: this.resourceLoader,
|
|
388
399
|
callbacks: {
|
|
389
400
|
onComposedStreamReady: (stream, fps) => {
|
|
390
401
|
if (options?.forL2Only) {
|
|
@@ -426,7 +437,7 @@ class Orchestrator {
|
|
|
426
437
|
},
|
|
427
438
|
onPipelineReady: async (attachmentResourceIds) => {
|
|
428
439
|
const clip2 = this.compositionModel?.findClip(clipId);
|
|
429
|
-
if (clip2
|
|
440
|
+
if (clip2 && hasResourceId(clip2)) {
|
|
430
441
|
await this.resourceLoader.fetch(clip2.resourceId, {
|
|
431
442
|
priority: options?.forL2Only ? "low" : "high",
|
|
432
443
|
sessionId,
|
|
@@ -450,6 +461,10 @@ class Orchestrator {
|
|
|
450
461
|
return session;
|
|
451
462
|
}
|
|
452
463
|
async dispose() {
|
|
464
|
+
if (this.ensureCacheDebounceTimer !== null) {
|
|
465
|
+
clearTimeout(this.ensureCacheDebounceTimer);
|
|
466
|
+
this.ensureCacheDebounceTimer = null;
|
|
467
|
+
}
|
|
453
468
|
this.resourceLoader.dispose();
|
|
454
469
|
await this.clipSessionManager.dispose();
|
|
455
470
|
await this.cacheManager.clear();
|