@meframe/core 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +5 -4
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +2 -2
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +4 -3
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/cache/l1/VideoL1Cache.d.ts +2 -2
  9. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  10. package/dist/cache/l1/VideoL1Cache.js +13 -8
  11. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  12. package/dist/config/defaults.d.ts.map +1 -1
  13. package/dist/config/defaults.js +2 -1
  14. package/dist/config/defaults.js.map +1 -1
  15. package/dist/config/types.d.ts +3 -0
  16. package/dist/config/types.d.ts.map +1 -1
  17. package/dist/controllers/PlaybackController.d.ts +7 -8
  18. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  19. package/dist/controllers/PlaybackController.js +56 -76
  20. package/dist/controllers/PlaybackController.js.map +1 -1
  21. package/dist/controllers/types.d.ts +2 -3
  22. package/dist/controllers/types.d.ts.map +1 -1
  23. package/dist/event/events.d.ts +1 -4
  24. package/dist/event/events.d.ts.map +1 -1
  25. package/dist/event/events.js.map +1 -1
  26. package/dist/model/CompositionModel.d.ts +1 -0
  27. package/dist/model/CompositionModel.d.ts.map +1 -1
  28. package/dist/model/CompositionModel.js +2 -0
  29. package/dist/model/CompositionModel.js.map +1 -1
  30. package/dist/model/patch.d.ts +6 -2
  31. package/dist/model/patch.d.ts.map +1 -1
  32. package/dist/model/patch.js +76 -2
  33. package/dist/model/patch.js.map +1 -1
  34. package/dist/model/types.d.ts +1 -0
  35. package/dist/model/types.d.ts.map +1 -1
  36. package/dist/orchestrator/CompositionPlanner.d.ts +8 -7
  37. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  38. package/dist/orchestrator/CompositionPlanner.js +33 -56
  39. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  40. package/dist/orchestrator/Orchestrator.d.ts +0 -1
  41. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  42. package/dist/orchestrator/Orchestrator.js +39 -19
  43. package/dist/orchestrator/Orchestrator.js.map +1 -1
  44. package/dist/orchestrator/VideoClipSession.d.ts +3 -5
  45. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  46. package/dist/orchestrator/VideoClipSession.js +66 -69
  47. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  48. package/dist/orchestrator/types.d.ts +1 -0
  49. package/dist/orchestrator/types.d.ts.map +1 -1
  50. package/dist/stages/compose/GlobalAudioSession.d.ts +6 -0
  51. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  52. package/dist/stages/compose/GlobalAudioSession.js +17 -1
  53. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  54. package/dist/stages/load/ResourceLoader.d.ts +22 -1
  55. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  56. package/dist/stages/load/ResourceLoader.js +71 -25
  57. package/dist/stages/load/ResourceLoader.js.map +1 -1
  58. package/dist/stages/load/TaskManager.d.ts +1 -1
  59. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  60. package/dist/stages/load/TaskManager.js +3 -2
  61. package/dist/stages/load/TaskManager.js.map +1 -1
  62. package/dist/stages/load/types.d.ts +2 -0
  63. package/dist/stages/load/types.d.ts.map +1 -1
  64. package/dist/worker/WorkerPool.d.ts +2 -0
  65. package/dist/worker/WorkerPool.d.ts.map +1 -1
  66. package/dist/worker/WorkerPool.js +4 -1
  67. package/dist/worker/WorkerPool.js.map +1 -1
  68. package/dist/workers/stages/compose/video-compose.worker.js +113 -81
  69. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
  70. package/package.json +1 -1
  71. package/dist/controllers/PreviewHandle.d.ts +0 -25
  72. package/dist/controllers/PreviewHandle.d.ts.map +0 -1
  73. package/dist/controllers/PreviewHandle.js +0 -45
  74. package/dist/controllers/PreviewHandle.js.map +0 -1
  75. package/dist/model/dirty-range.js +0 -220
  76. package/dist/model/dirty-range.js.map +0 -1
