@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.
- 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 +10 -24
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +134 -182
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +4 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +17 -3
- 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 -25
- 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 +2 -6
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
- package/dist/orchestrator/ClipSessionManager.js +3 -9
- 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 +7 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +82 -14
- 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/decode/BaseDecoder.js +130 -0
- package/dist/stages/decode/BaseDecoder.js.map +1 -0
- package/dist/stages/decode/VideoChunkDecoder.js +199 -0
- package/dist/stages/decode/VideoChunkDecoder.js.map +1 -0
- 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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"}
|
|
@@ -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 (
|
|
29
|
+
* Ensure clips are active (Current, Next)
|
|
34
30
|
* Deactivates clips outside the set
|
|
35
31
|
*/
|
|
36
|
-
ensureClips(
|
|
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,
|
|
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 ??
|
|
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 (
|
|
14
|
+
* Ensure clips are active (Current, Next)
|
|
17
15
|
* Deactivates clips outside the set
|
|
18
16
|
*/
|
|
19
|
-
async ensureClips(
|
|
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
|
|
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;
|
|
@@ -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
|
-
*
|
|
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":"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,
|
|
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.
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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();
|