@@ -31,7 +31,8 @@ class Orchestrator {
31
31
  this.workers = new WorkerPool({
32
32
  eventBus: this.eventBus,
33
33
  workerConfigs,
34
- workerPath: config.workerPath
34
+ workerPath: config.workerPath,
35
+ workerExtension: config.workerExtension
35
36
  });
36
37
  this.resourceLoader = new ResourceLoader({
37
38
  orchestrator: this,
@@ -127,20 +128,26 @@ class Orchestrator {
127
128
  if (!this.compositionModel) {
128
129
  throw new Error("No composition model set");
129
130
  }
130
- const dirtyRanges = applyPatch(this.compositionModel, patch);
131
- const clipUpdates = this.planner.applyPatch(patch, dirtyRanges);
131
+ const affectedClipIds = applyPatch(this.compositionModel, patch);
132
+ const clipUpdates = this.planner.applyPatch(patch, affectedClipIds);
132
133
  this.eventBus.emit(MeframeEvent.PatchApplied, {
133
134
  operations: patch.operations.length,
134
- dirtyRanges: dirtyRanges.map((r) => ({ startUs: r.startUs, endUs: r.endUs }))
135
+ affectedClips: Array.from(affectedClipIds)
135
136
  });
136
- for (const range of dirtyRanges) {
137
- await this.handleDirtyRange(range);
138
- }
137
+ const activeClipIds = new Set(
138
+ Array.from(this.clipSessionManager["entries"].keys()).filter(
139
+ (clipId) => this.clipSessionManager.isClipActive(clipId)
140
+ )
141
+ );
139
142
  for (const update of clipUpdates) {
140
143
  if (update.type === "remove") {
141
144
  this.activeClips.delete(update.clipId);
142
145
  this.cacheManager.evictClip(update.clipId);
143
146
  }
147
+ await this.clipSessionManager.handlePlannerUpdate(update.clipId, update);
148
+ if (activeClipIds.has(update.clipId) && update.type !== "remove") {
149
+ this.cacheManager.evictClip(update.clipId);
150
+ }
144
151
  }
145
152
  }
146
153
  handleResourceStateChange(resourceId, state) {
@@ -151,15 +158,30 @@ class Orchestrator {
151
158
  if (state !== "ready") {
152
159
  return;
153
160
  }
161
+ const resource = this.compositionModel.getResource(resourceId);
162
+ if (!resource) {
163
+ return;
164
+ }
165
+ if (resource.type === "video" || resource.type === "audio") {
166
+ return;
167
+ }
154
168
  const clipIds = this.compositionModel.getClipIdsByResourceId(resourceId);
155
169
  for (const clipId of clipIds) {
170
+ if (!this.clipSessionManager.isClipActive(clipId)) {
171
+ continue;
172
+ }
156
173
  const clip = this.compositionModel.findClip(clipId);
157
174
  if (!clip) {
158
175
  continue;
159
176
  }
160
- const update = this.planner.refreshClip(clipId, "resource-ready");
161
- if (update) {
162
- this.clipSessionManager.handlePlannerUpdate(clipId, update);
177
+ const instructions = this.planner.getInstructions(clipId);
178
+ if (!instructions) {
179
+ continue;
180
+ }
181
+ const session = this.clipSessionManager.getSession(clipId);
182
+ const visualWorker = session?.visualWorkerHandle;
183
+ if (visualWorker) {
184
+ visualWorker.send("install_instructions", instructions);
163
185
  }
164
186
  }
165
187
  }
@@ -188,7 +210,7 @@ class Orchestrator {
188
210
  if (!this.compositionModel) {
189
211
  throw new Error("No composition model set");
190
212
  }
191
- const clip = this.compositionModel.getClipsAtTime(timeUs, "main")[0];
213
+ const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];
192
214
  if (!clip) {
193
215
  return null;
194
216
  }
@@ -234,7 +256,7 @@ class Orchestrator {
234
256
  if (!this.compositionModel) {
235
257
  return false;
236
258
  }
237
- const clips = this.compositionModel.getClipsAtTime(timeUs, "main");
259
+ const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);
238
260
  if (clips.length === 0) {
239
261
  return true;
240
262
  }
@@ -244,7 +266,7 @@ class Orchestrator {
244
266
  }
245
267
  return this.cacheManager.waitForClipReady(currentClip.id, {
246
268
  minFrameCount: options?.minFrameCount ?? 30,
247
- timeoutMs: options?.timeoutMs ?? 3e3
269
+ timeoutMs: options?.timeoutMs ?? 5e3
248
270
  });
249
271
  }
250
272
  /**
@@ -264,9 +286,9 @@ class Orchestrator {
264
286
  workerConfigs: this.buildWorkerConfigs(),
265
287
  callbacks: {
266
288
  onStreamReady: (id, stream, fps) => {
267
- this.cacheManager.acceptComposedFrames(stream, {
289
+ this.cacheManager.receiveComposedFrames(stream, {
268
290
  clipId: id,
269
- trackId: "main",
291
+ trackId: this.compositionModel.mainTrackId,
270
292
  fps,
271
293
  onFrame: () => {
272
294
  }
@@ -277,7 +299,8 @@ class Orchestrator {
277
299
  if (clip2?.resourceId) {
278
300
  await this.resourceLoader.fetch(clip2.resourceId, {
279
301
  priority: "high",
280
- clipId: clipId2
302
+ clipId: clipId2,
303
+ trackId: clip2.trackId
281
304
  });
282
305
  }
283
306
  }
@@ -370,9 +393,6 @@ class Orchestrator {
370
393
  { transfer: [videoChannel.port2] }
371
394
  );
372
395
  }
373
- async handleDirtyRange(range) {
374
- this.cacheManager.invalidateRange(range.startUs, range.endUs, range.trackId);
375
- }
376
396
  }
377
397
  export {
378
398
  Orchestrator
@@ -1 +1 @@
1
- {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, DirtyRange, RenderFrameOptions } from './types';\nimport { WorkerStatus, WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame } from '../model';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { VideoClipSession } from './VideoClipSession';\nimport { ClipSessionManager } from './ClipSessionManager';\nimport { GlobalAudioSession } from '../stages/compose/GlobalAudioSession';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n\n private activeClips = new Set<string>();\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private clipSessionManager: ClipSessionManager;\n private currentClipId: string | null = null;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n });\n\n this.resourceLoader = new ResourceLoader({\n orchestrator: this as any,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: config.maxWorkers || (this.config.load as any)?.retry?.maxAttempts || 4,\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const cacheConfig = config.cacheConfig || this.config.cache;\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB:\n (cacheConfig as any)?.l1Size || (cacheConfig as any)?.l1?.maxMemoryMB || 1024,\n maxGOPs: (this.config.decode as any)?.video?.maxGOPs || 4,\n },\n l2: {\n maxSizeMB: (cacheConfig as any)?.l2Size || (cacheConfig as any)?.l2?.maxSizeMB || 2048,\n projectId: 'default',\n },\n },\n this.eventBus\n );\n\n this.clipSessionManager = new ClipSessionManager({\n maxConcurrent: 2,\n factory: {\n createSession: (clipId) => this.createSession(clipId),\n },\n model: () => this.compositionModel,\n cacheManager: this.cacheManager,\n });\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workers: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n getModel: () => this.compositionModel,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n }\n\n get workerStatus(): WorkerStatus {\n const status = this.workers.status;\n const result: WorkerStatus = {} as WorkerStatus;\n\n const workerTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'decode',\n 'videoCompose',\n 'audioCompose',\n 'encode',\n 'mux',\n ];\n\n for (const type of workerTypes) {\n result[type] = status[type] || {\n state: 'idle',\n taskCount: 0,\n };\n }\n\n return result;\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n // Use unified stream pipeline wiring\n await this.setupSharedConnections();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.planner.setModel(model);\n this.cacheManager.configure(model);\n this.currentClipId = null;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n\n await this.audioSession.activateAllAudioClips();\n\n this.ensureClipCache(0);\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const dirtyRanges = applyModelPatch(this.compositionModel, patch);\n const clipUpdates = this.planner.applyPatch(patch, dirtyRanges);\n\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n dirtyRanges: dirtyRanges.map((r) => ({ startUs: r.startUs, endUs: r.endUs })),\n });\n\n for (const range of dirtyRanges) {\n await this.handleDirtyRange(range);\n }\n\n for (const update of clipUpdates) {\n // In 3-Clip strategy, session lifecycle is managed by ClipSessionManager\n // Clip updates handled through cache invalidation in dirtyRanges\n if (update.type === 'remove') {\n this.activeClips.delete(update.clipId);\n this.cacheManager.evictClip(update.clipId);\n }\n }\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n const clipIds = this.compositionModel.getClipIdsByResourceId(resourceId);\n for (const clipId of clipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (!clip) {\n continue;\n }\n\n const update = this.planner.refreshClip(clipId, 'resource-ready');\n if (update) {\n this.clipSessionManager.handlePlannerUpdate(clipId, update);\n }\n // Clip will be activated when ensureClipCache is called\n }\n }\n\n async restartWorker(type: WorkerType, clipId?: string): Promise<void> {\n if ((type === 'videoDemux' || type === 'audioDemux' || type === 'videoCompose') && !clipId) {\n throw new Error(`clipId required for restarting ${type} worker`);\n }\n\n this.workers.terminate(type, clipId);\n const worker = await this.workers.get(type, clipId);\n\n this.eventBus.emit(MeframeEvent.WorkerRestarted, {\n type,\n workerId: worker.getWorkerId(),\n reason: 'Manual restart',\n });\n\n if (clipId) {\n const session = this.clipSessionManager.getSession(clipId);\n if (session) {\n await session.activate();\n }\n } else if (\n type === 'decode' ||\n type === 'audioCompose' ||\n type === 'encode' ||\n type === 'mux'\n ) {\n await this.setupSharedConnections();\n }\n }\n\n async renderFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, 'main')[0];\n if (!clip) {\n return null;\n }\n\n // Detect clip change and proactively ensure cache for prev/current/next\n if (this.currentClipId !== clip.id) {\n this.currentClipId = clip.id;\n void this.ensureClipCache(timeUs);\n }\n\n const cachedFrame = await this.cacheManager.getFrame(timeUs, clip.id);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${timeUs}`,\n });\n return cachedFrame;\n }\n // Cache miss - also ensure cache (defensive, already called on clip change)\n void this.ensureClipCache(timeUs);\n\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${timeUs}`,\n });\n\n if (signal?.aborted) {\n throw new DOMException('Render aborted', 'AbortError');\n }\n\n // Return null immediately instead of waiting\n // This allows PlaybackController to detect miss and trigger buffering\n return null;\n }\n\n /**\n * Ensure clips are cached using 3-Clip strategy\n * Called by PlaybackController\n */\n async ensureClipCache(timeUs: TimeUs): Promise<void> {\n if (!this.compositionModel) {\n return;\n }\n\n await this.clipSessionManager.ensureClips(timeUs);\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, 'main');\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 30,\n timeoutMs: options?.timeoutMs ?? 3_000,\n });\n }\n\n /**\n * Create a new session for a clip\n */\n private async createSession(clipId: string): Promise<VideoClipSession> {\n const clip = this.compositionModel?.findClip(clipId);\n if (!clip) {\n throw new Error(`Clip ${clipId} not found`);\n }\n\n const session = await VideoClipSession.create({\n clipId,\n planner: this.planner,\n workerPool: this.workers,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n workerConfigs: this.buildWorkerConfigs(),\n callbacks: {\n onStreamReady: (id, stream, fps) => {\n this.cacheManager.acceptComposedFrames(stream, {\n clipId: id,\n trackId: 'main',\n fps,\n onFrame: () => {\n // Frame arrived\n },\n });\n },\n onPipelineReady: async (clipId) => {\n // Pipeline is connected, now trigger resource loading\n const clip = this.compositionModel?.findClip(clipId);\n if (clip?.resourceId) {\n await this.resourceLoader.fetch(clip.resourceId, {\n priority: 'high',\n clipId,\n });\n }\n },\n },\n });\n\n this.activeClips.add(clipId);\n return session;\n }\n\n async dispose(): Promise<void> {\n this.resourceLoader.dispose();\n await this.clipSessionManager.dispose();\n await this.cacheManager.clear();\n\n this.currentClipId = null;\n this.activeClips.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config as any;\n const defaultCanvasWidth = config.global?.defaultCanvasWidth ?? 1280;\n const defaultCanvasHeight = config.global?.defaultCanvasHeight ?? 720;\n const defaultFps = config.global?.defaultFps ?? 30;\n return {\n videoDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n audioDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n decode: {\n video: config.decode?.video,\n audio: config.decode?.audio,\n },\n videoCompose: {\n width: config.compose?.canvas?.width ?? defaultCanvasWidth,\n height: config.compose?.canvas?.height ?? defaultCanvasHeight,\n fps: config.global?.defaultFps ?? defaultFps,\n backgroundColor: config.compose?.canvas?.backgroundColor ?? '#000000',\n enableSmoothing: config.compose?.visual?.enableSmoothing ?? true,\n enableHardwareAcceleration: config.compose?.visual?.enableHardwareAcceleration ?? true,\n },\n audioCompose: {\n ducking: config.compose?.audio?.ducking,\n mixing: config.compose?.audio?.mixing,\n },\n encode: {\n video: {\n codec: 'avc1.42002A',\n width: config.compose?.canvas?.width || defaultCanvasWidth,\n height: config.compose?.canvas?.height || defaultCanvasHeight,\n bitrate: config.encode?.video?.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : 12_000_000,\n framerate: config.encode?.video?.framerate || defaultFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'prefer-hardware',\n ...(config.encode?.video as any),\n },\n audio: {\n codec: 'mp4a.40.2',\n sampleRate: 48000,\n numberOfChannels: 2,\n bitrate: config.encode?.audio?.bitrateKbps\n ? config.encode.audio.bitrateKbps * 1000\n : 128000,\n ...(config.encode?.audio as any),\n },\n },\n mux: {\n container: config.export?.container || 'mp4',\n },\n };\n }\n\n private async setupSharedConnections(): Promise<void> {\n const decodeWorker = await this.workers.get('decode');\n\n decodeWorker.receiveStream((stream, metadata) => {\n if (metadata?.streamType === 'audio') {\n this.audioSession.handleAudioStream(stream as ReadableStream<AudioData>, metadata);\n }\n });\n\n const encodeWorker = await this.workers.get('encode');\n const muxWorker = await this.workers.get('mux');\n const videoChannel = new MessageChannel();\n await encodeWorker.send(\n 'connect',\n { direction: 'downstream', port: videoChannel.port1, streamType: 'video' },\n { transfer: [videoChannel.port1] }\n );\n await muxWorker.send(\n 'connect',\n { direction: 'upstream', port: videoChannel.port2, streamType: 'video' },\n { transfer: [videoChannel.port2] }\n );\n }\n\n private async handleDirtyRange(range: DirtyRange): Promise<void> {\n this.cacheManager.invalidateRange(range.startUs, range.endUs, range.trackId);\n }\n}\n"],"names":["applyModelPatch","clipId","clip"],"mappings":"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"}
1
+ {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerStatus, WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame } from '../model';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { VideoClipSession } from './VideoClipSession';\nimport { ClipSessionManager } from './ClipSessionManager';\nimport { GlobalAudioSession } from '../stages/compose/GlobalAudioSession';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n\n private activeClips = new Set<string>();\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private clipSessionManager: ClipSessionManager;\n private currentClipId: string | null = null;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n this.resourceLoader = new ResourceLoader({\n orchestrator: this as any,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: config.maxWorkers || (this.config.load as any)?.retry?.maxAttempts || 4,\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const cacheConfig = config.cacheConfig || this.config.cache;\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB:\n (cacheConfig as any)?.l1Size || (cacheConfig as any)?.l1?.maxMemoryMB || 1024,\n maxGOPs: (this.config.decode as any)?.video?.maxGOPs || 4,\n },\n l2: {\n maxSizeMB: (cacheConfig as any)?.l2Size || (cacheConfig as any)?.l2?.maxSizeMB || 2048,\n projectId: 'default',\n },\n },\n this.eventBus\n );\n\n this.clipSessionManager = new ClipSessionManager({\n maxConcurrent: 2,\n factory: {\n createSession: (clipId) => this.createSession(clipId),\n },\n model: () => this.compositionModel,\n cacheManager: this.cacheManager,\n });\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workers: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n getModel: () => this.compositionModel,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n }\n\n get workerStatus(): WorkerStatus {\n const status = this.workers.status;\n const result: WorkerStatus = {} as WorkerStatus;\n\n const workerTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'decode',\n 'videoCompose',\n 'audioCompose',\n 'encode',\n 'mux',\n ];\n\n for (const type of workerTypes) {\n result[type] = status[type] || {\n state: 'idle',\n taskCount: 0,\n };\n }\n\n return result;\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n // Use unified stream pipeline wiring\n await this.setupSharedConnections();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.planner.setModel(model);\n this.cacheManager.configure(model);\n this.currentClipId = null;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n\n await this.audioSession.activateAllAudioClips();\n\n this.ensureClipCache(0);\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 3-Clip strategy)\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n const clipUpdates = this.planner.applyPatch(patch, affectedClipIds);\n\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Get currently active clips in the 3-Clip window\n const activeClipIds = new Set(\n Array.from(this.clipSessionManager['entries'].keys()).filter((clipId) =>\n this.clipSessionManager.isClipActive(clipId)\n )\n );\n\n // Process clip updates\n for (const update of clipUpdates) {\n if (update.type === 'remove') {\n this.activeClips.delete(update.clipId);\n this.cacheManager.evictClip(update.clipId);\n }\n\n // Notify ClipSessionManager to handle the update\n // This will install new instructions or restart pipeline as needed\n await this.clipSessionManager.handlePlannerUpdate(update.clipId, update);\n\n // Only evict cache for active clips (in current 3-Clip window)\n // Non-active clips will be regenerated when they enter the window\n if (activeClipIds.has(update.clipId) && update.type !== 'remove') {\n this.cacheManager.evictClip(update.clipId);\n }\n }\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n const resource = this.compositionModel.getResource(resourceId);\n if (!resource) {\n return;\n }\n\n // Main video/audio resources: data will flow naturally into pipeline\n if (resource.type === 'video' || resource.type === 'audio') {\n return;\n }\n\n // Attachment resources (fonts, images): update instructions for active clips\n const clipIds = this.compositionModel.getClipIdsByResourceId(resourceId);\n for (const clipId of clipIds) {\n // Only update active clips (in 3-Clip window)\n if (!this.clipSessionManager.isClipActive(clipId)) {\n continue;\n }\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip) {\n continue;\n }\n\n // Rebuild instructions with updated resource status\n const instructions = this.planner.getInstructions(clipId);\n if (!instructions) {\n continue;\n }\n\n // Send updated instructions to worker (no pipeline restart needed)\n const session = this.clipSessionManager.getSession(clipId);\n const visualWorker = session?.visualWorkerHandle;\n if (visualWorker) {\n visualWorker.send('install_instructions', instructions);\n }\n }\n }\n\n async restartWorker(type: WorkerType, clipId?: string): Promise<void> {\n if ((type === 'videoDemux' || type === 'audioDemux' || type === 'videoCompose') && !clipId) {\n throw new Error(`clipId required for restarting ${type} worker`);\n }\n\n this.workers.terminate(type, clipId);\n const worker = await this.workers.get(type, clipId);\n\n this.eventBus.emit(MeframeEvent.WorkerRestarted, {\n type,\n workerId: worker.getWorkerId(),\n reason: 'Manual restart',\n });\n\n if (clipId) {\n const session = this.clipSessionManager.getSession(clipId);\n if (session) {\n await session.activate();\n }\n } else if (\n type === 'decode' ||\n type === 'audioCompose' ||\n type === 'encode' ||\n type === 'mux'\n ) {\n await this.setupSharedConnections();\n }\n }\n\n async renderFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n // Detect clip change and proactively ensure cache for prev/current/next\n if (this.currentClipId !== clip.id) {\n this.currentClipId = clip.id;\n void this.ensureClipCache(timeUs);\n }\n\n const cachedFrame = await this.cacheManager.getFrame(timeUs, clip.id);\n // console.log('>>>>>>>>>>>> renderFrame', timeUs, clip.id, cachedFrame);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${timeUs}`,\n });\n return cachedFrame;\n }\n // Cache miss - also ensure cache (defensive, already called on clip change)\n void this.ensureClipCache(timeUs);\n\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${timeUs}`,\n });\n\n if (signal?.aborted) {\n throw new DOMException('Render aborted', 'AbortError');\n }\n\n // Return null immediately instead of waiting\n // This allows PlaybackController to detect miss and trigger buffering\n return null;\n }\n\n /**\n * Ensure clips are cached using 3-Clip strategy\n * Called by PlaybackController\n */\n async ensureClipCache(timeUs: TimeUs): Promise<void> {\n if (!this.compositionModel) {\n return;\n }\n\n await this.clipSessionManager.ensureClips(timeUs);\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 30,\n timeoutMs: options?.timeoutMs ?? 5_000,\n });\n }\n\n /**\n * Create a new session for a clip\n */\n private async createSession(clipId: string): Promise<VideoClipSession> {\n const clip = this.compositionModel?.findClip(clipId);\n if (!clip) {\n throw new Error(`Clip ${clipId} not found`);\n }\n\n const session = await VideoClipSession.create({\n clipId,\n planner: this.planner,\n workerPool: this.workers,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n workerConfigs: this.buildWorkerConfigs(),\n callbacks: {\n onStreamReady: (id, stream, fps) => {\n this.cacheManager.receiveComposedFrames(stream, {\n clipId: id,\n trackId: this.compositionModel!.mainTrackId,\n fps,\n onFrame: () => {\n // Frame arrived\n },\n });\n },\n onPipelineReady: async (clipId) => {\n // Pipeline is connected, now trigger resource loading\n const clip = this.compositionModel?.findClip(clipId);\n if (clip?.resourceId) {\n await this.resourceLoader.fetch(clip.resourceId, {\n priority: 'high',\n clipId,\n trackId: clip.trackId,\n });\n }\n },\n },\n });\n\n this.activeClips.add(clipId);\n return session;\n }\n\n async dispose(): Promise<void> {\n this.resourceLoader.dispose();\n await this.clipSessionManager.dispose();\n await this.cacheManager.clear();\n\n this.currentClipId = null;\n this.activeClips.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config as any;\n const defaultCanvasWidth = config.global?.defaultCanvasWidth ?? 1280;\n const defaultCanvasHeight = config.global?.defaultCanvasHeight ?? 720;\n const defaultFps = config.global?.defaultFps ?? 30;\n return {\n videoDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n audioDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n decode: {\n video: config.decode?.video,\n audio: config.decode?.audio,\n },\n videoCompose: {\n width: config.compose?.canvas?.width ?? defaultCanvasWidth,\n height: config.compose?.canvas?.height ?? defaultCanvasHeight,\n fps: config.global?.defaultFps ?? defaultFps,\n backgroundColor: config.compose?.canvas?.backgroundColor ?? '#000000',\n enableSmoothing: config.compose?.visual?.enableSmoothing ?? true,\n enableHardwareAcceleration: config.compose?.visual?.enableHardwareAcceleration ?? true,\n },\n audioCompose: {\n ducking: config.compose?.audio?.ducking,\n mixing: config.compose?.audio?.mixing,\n },\n encode: {\n video: {\n codec: 'avc1.42002A',\n width: config.compose?.canvas?.width || defaultCanvasWidth,\n height: config.compose?.canvas?.height || defaultCanvasHeight,\n bitrate: config.encode?.video?.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : 12_000_000,\n framerate: config.encode?.video?.framerate || defaultFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'prefer-hardware',\n ...(config.encode?.video as any),\n },\n audio: {\n codec: 'mp4a.40.2',\n sampleRate: 48000,\n numberOfChannels: 2,\n bitrate: config.encode?.audio?.bitrateKbps\n ? config.encode.audio.bitrateKbps * 1000\n : 128000,\n ...(config.encode?.audio as any),\n },\n },\n mux: {\n container: config.export?.container || 'mp4',\n },\n };\n }\n\n private async setupSharedConnections(): Promise<void> {\n const decodeWorker = await this.workers.get('decode');\n\n decodeWorker.receiveStream((stream, metadata) => {\n if (metadata?.streamType === 'audio') {\n this.audioSession.handleAudioStream(stream as ReadableStream<AudioData>, metadata);\n }\n });\n\n const encodeWorker = await this.workers.get('encode');\n const muxWorker = await this.workers.get('mux');\n const videoChannel = new MessageChannel();\n await encodeWorker.send(\n 'connect',\n { direction: 'downstream', port: videoChannel.port1, streamType: 'video' },\n { transfer: [videoChannel.port1] }\n );\n await muxWorker.send(\n 'connect',\n { direction: 'upstream', port: videoChannel.port2, streamType: 'video' },\n { transfer: [videoChannel.port2] }\n );\n }\n}\n"],"names":["applyModelPatch","clipId","clip"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { WorkerPool } from '../worker/WorkerPool';
2
2
  import { CacheManager } from '../cache/CacheManager';
3
3
  import { CompositionModel } from '../model';
4
- import { DirtyRange } from './types';
5
4
  import { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';
6
5
  import { BaseWorker } from '../worker/BaseWorker';
7
6
  import { WorkerType } from '../worker/types';
@@ -45,20 +44,19 @@ export declare class VideoClipSession {
45
44
  deactivate(): Promise<void>;
46
45
  dispose(): Promise<void>;
47
46
  handlePlannerUpdate(update: ClipUpdateResult): Promise<void>;
48
- requestReplay(range: DirtyRange): Promise<void>;
49
47
  get visualWorkerHandle(): BaseWorker | null;
50
48
  private getClip;
51
49
  private getResource;
52
50
  private ensureInstructions;
53
- private setupPipeline;
51
+ private setupImagePipeline;
52
+ private connectImagePipeline;
53
+ private setupVideoPipeline;
54
54
  private acquireWorkers;
55
55
  private connectVideoPipeline;
56
56
  private installInstructions;
57
57
  private restartPipeline;
58
58
  private releasePipeline;
59
59
  private invalidateClipCache;
60
- private invalidateRanges;
61
- private syncDirtyRanges;
62
60
  }
63
61
  export {};
64
62
  //# sourceMappingURL=VideoClipSession.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,UAAU,yBAAyB;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACrF,gBAAgB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACvC,SAAS,EAAE,yBAAyB,CAAC;CACtC;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IAEtD,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;WAEd,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI9E,OAAO;IAUD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC5D,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAarD,IAAI,kBAAkB,IAAI,UAAU,GAAG,IAAI,CAE1C;IAED,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;YAML,kBAAkB;YAiBlB,aAAa;YAmBb,cAAc;YAsBd,oBAAoB;YAwFpB,mBAAmB;YAwBnB,eAAe;YAcf,eAAe;YAuCf,mBAAmB;YAInB,gBAAgB;YAMhB,eAAe;CAW9B"}
1
+ {"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,UAAU,yBAAyB;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACrF,gBAAgB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACvC,SAAS,EAAE,yBAAyB,CAAC;CACtC;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IAEtD,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;WAEd,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI9E,OAAO;IAUD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BlE,IAAI,kBAAkB,IAAI,UAAU,GAAG,IAAI,CAE1C;IAED,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;YAML,kBAAkB;YAQlB,kBAAkB;YAKlB,oBAAoB;YAoCpB,kBAAkB;YAMlB,cAAc;YAwBd,oBAAoB;YAwFpB,mBAAmB;YAYnB,eAAe;YAgBf,eAAe;YAuCf,mBAAmB;CAGlC"}
@@ -30,13 +30,18 @@ class VideoClipSession {
30
30
  this.callbacks = config.callbacks;
31
31
  }
32
32
  async activate() {
33
+ console.log(">>>>>>>>>>>> activate", this.clipId);
33
34
  if (this.isActive || this.isDisposed) return;
34
35
  const clip = this.getClip();
35
36
  const resource = this.getResource();
36
- if (!clip || !resource || resource.type !== "video") {
37
+ if (!clip || !resource) {
37
38
  return;
38
39
  }
39
- await this.setupPipeline(clip, resource);
40
+ if (resource.type === "video") {
41
+ await this.setupVideoPipeline(clip, resource);
42
+ } else if (resource.type === "image") {
43
+ await this.setupImagePipeline(clip);
44
+ }
40
45
  await this.ensureInstructions(clip);
41
46
  this.isActive = true;
42
47
  if (this.callbacks.onPipelineReady) {
@@ -69,31 +74,15 @@ class VideoClipSession {
69
74
  }
70
75
  const clip = this.getClip();
71
76
  const resource = this.getResource();
72
- if (!clip || !resource || resource.type !== "video") {
77
+ if (!clip || !resource || resource.type !== "video" && resource.type !== "image") {
73
78
  return;
74
79
  }
75
- if (!this.isActive) {
80
+ if (this.isActive) {
81
+ console.log(">>>>>>>>>>>> restartPipeline", this.clipId);
82
+ await this.restartPipeline(clip, resource, instructions);
83
+ } else {
76
84
  await this.activate();
77
85
  }
78
- if (update.type === "pipelineChange") {
79
- await this.restartPipeline(clip, resource, instructions, update.dirtyRanges);
80
- return;
81
- }
82
- await this.installInstructions(instructions, update.dirtyRanges, {
83
- expectCacheInvalidation: false,
84
- triggerSync: update.type !== "instructionOnly"
85
- });
86
- }
87
- async requestReplay(range) {
88
- if (this.isDisposed || !this.isActive || !this.visualWorker || !this.instructionContext) {
89
- return;
90
- }
91
- await this.cacheManager.invalidateRange(range.startUs, range.endUs, this.clipId);
92
- await this.visualWorker.send("sync_clip", {
93
- clipId: this.clipId,
94
- revision: this.instructionContext.revision,
95
- range
96
- });
97
86
  }
98
87
  get visualWorkerHandle() {
99
88
  return this.visualWorker;
@@ -106,33 +95,61 @@ class VideoClipSession {
106
95
  if (!clip) return null;
107
96
  return this.compositionModel.resources.get(clip.resourceId) || null;
108
97
  }
109
- async ensureInstructions(clip) {
98
+ async ensureInstructions(_clip) {
110
99
  const instructions = this.planner.getInstructions(this.clipId);
111
100
  if (!instructions) {
112
101
  throw new Error(`No instructions for clip ${this.clipId}`);
113
102
  }
114
- await this.installInstructions(instructions, [
103
+ await this.installInstructions(instructions);
104
+ }
105
+ async setupImagePipeline(clip) {
106
+ await this.acquireWorkers(clip, false);
107
+ await this.connectImagePipeline(clip);
108
+ }
109
+ async connectImagePipeline(clip) {
110
+ if (!this.visualWorker || !this.encodeWorker) {
111
+ throw new Error("Pipeline workers not ready");
112
+ }
113
+ const clipId = clip.id;
114
+ this.visualToEncodeChannel = new MessageChannel();
115
+ await this.visualWorker.send(
116
+ "connect",
117
+ {
118
+ direction: "downstream",
119
+ port: this.visualToEncodeChannel.port1,
120
+ streamType: "video",
121
+ clipId
122
+ },
123
+ { transfer: [this.visualToEncodeChannel.port1] }
124
+ );
125
+ await this.encodeWorker.send(
126
+ "connect",
115
127
  {
116
- clipId: clip.id,
117
- trackId: clip.trackId || "main",
118
- startUs: clip.startUs,
119
- endUs: clip.startUs + clip.durationUs,
120
- reason: "initial-install"
121
- }
122
- ]);
128
+ direction: "upstream",
129
+ port: this.visualToEncodeChannel.port2,
130
+ streamType: "video",
131
+ clipId
132
+ },
133
+ { transfer: [this.visualToEncodeChannel.port2] }
134
+ );
135
+ this.visualWorker.receiveStream((stream) => {
136
+ const visualConfig = this.workerConfigs.videoCompose ?? {};
137
+ const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;
138
+ this.callbacks.onStreamReady(this.clipId, stream, fps);
139
+ });
123
140
  }
124
- async setupPipeline(clip, resource) {
125
- await this.acquireWorkers(clip);
141
+ async setupVideoPipeline(clip, resource) {
142
+ console.log(">>>>>>>>>>>> setupVideoPipeline", this.clipId);
143
+ await this.acquireWorkers(clip, true);
126
144
  await this.connectVideoPipeline(clip, resource.id);
127
- if (!this.visualWorker) {
128
- throw new Error("Visual worker unavailable");
129
- }
130
145
  }
131
- async acquireWorkers(clip) {
132
- this.visualWorker = await this.workerPool.get("videoCompose", clip.id, { lazy: true });
133
- this.decodeWorker = await this.workerPool.get("decode");
146
+ async acquireWorkers(clip, isVideo) {
147
+ if (isVideo) {
148
+ this.decodeWorker = await this.workerPool.get("decode");
149
+ this.videoDemuxWorker = await this.workerPool.get("videoDemux", clip.id, { lazy: true });
150
+ }
134
151
  this.encodeWorker = await this.workerPool.get("encode");
135
- this.videoDemuxWorker = await this.workerPool.get("videoDemux", clip.id, { lazy: true });
152
+ this.visualWorker = await this.workerPool.get("videoCompose", clip.id, { lazy: true });
136
153
  const visualConfig = this.workerConfigs.videoCompose ?? {};
137
154
  const renderOverrides = this.compositionModel.renderConfig ?? {};
138
155
  const timeline = {
@@ -228,29 +245,24 @@ class VideoClipSession {
228
245
  this.callbacks.onStreamReady(this.clipId, stream, fps);
229
246
  });
230
247
  }
231
- async installInstructions(instructions, dirtyRanges, options) {
248
+ async installInstructions(instructions) {
232
249
  this.instructionContext = {
233
250
  revision: instructions.revision,
234
251
  instructions,
235
252
  status: instructions.status
236
253
  };
237
- if (options?.expectCacheInvalidation) {
238
- await this.invalidateRanges(dirtyRanges);
239
- }
240
254
  if (this.visualWorker) {
241
255
  await this.visualWorker.send("install_instructions", instructions);
242
256
  }
243
- if (options?.triggerSync) {
244
- await this.syncDirtyRanges(dirtyRanges, instructions.revision);
245
- }
246
257
  }
247
- async restartPipeline(clip, resource, instructions, dirtyRanges) {
258
+ async restartPipeline(clip, resource, instructions) {
248
259
  await this.releasePipeline();
249
- await this.setupPipeline(clip, resource);
250
- await this.installInstructions(instructions, dirtyRanges, {
251
- expectCacheInvalidation: true,
252
- triggerSync: true
253
- });
260
+ if (resource.type === "video") {
261
+ await this.setupVideoPipeline(clip, resource);
262
+ } else if (resource.type === "image") {
263
+ await this.setupImagePipeline(clip);
264
+ }
265
+ await this.installInstructions(instructions);
254
266
  }
255
267
  async releasePipeline() {
256
268
  if (this.visualWorker && this.instructionContext) {
@@ -286,21 +298,6 @@ class VideoClipSession {
286
298
  async invalidateClipCache() {
287
299
  await this.cacheManager.invalidateClip(this.clipId);
288
300
  }
289
- async invalidateRanges(ranges) {
290
- for (const range of ranges) {
291
- await this.cacheManager.invalidateRange(range.startUs, range.endUs, this.clipId);
292
- }
293
- }
294
- async syncDirtyRanges(ranges, revision) {
295
- if (!this.visualWorker) return;
296
- for (const range of ranges) {
297
- await this.visualWorker.send("sync_clip", {
298
- clipId: this.clipId,
299
- revision,
300
- range
301
- });
302
- }
303
- }
304
301
  }
305
302
  export {
306
303
  VideoClipSession
@@ -1 +1 @@
1
- {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { DirtyRange } from './types';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\n\ninterface VideoClipSessionCallbacks {\n onStreamReady(clipId: string, stream: ReadableStream<VideoFrame>, fps: number): void;\n onStreamDisposed?(clipId: string): void;\n onPipelineReady?(clipId: string): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n\n private instructionContext: InstructionContext | null = null;\n private visualWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private encodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private visualToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || resource.type !== 'video') {\n return;\n }\n\n await this.setupPipeline(clip, resource);\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady(this.clipId);\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.invalidateClipCache();\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || resource.type !== 'video') {\n return;\n }\n\n if (!this.isActive) {\n await this.activate();\n }\n\n if (update.type === 'pipelineChange') {\n await this.restartPipeline(clip, resource, instructions, update.dirtyRanges);\n return;\n }\n\n await this.installInstructions(instructions, update.dirtyRanges, {\n expectCacheInvalidation: false,\n triggerSync: update.type !== 'instructionOnly',\n });\n }\n\n async requestReplay(range: DirtyRange): Promise<void> {\n if (this.isDisposed || !this.isActive || !this.visualWorker || !this.instructionContext) {\n return;\n }\n\n await this.cacheManager.invalidateRange(range.startUs, range.endUs, this.clipId);\n await this.visualWorker.send('sync_clip', {\n clipId: this.clipId,\n revision: this.instructionContext.revision,\n range,\n });\n }\n\n get visualWorkerHandle(): BaseWorker | null {\n return this.visualWorker;\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip) return null;\n return this.compositionModel.resources.get(clip.resourceId) || null;\n }\n\n private async ensureInstructions(clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n\n await this.installInstructions(instructions, [\n {\n clipId: clip.id,\n trackId: clip.trackId || 'main',\n startUs: clip.startUs,\n endUs: clip.startUs + clip.durationUs,\n reason: 'initial-install',\n },\n ]);\n }\n\n private async setupPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip);\n await this.connectVideoPipeline(clip, resource.id);\n\n if (!this.visualWorker) {\n throw new Error('Visual worker unavailable');\n }\n\n // this.visualStream = await this.visualWorker.send<never, ReadableStream<VideoFrame>>(\n // 'get_stream'\n // );\n\n // if (this.visualStream) {\n // const visualConfig = this.workerConfigs.videoCompose ?? {};\n // const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n // this.callbacks.onStreamReady(this.clipId, this.visualStream, clip.trackId || 'main', fps);\n // }\n }\n\n private async acquireWorkers(clip: Clip): Promise<void> {\n this.visualWorker = await this.workerPool.get('videoCompose', clip.id, { lazy: true });\n this.decodeWorker = await this.workerPool.get('decode');\n this.encodeWorker = await this.workerPool.get('encode');\n this.videoDemuxWorker = await this.workerPool.get('videoDemux', clip.id, { lazy: true });\n\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n\n await this.visualWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n }\n\n private async connectVideoPipeline(clip: Clip, resourceId: string): Promise<void> {\n if (!this.visualWorker || !this.decodeWorker || !this.encodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n const clipId = clip.id;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onStreamReady(this.clipId, stream, fps);\n });\n }\n\n private async installInstructions(\n instructions: ClipInstructionSet,\n dirtyRanges: DirtyRange[],\n options?: { expectCacheInvalidation?: boolean; triggerSync?: boolean }\n ): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (options?.expectCacheInvalidation) {\n await this.invalidateRanges(dirtyRanges);\n }\n\n if (this.visualWorker) {\n await this.visualWorker.send('install_instructions', instructions);\n }\n\n if (options?.triggerSync) {\n await this.syncDirtyRanges(dirtyRanges, instructions.revision);\n }\n }\n\n private async restartPipeline(\n clip: Clip,\n resource: { id: string },\n instructions: ClipInstructionSet,\n dirtyRanges: DirtyRange[]\n ): Promise<void> {\n await this.releasePipeline();\n await this.setupPipeline(clip, resource);\n await this.installInstructions(instructions, dirtyRanges, {\n expectCacheInvalidation: true,\n triggerSync: true,\n });\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.visualWorker && this.instructionContext) {\n await this.visualWorker.notify('dispose_clip', {\n clipId: this.clipId,\n revision: this.instructionContext.revision,\n });\n }\n\n this.visualToEncodeChannel?.port1.close();\n this.visualToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n\n this.visualToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed(this.clipId);\n }\n this.visualStream = null;\n\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.clipId);\n this.videoDemuxWorker = null;\n }\n\n if (this.visualWorker) {\n this.workerPool.terminate('videoCompose', this.clipId);\n this.visualWorker = null;\n }\n\n // Shared workers are not terminated here (decode / encode)\n this.decodeWorker = null;\n this.encodeWorker = null;\n }\n\n private async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n\n private async invalidateRanges(ranges: DirtyRange[]): Promise<void> {\n for (const range of ranges) {\n await this.cacheManager.invalidateRange(range.startUs, range.endUs, this.clipId);\n }\n }\n\n private async syncDirtyRanges(ranges: DirtyRange[], revision: number): Promise<void> {\n if (!this.visualWorker) return;\n\n for (const range of ranges) {\n await this.visualWorker.send('sync_clip', {\n clipId: this.clipId,\n revision,\n range,\n });\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\n\ninterface VideoClipSessionCallbacks {\n onStreamReady(clipId: string, stream: ReadableStream<VideoFrame>, fps: number): void;\n onStreamDisposed?(clipId: string): void;\n onPipelineReady?(clipId: string): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n\n private instructionContext: InstructionContext | null = null;\n private visualWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private encodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private visualToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n }\n\n async activate(): Promise<void> {\n console.log('>>>>>>>>>>>> activate', this.clipId);\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n return;\n }\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n }\n\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady(this.clipId);\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.invalidateClipCache();\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n console.log('>>>>>>>>>>>> restartPipeline', this.clipId);\n await this.restartPipeline(clip, resource, instructions);\n } else {\n await this.activate();\n }\n }\n\n get visualWorkerHandle(): BaseWorker | null {\n return this.visualWorker;\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip) return null;\n return this.compositionModel.resources.get(clip.resourceId) || null;\n }\n\n private async ensureInstructions(_clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n await this.installInstructions(instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip, false);\n await this.connectImagePipeline(clip);\n }\n\n private async connectImagePipeline(clip: Clip): Promise<void> {\n if (!this.visualWorker || !this.encodeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n const clipId = clip.id;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onStreamReady(this.clipId, stream, fps);\n });\n }\n\n private async setupVideoPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n console.log('>>>>>>>>>>>> setupVideoPipeline', this.clipId);\n await this.acquireWorkers(clip, true);\n await this.connectVideoPipeline(clip, resource.id);\n }\n\n private async acquireWorkers(clip: Clip, isVideo: boolean): Promise<void> {\n if (isVideo) {\n this.decodeWorker = await this.workerPool.get('decode');\n this.videoDemuxWorker = await this.workerPool.get('videoDemux', clip.id, { lazy: true });\n }\n\n this.encodeWorker = await this.workerPool.get('encode');\n this.visualWorker = await this.workerPool.get('videoCompose', clip.id, { lazy: true });\n\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.visualWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n }\n\n private async connectVideoPipeline(clip: Clip, resourceId: string): Promise<void> {\n if (!this.visualWorker || !this.decodeWorker || !this.encodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n const clipId = clip.id;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onStreamReady(this.clipId, stream, fps);\n });\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (this.visualWorker) {\n await this.visualWorker.send('install_instructions', instructions);\n }\n }\n\n private async restartPipeline(\n clip: Clip,\n resource: { id: string; type: string },\n instructions: ClipInstructionSet\n ): Promise<void> {\n await this.releasePipeline();\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n }\n\n await this.installInstructions(instructions);\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.visualWorker && this.instructionContext) {\n await this.visualWorker.notify('dispose_clip', {\n clipId: this.clipId,\n revision: this.instructionContext.revision,\n });\n }\n\n this.visualToEncodeChannel?.port1.close();\n this.visualToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n\n this.visualToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed(this.clipId);\n }\n this.visualStream = null;\n\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.clipId);\n this.videoDemuxWorker = null;\n }\n\n if (this.visualWorker) {\n this.workerPool.terminate('videoCompose', this.clipId);\n this.visualWorker = null;\n }\n\n // Shared workers are not terminated here (decode / encode)\n this.decodeWorker = null;\n this.encodeWorker = null;\n }\n\n private async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -15,6 +15,7 @@ export interface OrchestratorConfig {
15
15
  };
16
16
  eventBus?: EventBus<EventPayloadMap>;
17
17
  workerPath?: string;
18
+ workerExtension?: string;
18
19
  }
19
20
  /**
20
21
  * Frame render options
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/orchestrator/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,MAAM,EACN,OAAO,EACR,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,QAAQ,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IACxE,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnD,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACnF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;IACb,gBAAgB,IAAI,MAAM,CAAC;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/orchestrator/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,MAAM,EACN,OAAO,EACR,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,QAAQ,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IACxE,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnD,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACnF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;IACb,gBAAgB,IAAI,MAAM,CAAC;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB"}
@@ -12,6 +12,11 @@ interface AudioDataMessage {
12
12
  clipStartUs: TimeUs;
13
13
  clipDurationUs: TimeUs;
14
14
  }
15
+ export interface AudioPlaybackInfo {
16
+ buffer: AudioBuffer;
17
+ offsetSeconds: number;
18
+ durationSeconds: number;
19
+ }
15
20
  interface AudioSessionDeps {
16
21
  cacheManager: CacheManager;
17
22
  workers: WorkerPool;
@@ -28,6 +33,7 @@ export declare class GlobalAudioSession {
28
33
  constructor(deps: AudioSessionDeps);
29
34
  onAudioData(message: AudioDataMessage): void;
30
35
  ensureMixedPCM(startUs: TimeUs): Promise<AudioBuffer | null>;
36
+ prepareAudioForPlayback(startUs: TimeUs): Promise<AudioPlaybackInfo | null>;
31
37
  activateAllAudioClips(): Promise<void>;
32
38
  handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void;
33
39
  reset(): void;