@meframe/core 0.0.29 → 0.0.30

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 (218) hide show
  1. package/dist/Meframe.d.ts +2 -30
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +7 -118
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +42 -68
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +224 -188
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
  10. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  11. package/dist/cache/l1/VideoL1Cache.js +58 -38
  12. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  13. package/dist/cache/l2/L2Cache.d.ts.map +1 -1
  14. package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
  15. package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
  16. package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
  17. package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
  18. package/dist/cache/resource/AudioSampleCache.js +69 -0
  19. package/dist/cache/resource/AudioSampleCache.js.map +1 -0
  20. package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
  21. package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
  22. package/dist/cache/resource/ImageBitmapCache.js +101 -0
  23. package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
  24. package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
  25. package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
  26. package/dist/cache/resource/MP4IndexCache.js +104 -0
  27. package/dist/cache/resource/MP4IndexCache.js.map +1 -0
  28. package/dist/cache/resource/ResourceCache.d.ts +46 -0
  29. package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
  30. package/dist/cache/resource/ResourceCache.js +92 -0
  31. package/dist/cache/resource/ResourceCache.js.map +1 -0
  32. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
  33. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
  34. package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
  35. package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
  36. package/dist/cache/storage/opfs/OPFSManager.js +133 -0
  37. package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
  38. package/dist/cache/storage/opfs/types.d.ts +16 -0
  39. package/dist/cache/storage/opfs/types.d.ts.map +1 -0
  40. package/dist/config/defaults.d.ts.map +1 -1
  41. package/dist/config/defaults.js +21 -2
  42. package/dist/config/defaults.js.map +1 -1
  43. package/dist/config/types.d.ts +28 -0
  44. package/dist/config/types.d.ts.map +1 -1
  45. package/dist/controllers/ExportController.d.ts +16 -0
  46. package/dist/controllers/ExportController.d.ts.map +1 -0
  47. package/dist/controllers/ExportController.js +44 -0
  48. package/dist/controllers/ExportController.js.map +1 -0
  49. package/dist/controllers/PlaybackController.d.ts +28 -4
  50. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  51. package/dist/controllers/PlaybackController.js +115 -51
  52. package/dist/controllers/PlaybackController.js.map +1 -1
  53. package/dist/controllers/index.d.ts +2 -3
  54. package/dist/controllers/index.d.ts.map +1 -1
  55. package/dist/controllers/types.d.ts +0 -28
  56. package/dist/controllers/types.d.ts.map +1 -1
  57. package/dist/event/events.d.ts +8 -0
  58. package/dist/event/events.d.ts.map +1 -1
  59. package/dist/event/events.js +1 -0
  60. package/dist/event/events.js.map +1 -1
  61. package/dist/model/CompositionModel.d.ts.map +1 -1
  62. package/dist/model/CompositionModel.js +11 -6
  63. package/dist/model/CompositionModel.js.map +1 -1
  64. package/dist/model/RcFrame.d.ts +2 -0
  65. package/dist/model/RcFrame.d.ts.map +1 -1
  66. package/dist/model/RcFrame.js +3 -0
  67. package/dist/model/RcFrame.js.map +1 -1
  68. package/dist/model/types.d.ts +0 -1
  69. package/dist/model/types.d.ts.map +1 -1
  70. package/dist/model/types.js.map +1 -1
  71. package/dist/orchestrator/ExportScheduler.d.ts +35 -0
  72. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
  73. package/dist/orchestrator/ExportScheduler.js +241 -0
  74. package/dist/orchestrator/ExportScheduler.js.map +1 -0
  75. package/dist/orchestrator/GlobalAudioSession.d.ts +24 -9
  76. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  77. package/dist/orchestrator/GlobalAudioSession.js +149 -152
  78. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  79. package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
  80. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
  81. package/dist/orchestrator/OnDemandVideoSession.js +281 -0
  82. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
  83. package/dist/orchestrator/Orchestrator.d.ts +22 -17
  84. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  85. package/dist/orchestrator/Orchestrator.js +244 -312
  86. package/dist/orchestrator/Orchestrator.js.map +1 -1
  87. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  88. package/dist/orchestrator/VideoClipSession.js +3 -15
  89. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  90. package/dist/orchestrator/index.d.ts +0 -1
  91. package/dist/orchestrator/index.d.ts.map +1 -1
  92. package/dist/orchestrator/types.d.ts +4 -4
  93. package/dist/orchestrator/types.d.ts.map +1 -1
  94. package/dist/stages/compose/FilterProcessor.d.ts +1 -1
  95. package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
  96. package/dist/stages/compose/FilterProcessor.js +226 -0
  97. package/dist/stages/compose/FilterProcessor.js.map +1 -0
  98. package/dist/stages/compose/LayerRenderer.d.ts +1 -1
  99. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  100. package/dist/stages/compose/LayerRenderer.js +270 -0
  101. package/dist/stages/compose/LayerRenderer.js.map +1 -0
  102. package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
  103. package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
  104. package/dist/stages/compose/TransitionProcessor.js +189 -0
  105. package/dist/stages/compose/TransitionProcessor.js.map +1 -0
  106. package/dist/stages/compose/VideoComposer.d.ts +4 -2
  107. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  108. package/dist/stages/compose/VideoComposer.js +229 -0
  109. package/dist/stages/compose/VideoComposer.js.map +1 -0
  110. package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
  111. package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
  112. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
  113. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
  114. package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
  115. package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
  116. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
  117. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
  118. package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
  119. package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
  120. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
  121. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
  122. package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
  123. package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
  124. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
  125. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
  126. package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
  127. package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
  128. package/dist/stages/compose/text-utils/locale-detector.js +16 -0
  129. package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
  130. package/dist/stages/compose/text-utils/text-metrics.js +21 -0
  131. package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
  132. package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
  133. package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
  134. package/dist/stages/compose/types.d.ts +2 -1
  135. package/dist/stages/compose/types.d.ts.map +1 -1
  136. package/dist/stages/decode/BaseDecoder.js +0 -3
  137. package/dist/stages/decode/BaseDecoder.js.map +1 -1
  138. package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
  139. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  140. package/dist/stages/demux/MP4Demuxer.js +281 -0
  141. package/dist/stages/demux/MP4Demuxer.js.map +1 -0
  142. package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
  143. package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
  144. package/dist/stages/demux/MP4IndexParser.js +416 -0
  145. package/dist/stages/demux/MP4IndexParser.js.map +1 -0
  146. package/dist/stages/demux/types.d.ts +48 -0
  147. package/dist/stages/demux/types.d.ts.map +1 -1
  148. package/dist/stages/load/ResourceLoader.d.ts +50 -15
  149. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  150. package/dist/stages/load/ResourceLoader.js +297 -80
  151. package/dist/stages/load/ResourceLoader.js.map +1 -1
  152. package/dist/stages/load/TaskManager.d.ts +6 -2
  153. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  154. package/dist/stages/load/TaskManager.js +27 -4
  155. package/dist/stages/load/TaskManager.js.map +1 -1
  156. package/dist/stages/load/index.d.ts +0 -1
  157. package/dist/stages/load/index.d.ts.map +1 -1
  158. package/dist/stages/load/types.d.ts +9 -9
  159. package/dist/stages/load/types.d.ts.map +1 -1
  160. package/dist/stages/mux/MP4Muxer.d.ts +2 -2
  161. package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
  162. package/dist/stages/mux/MP4Muxer.js +24 -13
  163. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  164. package/dist/stages/mux/MuxManager.d.ts +10 -21
  165. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  166. package/dist/stages/mux/MuxManager.js +21 -162
  167. package/dist/stages/mux/MuxManager.js.map +1 -1
  168. package/dist/stages/mux/index.d.ts +0 -1
  169. package/dist/stages/mux/index.d.ts.map +1 -1
  170. package/dist/utils/binary-search.d.ts +12 -4
  171. package/dist/utils/binary-search.d.ts.map +1 -1
  172. package/dist/utils/binary-search.js +52 -6
  173. package/dist/utils/binary-search.js.map +1 -1
  174. package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
  175. package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
  176. package/dist/workers/{MP4Demuxer.lMOUMWFh.js → MP4Demuxer.BEa6PLJm.js} +9 -2
  177. package/dist/workers/{MP4Demuxer.lMOUMWFh.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
  178. package/dist/workers/stages/compose/{video-compose.worker.CIeEIJO7.js → video-compose.worker.DHQ8B105.js} +59 -31
  179. package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
  180. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
  181. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
  182. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
  183. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
  184. package/dist/workers/stages/demux/{audio-demux.worker.DcurGC8i.js → audio-demux.worker._VRQdLdv.js} +2 -2
  185. package/dist/workers/stages/demux/{audio-demux.worker.DcurGC8i.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
  186. package/dist/workers/stages/demux/{video-demux.worker.B1_wntU4.js → video-demux.worker.CSkxGtmx.js} +3 -19
  187. package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
  188. package/dist/workers/worker-manifest.json +5 -5
  189. package/package.json +1 -1
  190. package/dist/cache/l2/IndexedDBStore.js +0 -180
  191. package/dist/cache/l2/IndexedDBStore.js.map +0 -1
  192. package/dist/cache/l2/L2Cache.js +0 -329
  193. package/dist/cache/l2/L2Cache.js.map +0 -1
  194. package/dist/cache/l2/OPFSStore.js +0 -131
  195. package/dist/cache/l2/OPFSStore.js.map +0 -1
  196. package/dist/controllers/PreRenderService.d.ts +0 -59
  197. package/dist/controllers/PreRenderService.d.ts.map +0 -1
  198. package/dist/controllers/PreRenderService.js +0 -185
  199. package/dist/controllers/PreRenderService.js.map +0 -1
  200. package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
  201. package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
  202. package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
  203. package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
  204. package/dist/orchestrator/ClipSessionManager.js +0 -158
  205. package/dist/orchestrator/ClipSessionManager.js.map +0 -1
  206. package/dist/stages/decode/AudioChunkDecoder.js +0 -169
  207. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  208. package/dist/stages/load/EventHandlers.d.ts +0 -26
  209. package/dist/stages/load/EventHandlers.d.ts.map +0 -1
  210. package/dist/stages/load/EventHandlers.js +0 -42
  211. package/dist/stages/load/EventHandlers.js.map +0 -1
  212. package/dist/stages/mux/OPFSWriter.d.ts +0 -46
  213. package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
  214. package/dist/utils/BackpressureAdapter.d.ts +0 -26
  215. package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
  216. package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
  217. package/dist/workers/stages/compose/video-compose.worker.CIeEIJO7.js.map +0 -1
  218. package/dist/workers/stages/demux/video-demux.worker.B1_wntU4.js.map +0 -1
@@ -1,9 +1,10 @@
1
+ import { hasResourceId } from "../../model/types.js";
1
2
  import { TaskManager } from "./TaskManager.js";
2
3
  import { StreamFactory } from "./StreamFactory.js";
3
- import { EventHandlers } from "./EventHandlers.js";
4
4
  import { MeframeEvent } from "../../event/events.js";
5
5
  import { WindowByteRangeResolver } from "./WindowByteRangeResolver.js";
6
6
  import { createImageBitmapFromBlob } from "../../utils/image-utils.js";
7
+ import { MP4IndexParser } from "../demux/MP4IndexParser.js";
7
8
  class ResourceConflictError extends Error {
8
9
  constructor(message) {
9
10
  super(message);
@@ -11,51 +12,89 @@ class ResourceConflictError extends Error {
11
12
  }
12
13
  }
13
14
  class ResourceLoader {
14
- orchestrator;
15
+ cacheManager;
16
+ workerPool;
15
17
  model;
16
18
  taskManager;
17
19
  streamFactory;
18
- eventHandlers;
19
20
  eventBus;
20
21
  onStateChange;
21
22
  byteRangeResolver;
22
23
  blobCache = /* @__PURE__ */ new Map();
23
24
  pendingTransfers = /* @__PURE__ */ new Map();
25
+ parsingIndexes = /* @__PURE__ */ new Set();
26
+ // Track in-progress index parsing
27
+ // Preloading state
28
+ isPreloadingEnabled = true;
29
+ preloadQueue = [];
30
+ activePreloads = /* @__PURE__ */ new Set();
31
+ // TODO: make this configurable
32
+ IDLE_PRELOAD_CONCURRENCY = 2;
24
33
  constructor(options) {
25
- const maxConcurrent = options?.config?.maxConcurrent ?? 4;
34
+ const maxConcurrent = options.config?.maxConcurrent ?? 4;
26
35
  this.taskManager = new TaskManager(maxConcurrent);
27
- this.streamFactory = new StreamFactory(options?.onProgress, options?.config);
28
- this.eventBus = options?.eventBus;
29
- this.onStateChange = options?.onStateChange;
36
+ this.streamFactory = new StreamFactory(options.onProgress, options.config);
37
+ this.eventBus = options.eventBus;
38
+ this.onStateChange = options.onStateChange;
30
39
  this.byteRangeResolver = new WindowByteRangeResolver();
31
- if (options?.orchestrator) {
32
- this.bind(options.orchestrator);
40
+ this.cacheManager = options.cacheManager;
41
+ this.workerPool = options.workerPool;
42
+ }
43
+ async setModel(model) {
44
+ this.model = model;
45
+ const mainTrack = model.tracks.find((track) => track.id === (model.mainTrackId || "main"));
46
+ if (mainTrack?.clips?.[0] && hasResourceId(mainTrack.clips[0])) {
47
+ await this.fetch(mainTrack.clips[0].resourceId, {
48
+ priority: "high",
49
+ clipId: mainTrack.clips[0].id,
50
+ trackId: mainTrack.id
51
+ });
33
52
  }
53
+ this.startPreloading();
34
54
  }
35
- /**
36
- * Bind to Orchestrator event system
37
- */
38
- bind(orchestrator) {
39
- this.unbind();
40
- this.orchestrator = orchestrator;
41
- this.eventHandlers = new EventHandlers(
42
- orchestrator,
43
- (resourceId) => this.cancel(resourceId),
44
- (model) => this.handleModelSet(model)
45
- );
55
+ setPreloadingEnabled(enabled) {
56
+ this.isPreloadingEnabled = enabled;
57
+ if (enabled) {
58
+ this.startPreloading();
59
+ } else {
60
+ this.preloadQueue = [];
61
+ }
46
62
  }
47
- /**
48
- * Unbind from Orchestrator
49
- */
50
- unbind() {
51
- this.eventHandlers?.dispose();
52
- this.eventHandlers = void 0;
53
- this.orchestrator = void 0;
63
+ startPreloading() {
64
+ if (!this.model || !this.isPreloadingEnabled) return;
65
+ const mainTrack = this.model.tracks.find(
66
+ (track) => track.id === (this.model?.mainTrackId || "main")
67
+ );
68
+ if (!mainTrack) return;
69
+ const newQueue = [];
70
+ for (const clip of mainTrack.clips) {
71
+ if (!hasResourceId(clip)) continue;
72
+ const resource = this.model.getResource(clip.resourceId);
73
+ if (!resource) continue;
74
+ if (!resource || resource.state === "ready" || resource.state === "loading" || resource.state === "error") {
75
+ continue;
76
+ }
77
+ if (this.activePreloads.has(resource.id) || this.taskManager.hasActiveTask(resource.id)) {
78
+ continue;
79
+ }
80
+ newQueue.push(resource.id);
81
+ }
82
+ this.preloadQueue = newQueue;
83
+ this.processPreloadQueue();
54
84
  }
55
- handleModelSet(model) {
56
- this.model = model;
85
+ processPreloadQueue() {
86
+ if (!this.isPreloadingEnabled || this.preloadQueue.length === 0) return;
87
+ while (this.activePreloads.size < this.IDLE_PRELOAD_CONCURRENCY && this.preloadQueue.length > 0) {
88
+ const resourceId = this.preloadQueue.shift();
89
+ if (!resourceId) break;
90
+ this.activePreloads.add(resourceId);
91
+ this.fetch(resourceId, { priority: "low" }).finally(() => {
92
+ this.activePreloads.delete(resourceId);
93
+ this.processPreloadQueue();
94
+ });
95
+ }
57
96
  }
58
- enqueueLoad(resource, priority = "normal", sessionId, trackId, isMainTrack = false) {
97
+ enqueueLoad(resource, priority = "normal", sessionId, clipId, trackId, isMainTrack = false) {
59
98
  if (this.taskManager.hasActiveTask(resource.id)) {
60
99
  if (priority === "high") ;
61
100
  else if (isMainTrack) {
@@ -75,7 +114,7 @@ class ResourceLoader {
75
114
  }
76
115
  }
77
116
  }
78
- this.taskManager.enqueue(resource, priority, sessionId, trackId);
117
+ this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);
79
118
  this.processQueue();
80
119
  }
81
120
  registerPendingTransfer(resourceId, sessionId) {
@@ -99,12 +138,25 @@ class ResourceLoader {
99
138
  */
100
139
  async startLoad(task) {
101
140
  this.taskManager.activateTask(task);
141
+ let loadError;
102
142
  try {
103
143
  this.updateResourceState(task.resourceId, "loading");
104
144
  task.controller = new AbortController();
105
145
  if (task.resource.type === "image") {
106
146
  await this.loadImageBitmap(task);
107
- } else if (task.resource.type === "video" || task.resource.type === "audio") {
147
+ } else if (task.resource.type === "video") {
148
+ const cached = await this.cacheManager.hasResourceInCache(task.resourceId);
149
+ if (cached) {
150
+ await this.ensureIndexParsed(task.resourceId);
151
+ if (task.sessionId) {
152
+ const stream = await this.createOPFSReadStream(task.resourceId);
153
+ task.stream = stream;
154
+ await this.transferToDemuxWorker(task);
155
+ }
156
+ } else {
157
+ await this.loadWithOPFSCache(task);
158
+ }
159
+ } else if (task.resource.type === "audio") {
108
160
  const stream = await this.streamFactory.createRegularStream(task);
109
161
  if (!stream) {
110
162
  throw new Error(`Failed to create stream for ${task.resourceId}`);
@@ -117,12 +169,136 @@ class ResourceLoader {
117
169
  this.updateResourceState(task.resourceId, "ready");
118
170
  } catch (error) {
119
171
  task.error = error;
172
+ loadError = error;
120
173
  this.updateResourceState(task.resourceId, "error");
121
174
  } finally {
122
- this.taskManager.completeTask(task.resourceId);
175
+ this.taskManager.completeTask(task.resourceId, loadError);
123
176
  this.processQueue();
124
177
  }
125
178
  }
179
+ /**
180
+ * Ensure MP4 index is parsed for a cached resource
181
+ */
182
+ async ensureIndexParsed(resourceId) {
183
+ if (this.cacheManager.mp4IndexCache.has(resourceId)) {
184
+ return;
185
+ }
186
+ if (this.parsingIndexes.has(resourceId)) {
187
+ while (this.parsingIndexes.has(resourceId)) {
188
+ await new Promise((resolve) => setTimeout(resolve, 50));
189
+ }
190
+ return;
191
+ }
192
+ this.parsingIndexes.add(resourceId);
193
+ try {
194
+ const stream = await this.createOPFSReadStream(resourceId);
195
+ const parseTask = {
196
+ resourceId,
197
+ resource: { id: resourceId, type: "video", uri: "" },
198
+ bytesLoaded: 0,
199
+ totalBytes: 0,
200
+ startTime: Date.now(),
201
+ priority: "normal"
202
+ };
203
+ await this.parseIndexFromStream(parseTask, stream);
204
+ } finally {
205
+ this.parsingIndexes.delete(resourceId);
206
+ }
207
+ }
208
+ /**
209
+ * Create ReadableStream from OPFS file
210
+ * Reuses OPFSManager's underlying file access
211
+ */
212
+ async createOPFSReadStream(resourceId) {
213
+ const opfsManager = this.cacheManager.resourceCache.opfsManager;
214
+ const projectId = this.cacheManager.resourceCache.projectId;
215
+ const dir = await opfsManager.getProjectDir(projectId, "resource");
216
+ const fileName = `${resourceId}.mp4`;
217
+ const fileHandle = await dir.getFileHandle(fileName);
218
+ const file = await fileHandle.getFile();
219
+ return file.stream();
220
+ }
221
+ /**
222
+ * Load resource and cache to OPFS + parse moov + extract first frame
223
+ *
224
+ * Strategy: Parallel tee() approach for fast first frame
225
+ * - One stream writes to OPFS (background)
226
+ * - Another stream parses moov and extracts first GOP
227
+ * - First frame is decoded and cached immediately
228
+ */
229
+ async loadWithOPFSCache(task) {
230
+ const stream = await this.streamFactory.createRegularStream(task);
231
+ if (!stream) {
232
+ throw new Error(`Failed to create stream for ${task.resourceId}`);
233
+ }
234
+ let opfsStream;
235
+ let parseStream;
236
+ let workerStream;
237
+ if (task.sessionId) {
238
+ const [branch1, branch2] = stream.tee();
239
+ const [branch2a, branch2b] = branch2.tee();
240
+ opfsStream = branch1;
241
+ parseStream = branch2a;
242
+ workerStream = branch2b;
243
+ } else {
244
+ const [s1, s2] = stream.tee();
245
+ opfsStream = s1;
246
+ parseStream = s2;
247
+ }
248
+ const promises = [
249
+ this.writeToOPFS(task.resourceId, opfsStream),
250
+ this.parseIndexFromStream(task, parseStream)
251
+ ];
252
+ if (workerStream && task.sessionId) {
253
+ const workerTask = { ...task, stream: workerStream };
254
+ promises.push(this.transferToDemuxWorker(workerTask));
255
+ }
256
+ await Promise.all(promises);
257
+ }
258
+ /**
259
+ * Write resource stream to OPFS
260
+ */
261
+ async writeToOPFS(resourceId, stream) {
262
+ await this.cacheManager.resourceCache.writeResource(resourceId, stream);
263
+ }
264
+ /**
265
+ * Parse moov from stream and cache index + audio samples + decode first frame
266
+ */
267
+ async parseIndexFromStream(task, stream) {
268
+ const { resourceId, clipId } = task;
269
+ try {
270
+ const parser = new MP4IndexParser();
271
+ const result = await parser.parseFromStream(stream, {
272
+ onFirstFrameReady: async (index, chunks) => {
273
+ index.resourceId = resourceId;
274
+ this.cacheManager.mp4IndexCache.set(resourceId, index);
275
+ if (clipId) {
276
+ this.eventBus?.emit(MeframeEvent.ResourceFirstFrameReady, {
277
+ resourceId,
278
+ clipId,
279
+ index,
280
+ chunks
281
+ });
282
+ }
283
+ }
284
+ });
285
+ result.index.resourceId = resourceId;
286
+ if (!this.cacheManager.mp4IndexCache.has(resourceId)) {
287
+ this.cacheManager.mp4IndexCache.set(resourceId, result.index);
288
+ }
289
+ if (result.audioSamples && result.audioMetadata) {
290
+ this.cacheManager.audioSampleCache.set(
291
+ resourceId,
292
+ result.audioSamples,
293
+ result.audioMetadata
294
+ );
295
+ } else {
296
+ }
297
+ } catch (error) {
298
+ console.error(`[ResourceLoader] Failed to parse MP4 index for ${resourceId}:`, error);
299
+ throw error;
300
+ }
301
+ }
126
302
  /**
127
303
  * Load text-based resources (json, text)
128
304
  * Just download the content - state management is handled by startLoad()
@@ -131,8 +307,13 @@ class ResourceLoader {
131
307
  await this.fetchBlob(task.resource.uri, task.controller.signal);
132
308
  }
133
309
  /**
134
- * Load image resource: fetch blob → create ImageBitmap → transfer to worker
310
+ * Load image resource: fetch blob → create ImageBitmap → cache in CacheManager
135
311
  * Note: Images don't need streaming (typically < 5MB)
312
+ *
313
+ * Strategy for window cache architecture:
314
+ * - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)
315
+ * - OnDemandComposeSession retrieves from cache for composition
316
+ * - No longer transfer to Worker (composition is in main thread)
136
317
  */
137
318
  async loadImageBitmap(task) {
138
319
  let blob = this.blobCache.get(task.resourceId);
@@ -141,14 +322,7 @@ class ResourceLoader {
141
322
  this.blobCache.set(task.resourceId, blob);
142
323
  }
143
324
  const imageBitmap = await createImageBitmapFromBlob(blob);
144
- await this.transferImageToWorker(task, imageBitmap);
145
- const pending = this.pendingTransfers.get(task.resourceId);
146
- if (pending && pending.length > 0) {
147
- for (const sessionId of pending) {
148
- await this.transferCachedImage(task.resourceId, sessionId);
149
- }
150
- this.pendingTransfers.delete(task.resourceId);
151
- }
325
+ this.cacheManager.imageBitmapCache.set(task.resourceId, imageBitmap);
152
326
  }
153
327
  /**
154
328
  * Fetch resource as blob (for images, json, etc.)
@@ -162,25 +336,27 @@ class ResourceLoader {
162
336
  }
163
337
  /**
164
338
  * Transfer ImageBitmap to VideoComposeWorker
339
+ * Legacy: Not used in window cache architecture (images accessed via CacheManager)
165
340
  */
166
- async transferImageToWorker(task, imageBitmap) {
167
- if (!this.orchestrator) return;
168
- if (!task.sessionId) {
169
- throw new Error(
170
- `[ResourceLoader] sessionId required for resource ${task.resourceId}. In Clip-based architecture, use fetch(resourceId, { sessionId })`
171
- );
172
- }
173
- const composeWorker = await this.orchestrator.workers.get("videoCompose", task.sessionId);
174
- await composeWorker?.send?.(
175
- "receive_image",
176
- {
177
- resourceId: task.resourceId,
178
- sessionId: task.sessionId,
179
- imageBitmap
180
- },
181
- { transfer: [imageBitmap] }
182
- );
183
- }
341
+ // private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {
342
+ // if (!this.orchestrator) return;
343
+ // if (!task.sessionId) {
344
+ // throw new Error(
345
+ // `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +
346
+ // `In Clip-based architecture, use fetch(resourceId, { sessionId })`
347
+ // );
348
+ // }
349
+ // const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);
350
+ // await composeWorker?.send?.(
351
+ // 'receive_image',
352
+ // {
353
+ // resourceId: task.resourceId,
354
+ // sessionId: task.sessionId,
355
+ // imageBitmap,
356
+ // },
357
+ // { transfer: [imageBitmap] }
358
+ // );
359
+ // }
184
360
  /**
185
361
  * Transfer cached image to a session
186
362
  * Creates new ImageBitmap from cached Blob and transfers to worker
@@ -189,8 +365,7 @@ class ResourceLoader {
189
365
  const blob = this.blobCache.get(resourceId);
190
366
  if (!blob || !sessionId) return;
191
367
  const imageBitmap = await createImageBitmapFromBlob(blob);
192
- if (!this.orchestrator) return;
193
- const composeWorker = await this.orchestrator.workers.get("videoCompose", sessionId);
368
+ const composeWorker = await this.workerPool.get("videoCompose", sessionId);
194
369
  await composeWorker?.send?.(
195
370
  "receive_image",
196
371
  {
@@ -201,15 +376,18 @@ class ResourceLoader {
201
376
  { transfer: [imageBitmap] }
202
377
  );
203
378
  }
379
+ /**
380
+ * Transfer stream to demux worker (for audio files)
381
+ */
204
382
  async transferToDemuxWorker(task) {
205
- if (!task.stream || !this.orchestrator) return;
383
+ if (!task.stream) return;
206
384
  if (!task.sessionId) {
207
385
  throw new Error(
208
386
  `[ResourceLoader] sessionId required for resource ${task.resourceId}. In Clip-based architecture, use fetch(resourceId, { sessionId })`
209
387
  );
210
388
  }
211
389
  const workerType = task.resource.type === "video" ? "videoDemux" : "audioDemux";
212
- const demuxWorker = await this.orchestrator.workers.get(workerType, task.sessionId);
390
+ const demuxWorker = await this.workerPool.get(workerType, task.sessionId);
213
391
  if (demuxWorker) {
214
392
  await demuxWorker.sendStream(task.stream, {
215
393
  sessionId: task.sessionId,
@@ -226,14 +404,12 @@ class ResourceLoader {
226
404
  if (resource) {
227
405
  const oldState = resource.state;
228
406
  resource.state = state;
229
- if (this.orchestrator) {
230
- this.eventBus?.emit(MeframeEvent.ResourceStageChange, {
231
- type: MeframeEvent.ResourceStageChange,
232
- resourceId,
233
- oldState,
234
- newState: state
235
- });
236
- }
407
+ this.eventBus?.emit(MeframeEvent.ResourceStageChange, {
408
+ type: MeframeEvent.ResourceStageChange,
409
+ resourceId,
410
+ oldState,
411
+ newState: state
412
+ });
237
413
  }
238
414
  this.onStateChange?.(resourceId, state);
239
415
  }
@@ -246,13 +422,54 @@ class ResourceLoader {
246
422
  console.warn(`Resource ${resourceId} not found in model`);
247
423
  return;
248
424
  }
249
- this.enqueueLoad(
250
- resource,
251
- options?.priority || "normal",
252
- options?.sessionId,
253
- options?.trackId,
254
- options?.isMainTrack ?? false
255
- );
425
+ const transferResourceToWorker = async (rId, sId, type) => {
426
+ if (type === "video") {
427
+ const stream = await this.createOPFSReadStream(rId);
428
+ const task = {
429
+ resourceId: rId,
430
+ sessionId: sId,
431
+ resource: this.model?.resources.get(rId),
432
+ stream,
433
+ bytesLoaded: 0,
434
+ totalBytes: 0,
435
+ startTime: Date.now(),
436
+ priority: "normal"
437
+ };
438
+ await this.transferToDemuxWorker(task);
439
+ } else if (type === "image") {
440
+ await this.transferCachedImage(rId, sId);
441
+ }
442
+ };
443
+ if (resource.state === "ready") {
444
+ if (options?.sessionId) {
445
+ await transferResourceToWorker(resourceId, options.sessionId, resource.type);
446
+ }
447
+ return;
448
+ }
449
+ let taskPromise = this.taskManager.getTaskPromise(resourceId);
450
+ let isCoveredByTask = false;
451
+ if (taskPromise) {
452
+ const activeTask = this.taskManager.activeTasks.get(resourceId) || this.taskManager.taskQueue.find((t) => t.resourceId === resourceId);
453
+ if (activeTask && activeTask.sessionId === options?.sessionId) {
454
+ isCoveredByTask = true;
455
+ }
456
+ } else {
457
+ this.enqueueLoad(
458
+ resource,
459
+ options?.priority || "normal",
460
+ options?.sessionId,
461
+ options?.clipId,
462
+ options?.trackId,
463
+ options?.isMainTrack ?? false
464
+ );
465
+ taskPromise = this.taskManager.getTaskPromise(resourceId);
466
+ isCoveredByTask = true;
467
+ }
468
+ await taskPromise;
469
+ const updatedResource = this.model?.resources.get(resourceId);
470
+ if (!isCoveredByTask && updatedResource?.state === "ready" && options?.sessionId) {
471
+ await transferResourceToWorker(resourceId, options.sessionId, resource.type);
472
+ }
256
473
  }
257
474
  cancel(resourceId) {
258
475
  this.taskManager.cancelTask(resourceId);
@@ -281,6 +498,7 @@ class ResourceLoader {
281
498
  resource,
282
499
  options?.priority || "normal",
283
500
  options?.sessionId,
501
+ options?.clipId,
284
502
  options?.trackId
285
503
  );
286
504
  } else {
@@ -298,7 +516,6 @@ class ResourceLoader {
298
516
  this.byteRangeResolver.dispose();
299
517
  this.blobCache.clear();
300
518
  this.pendingTransfers.clear();
301
- this.unbind();
302
519
  }
303
520
  }
304
521
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import type { Resource, CompositionModel } from '../../model';\nimport type { Orchestrator, ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventHandlers } from './EventHandlers';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\nimport { createImageBitmapFromBlob } from '../../utils/image-utils';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private orchestrator?: Orchestrator;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventHandlers?: EventHandlers;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n private blobCache = new Map<string, Blob>();\n private pendingTransfers = new Map<string, string[]>();\n\n constructor(options?: ResourceLoaderOptions) {\n const maxConcurrent = options?.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options?.onProgress, options?.config);\n this.eventBus = options?.eventBus;\n this.onStateChange = options?.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n\n if (options?.orchestrator) {\n this.bind(options.orchestrator);\n }\n }\n\n /**\n * Bind to Orchestrator event system\n */\n bind(orchestrator: Orchestrator): void {\n this.unbind();\n this.orchestrator = orchestrator;\n\n this.eventHandlers = new EventHandlers(\n orchestrator,\n (resourceId) => this.cancel(resourceId),\n (model) => this.handleModelSet(model)\n );\n }\n\n /**\n * Unbind from Orchestrator\n */\n unbind(): void {\n this.eventHandlers?.dispose();\n this.eventHandlers = undefined;\n this.orchestrator = undefined;\n }\n\n private handleModelSet(model: CompositionModel): void {\n this.model = model;\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n trackId?: string,\n isMainTrack = false\n ): void {\n if (this.taskManager.hasActiveTask(resource.id)) {\n if (priority === 'high') {\n // Preview channel: high priority can preempt (existing logic preserved)\n } else if (isMainTrack) {\n // Main track resource conflict: throw error for PreRenderService to detect\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n } else {\n // Attachment resource conflict: check cache\n if (this.blobCache.has(resource.id)) {\n // Already cached: immediately create ImageBitmap and transfer\n void this.transferCachedImage(resource.id, sessionId);\n return;\n } else {\n // Loading in progress: register to pending queue, will transfer after load complete\n this.registerPendingTransfer(resource.id, sessionId);\n console.debug(\n `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`\n );\n return;\n }\n }\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, trackId);\n this.processQueue();\n }\n\n private registerPendingTransfer(resourceId: string, sessionId?: string): void {\n if (!sessionId) return;\n\n const pending = this.pendingTransfers.get(resourceId) || [];\n if (!pending.includes(sessionId)) {\n pending.push(sessionId);\n this.pendingTransfers.set(resourceId, pending);\n }\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n // Note: Each handler only deals with data, state is managed here\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video' || task.resource.type === 'audio') {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId);\n this.processQueue();\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → transfer to worker\n * Note: Images don't need streaming (typically < 5MB)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n // Check cache\n let blob = this.blobCache.get(task.resourceId);\n\n if (!blob) {\n // Not cached: download and cache\n blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n this.blobCache.set(task.resourceId, blob);\n }\n\n // Create ImageBitmap from Blob (with special handling for SVG)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n await this.transferImageToWorker(task, imageBitmap);\n\n // Process pending transfer queue\n const pending = this.pendingTransfers.get(task.resourceId);\n if (pending && pending.length > 0) {\n for (const sessionId of pending) {\n await this.transferCachedImage(task.resourceId, sessionId);\n }\n this.pendingTransfers.delete(task.resourceId);\n }\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n */\n private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n if (!this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);\n await composeWorker?.send?.(\n 'receive_image',\n {\n resourceId: task.resourceId,\n sessionId: task.sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n /**\n * Transfer cached image to a session\n * Creates new ImageBitmap from cached Blob and transfers to worker\n */\n private async transferCachedImage(resourceId: string, sessionId?: string): Promise<void> {\n const blob = this.blobCache.get(resourceId);\n if (!blob || !sessionId) return;\n\n // Create new ImageBitmap from cached Blob (with SVG support)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Transfer to Worker\n if (!this.orchestrator) return;\n\n const composeWorker = await this.orchestrator.workers.get('videoCompose', sessionId);\n\n await composeWorker?.send?.(\n 'receive_image',\n {\n resourceId,\n sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream || !this.orchestrator) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.orchestrator.workers.get(workerType, task.sessionId);\n if (demuxWorker) {\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n } else {\n // maybe the demux worker is terminated\n task.stream.cancel();\n }\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n if (this.orchestrator) {\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.trackId,\n options?.isMainTrack ?? false\n );\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.blobCache.clear();\n this.pendingTransfers.clear();\n this.unbind();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ResourceLoader.js","sources":["../../../src/stages/load/ResourceLoader.ts"],"sourcesContent":["import { type Resource, type CompositionModel, hasResourceId } from '../../model';\nimport type { ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';\nimport { TaskManager } from './TaskManager';\nimport { StreamFactory } from './StreamFactory';\nimport { EventPayloadMap, MeframeEvent } from '../../event/events';\nimport { EventBus } from '../../event/EventBus';\nimport { WindowByteRangeResolver } from './WindowByteRangeResolver';\nimport { createImageBitmapFromBlob } from '../../utils/image-utils';\nimport { MP4IndexParser, type MP4ParseResult } from '../demux/MP4IndexParser';\nimport type { CacheManager } from '../../cache/CacheManager';\nimport type { WorkerPool } from '../../worker/WorkerPool';\n\nexport class ResourceConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResourceConflictError';\n }\n}\n\nexport class ResourceLoader {\n private cacheManager: CacheManager;\n private workerPool: WorkerPool;\n private model?: CompositionModel;\n private taskManager: TaskManager;\n private streamFactory: StreamFactory;\n private eventBus?: EventBus<EventPayloadMap>;\n private onStateChange?: (resourceId: string, state: Resource['state']) => void;\n private byteRangeResolver: WindowByteRangeResolver;\n private blobCache = new Map<string, Blob>();\n private pendingTransfers = new Map<string, string[]>();\n private parsingIndexes = new Set<string>(); // Track in-progress index parsing\n\n // Preloading state\n private isPreloadingEnabled = true;\n private preloadQueue: string[] = [];\n private activePreloads = new Set<string>();\n // TODO: make this configurable\n private readonly IDLE_PRELOAD_CONCURRENCY = 2;\n\n constructor(options: ResourceLoaderOptions) {\n const maxConcurrent = options.config?.maxConcurrent ?? 4;\n this.taskManager = new TaskManager(maxConcurrent);\n this.streamFactory = new StreamFactory(options.onProgress, options.config);\n this.eventBus = options.eventBus;\n this.onStateChange = options.onStateChange;\n this.byteRangeResolver = new WindowByteRangeResolver();\n this.cacheManager = options.cacheManager;\n this.workerPool = options.workerPool;\n }\n\n async setModel(model: CompositionModel): Promise<void> {\n this.model = model;\n const mainTrack = model.tracks.find((track) => track.id === (model.mainTrackId || 'main'));\n if (mainTrack?.clips?.[0] && hasResourceId(mainTrack.clips[0])) {\n await this.fetch(mainTrack.clips[0].resourceId, {\n priority: 'high',\n clipId: mainTrack.clips[0].id,\n trackId: mainTrack.id,\n });\n }\n this.startPreloading();\n }\n\n setPreloadingEnabled(enabled: boolean): void {\n this.isPreloadingEnabled = enabled;\n if (enabled) {\n this.startPreloading();\n } else {\n // Clear queue if disabled, but let active preloads finish (they are low priority)\n this.preloadQueue = [];\n }\n }\n\n startPreloading(): void {\n if (!this.model || !this.isPreloadingEnabled) return;\n\n const mainTrack = this.model.tracks.find(\n (track) => track.id === (this.model?.mainTrackId || 'main')\n );\n if (!mainTrack) return;\n const newQueue: string[] = [];\n for (const clip of mainTrack.clips) {\n if (!hasResourceId(clip)) continue;\n const resource = this.model.getResource(clip.resourceId);\n if (!resource) continue;\n\n // Skip if already ready, loading, or error\n if (\n !resource ||\n resource.state === 'ready' ||\n resource.state === 'loading' ||\n resource.state === 'error'\n ) {\n continue;\n }\n\n // Also skip if currently in activePreloads or activeTasks\n if (this.activePreloads.has(resource.id) || this.taskManager.hasActiveTask(resource.id)) {\n continue;\n }\n\n newQueue.push(resource.id);\n }\n\n this.preloadQueue = newQueue;\n this.processPreloadQueue();\n }\n\n private processPreloadQueue(): void {\n if (!this.isPreloadingEnabled || this.preloadQueue.length === 0) return;\n\n while (\n this.activePreloads.size < this.IDLE_PRELOAD_CONCURRENCY &&\n this.preloadQueue.length > 0\n ) {\n const resourceId = this.preloadQueue.shift();\n if (!resourceId) break;\n\n this.activePreloads.add(resourceId);\n\n // Use low priority for preloading\n this.fetch(resourceId, { priority: 'low' }).finally(() => {\n this.activePreloads.delete(resourceId);\n // Continue processing queue\n this.processPreloadQueue();\n });\n }\n }\n\n private enqueueLoad(\n resource: Resource,\n priority: 'high' | 'normal' | 'low' = 'normal',\n sessionId?: string,\n clipId?: string,\n trackId?: string,\n isMainTrack = false\n ): void {\n if (this.taskManager.hasActiveTask(resource.id)) {\n if (priority === 'high') {\n // Preview channel: high priority can preempt (existing logic preserved)\n } else if (isMainTrack) {\n // Main track resource conflict: throw error for PreRenderService to detect\n throw new ResourceConflictError(\n `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`\n );\n } else {\n // Attachment resource conflict: check cache\n if (this.blobCache.has(resource.id)) {\n // Already cached: immediately create ImageBitmap and transfer\n void this.transferCachedImage(resource.id, sessionId);\n return;\n } else {\n // Loading in progress: register to pending queue, will transfer after load complete\n this.registerPendingTransfer(resource.id, sessionId);\n console.debug(\n `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`\n );\n return;\n }\n }\n }\n\n this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);\n this.processQueue();\n }\n\n private registerPendingTransfer(resourceId: string, sessionId?: string): void {\n if (!sessionId) return;\n\n const pending = this.pendingTransfers.get(resourceId) || [];\n if (!pending.includes(sessionId)) {\n pending.push(sessionId);\n this.pendingTransfers.set(resourceId, pending);\n }\n }\n\n private processQueue(): void {\n while (this.taskManager.canProcess) {\n const task = this.taskManager.getNextTask();\n if (!task) break;\n this.startLoad(task);\n }\n }\n\n /**\n * Start loading a resource\n * Handles state management (loading → ready/error) for all resource types\n */\n private async startLoad(task: LoadTask): Promise<void> {\n this.taskManager.activateTask(task);\n let loadError: Error | undefined;\n\n try {\n this.updateResourceState(task.resourceId, 'loading');\n task.controller = new AbortController();\n\n // Route to different handlers based on resource type\n if (task.resource.type === 'image') {\n await this.loadImageBitmap(task);\n } else if (task.resource.type === 'video') {\n // Video: OPFS cache + MP4 index parsing (for window cache architecture)\n const cached = await this.cacheManager.hasResourceInCache(task.resourceId);\n if (cached) {\n // Resource already in OPFS - ensure index is parsed\n await this.ensureIndexParsed(task.resourceId);\n\n // If sessionId is present (Worker Pipeline active), transfer OPFS stream to worker\n if (task.sessionId) {\n const stream = await this.createOPFSReadStream(task.resourceId);\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n }\n } else {\n // Not cached - download and cache to OPFS\n await this.loadWithOPFSCache(task);\n }\n } else if (task.resource.type === 'audio') {\n // Audio (MP3/WAV): Traditional pipeline (demux worker)\n // No OPFS cache or index parsing needed\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n task.stream = stream;\n await this.transferToDemuxWorker(task);\n } else if (task.resource.type === 'json' || task.resource.type === 'text') {\n await this.loadTextResource(task);\n }\n\n // Unified state update for all resource types\n this.updateResourceState(task.resourceId, 'ready');\n } catch (error) {\n task.error = error as Error;\n loadError = error as Error;\n this.updateResourceState(task.resourceId, 'error');\n } finally {\n this.taskManager.completeTask(task.resourceId, loadError);\n this.processQueue();\n }\n }\n\n /**\n * Ensure MP4 index is parsed for a cached resource\n */\n async ensureIndexParsed(resourceId: string): Promise<void> {\n // Check if index already exists\n if (this.cacheManager.mp4IndexCache.has(resourceId)) {\n return;\n }\n\n // Check if already parsing (avoid duplicate parsing for same resource)\n if (this.parsingIndexes.has(resourceId)) {\n // Wait for the in-progress parsing to complete\n while (this.parsingIndexes.has(resourceId)) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n return;\n }\n\n // Mark as parsing\n this.parsingIndexes.add(resourceId);\n\n try {\n // Parse from OPFS file\n const stream = await this.createOPFSReadStream(resourceId);\n // Create minimal task for parsing (no clipId since this is a background index parse)\n const parseTask: LoadTask = {\n resourceId,\n resource: { id: resourceId, type: 'video', uri: '' },\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.parseIndexFromStream(parseTask, stream);\n } finally {\n // Remove from parsing set\n this.parsingIndexes.delete(resourceId);\n }\n }\n\n /**\n * Create ReadableStream from OPFS file\n * Reuses OPFSManager's underlying file access\n */\n private async createOPFSReadStream(resourceId: string): Promise<ReadableStream<Uint8Array>> {\n const opfsManager = this.cacheManager.resourceCache.opfsManager;\n const projectId = this.cacheManager.resourceCache.projectId;\n\n // Get file handle from OPFS\n const dir = await opfsManager.getProjectDir(projectId, 'resource');\n const fileName = `${resourceId}.mp4`;\n const fileHandle = await dir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n\n // Return native stream\n return file.stream();\n }\n\n /**\n * Load resource and cache to OPFS + parse moov + extract first frame\n *\n * Strategy: Parallel tee() approach for fast first frame\n * - One stream writes to OPFS (background)\n * - Another stream parses moov and extracts first GOP\n * - First frame is decoded and cached immediately\n */\n private async loadWithOPFSCache(task: LoadTask): Promise<void> {\n const stream = await this.streamFactory.createRegularStream(task);\n if (!stream) {\n throw new Error(`Failed to create stream for ${task.resourceId}`);\n }\n\n // Prepare streams: 1 for OPFS, 1 for parsing, optional 1 for Worker\n let opfsStream: ReadableStream<Uint8Array>;\n let parseStream: ReadableStream<Uint8Array>;\n let workerStream: ReadableStream<Uint8Array> | undefined;\n\n if (task.sessionId) {\n // If worker needs it, split into 3 ways\n const [branch1, branch2] = stream.tee();\n const [branch2a, branch2b] = branch2.tee();\n\n opfsStream = branch1;\n parseStream = branch2a;\n workerStream = branch2b;\n } else {\n // Just 2 ways\n const [s1, s2] = stream.tee();\n opfsStream = s1;\n parseStream = s2;\n }\n\n const promises: Promise<void>[] = [\n this.writeToOPFS(task.resourceId, opfsStream),\n this.parseIndexFromStream(task, parseStream),\n ];\n\n if (workerStream && task.sessionId) {\n // Assign stream to task so transferToDemuxWorker uses it\n // Note: we need to clone the task or modify it carefully, but here it's safe\n // We create a temp task object or just modify current (since stream is consumed)\n // Actually transferToDemuxWorker uses task.stream.\n // We can't modify task.stream in place easily if type is readonly-ish, but it's not.\n const workerTask = { ...task, stream: workerStream };\n promises.push(this.transferToDemuxWorker(workerTask));\n }\n\n // Parallel execution\n await Promise.all(promises);\n }\n\n /**\n * Write resource stream to OPFS\n */\n private async writeToOPFS(resourceId: string, stream: ReadableStream<Uint8Array>): Promise<void> {\n await this.cacheManager.resourceCache.writeResource(resourceId, stream);\n }\n\n /**\n * Parse moov from stream and cache index + audio samples + decode first frame\n */\n private async parseIndexFromStream(\n task: LoadTask,\n stream: ReadableStream<Uint8Array>\n ): Promise<void> {\n const { resourceId, clipId } = task;\n\n try {\n const parser = new MP4IndexParser();\n\n const result: MP4ParseResult = await parser.parseFromStream(stream, {\n onFirstFrameReady: async (index, chunks) => {\n // Set resourceId on index\n index.resourceId = resourceId;\n\n // Cache index immediately\n this.cacheManager.mp4IndexCache.set(resourceId, index);\n\n // Emit event with chunks for Orchestrator to handle\n // Only if clipId is provided (indicates this is a preview/cover request)\n if (clipId) {\n this.eventBus?.emit(MeframeEvent.ResourceFirstFrameReady, {\n resourceId,\n clipId,\n index,\n chunks,\n });\n }\n },\n });\n\n result.index.resourceId = resourceId;\n\n // Cache index (if not already cached by onFirstFrameReady)\n if (!this.cacheManager.mp4IndexCache.has(resourceId)) {\n this.cacheManager.mp4IndexCache.set(resourceId, result.index);\n }\n\n // Cache audio samples if present\n if (result.audioSamples && result.audioMetadata) {\n this.cacheManager.audioSampleCache.set(\n resourceId,\n result.audioSamples,\n result.audioMetadata\n );\n } else {\n // Ensure cache knows this resource has NO audio\n // This prevents GlobalAudioSession from waiting for it\n // AudioSampleCache should ideally support storing \"empty\" state or we just rely on has() returning false\n // But has() returning false triggers fetch.\n // We need a way to say \"fetched, but no audio\".\n // Currently AudioSampleCache.set requires samples.\n // We might need to update AudioSampleCache to support explicit \"no audio\" record\n // Or we rely on MP4Index having audio track info.\n }\n } catch (error) {\n console.error(`[ResourceLoader] Failed to parse MP4 index for ${resourceId}:`, error);\n // Rethrow error to ensure resource is marked as failed\n // In the new architecture (Window Cache + AudioSampleCache), index parsing is critical.\n throw error;\n }\n }\n\n /**\n * Load text-based resources (json, text)\n * Just download the content - state management is handled by startLoad()\n */\n private async loadTextResource(task: LoadTask): Promise<void> {\n await this.fetchBlob(task.resource.uri, task.controller!.signal);\n // For json/text, just verify we can download it\n // Future: could parse and validate json here if needed\n }\n\n /**\n * Load image resource: fetch blob → create ImageBitmap → cache in CacheManager\n * Note: Images don't need streaming (typically < 5MB)\n *\n * Strategy for window cache architecture:\n * - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)\n * - OnDemandComposeSession retrieves from cache for composition\n * - No longer transfer to Worker (composition is in main thread)\n */\n private async loadImageBitmap(task: LoadTask): Promise<void> {\n // Check cache\n let blob = this.blobCache.get(task.resourceId);\n\n if (!blob) {\n // Not cached: download and cache\n blob = await this.fetchBlob(task.resource.uri, task.controller!.signal);\n this.blobCache.set(task.resourceId, blob);\n }\n\n // Create ImageBitmap from Blob (with special handling for SVG)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Store in CacheManager for main-thread access (OnDemandComposeSession)\n this.cacheManager.imageBitmapCache.set(task.resourceId, imageBitmap);\n\n // Legacy: Transfer to Worker if sessionId is provided (for old pipeline compatibility)\n // if (task.sessionId) {\n // await this.transferImageToWorker(task, imageBitmap);\n\n // // Process pending transfer queue\n // const pending = this.pendingTransfers.get(task.resourceId);\n // if (pending && pending.length > 0) {\n // for (const sessionId of pending) {\n // await this.transferCachedImage(task.resourceId, sessionId);\n // }\n // this.pendingTransfers.delete(task.resourceId);\n // }\n // }\n }\n\n /**\n * Fetch resource as blob (for images, json, etc.)\n */\n private async fetchBlob(uri: string, signal: AbortSignal): Promise<Blob> {\n const response = await fetch(uri, { signal });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response.blob();\n }\n\n /**\n * Transfer ImageBitmap to VideoComposeWorker\n * Legacy: Not used in window cache architecture (images accessed via CacheManager)\n */\n // private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {\n // if (!this.orchestrator) return;\n\n // if (!task.sessionId) {\n // throw new Error(\n // `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n // `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n // );\n // }\n\n // const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);\n // await composeWorker?.send?.(\n // 'receive_image',\n // {\n // resourceId: task.resourceId,\n // sessionId: task.sessionId,\n // imageBitmap,\n // },\n // { transfer: [imageBitmap] }\n // );\n // }\n\n /**\n * Transfer cached image to a session\n * Creates new ImageBitmap from cached Blob and transfers to worker\n */\n private async transferCachedImage(resourceId: string, sessionId?: string): Promise<void> {\n const blob = this.blobCache.get(resourceId);\n if (!blob || !sessionId) return;\n\n // Create new ImageBitmap from cached Blob (with SVG support)\n const imageBitmap = await createImageBitmapFromBlob(blob);\n\n // Transfer to Worker\n const composeWorker = await this.workerPool.get('videoCompose', sessionId);\n\n await composeWorker?.send?.(\n 'receive_image',\n {\n resourceId,\n sessionId,\n imageBitmap,\n },\n { transfer: [imageBitmap] }\n );\n }\n\n /**\n * Transfer stream to demux worker (for audio files)\n */\n private async transferToDemuxWorker(task: LoadTask): Promise<void> {\n if (!task.stream) return;\n\n if (!task.sessionId) {\n throw new Error(\n `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +\n `In Clip-based architecture, use fetch(resourceId, { sessionId })`\n );\n }\n\n const workerType = task.resource.type === 'video' ? 'videoDemux' : 'audioDemux';\n const demuxWorker = await this.workerPool.get(workerType, task.sessionId);\n if (demuxWorker) {\n await demuxWorker.sendStream(task.stream, {\n sessionId: task.sessionId,\n ...task.metadata,\n ...(task.range && { range: task.range }),\n ...(task.trackId && { trackId: task.trackId }),\n });\n } else {\n // Demux worker not ready - cancel stream\n task.stream.cancel();\n }\n }\n\n private updateResourceState(resourceId: string, state: Resource['state']): void {\n const resource = this.model?.resources.get(resourceId);\n if (resource) {\n const oldState = resource.state;\n resource.state = state;\n this.eventBus?.emit(MeframeEvent.ResourceStageChange, {\n type: MeframeEvent.ResourceStageChange,\n resourceId,\n oldState,\n newState: state,\n });\n }\n\n this.onStateChange?.(resourceId, state);\n }\n\n async fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void> {\n if (!resourceId) {\n return;\n }\n\n const resource = this.model?.resources.get(resourceId);\n if (!resource) {\n console.warn(`Resource ${resourceId} not found in model`);\n return;\n }\n\n const transferResourceToWorker = async (\n rId: string,\n sId: string,\n type: string\n ): Promise<void> => {\n if (type === 'video') {\n const stream = await this.createOPFSReadStream(rId);\n // Mock a task to reuse transfer logic\n const task: LoadTask = {\n resourceId: rId,\n sessionId: sId,\n resource: this.model?.resources.get(rId)!,\n stream,\n bytesLoaded: 0,\n totalBytes: 0,\n startTime: Date.now(),\n priority: 'normal',\n };\n await this.transferToDemuxWorker(task);\n } else if (type === 'image') {\n await this.transferCachedImage(rId, sId);\n }\n };\n\n // If resource is already ready, check if we need to transfer stream (Worker Pipeline)\n if (resource.state === 'ready') {\n if (options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n return;\n }\n\n // Check if task already exists\n let taskPromise = this.taskManager.getTaskPromise(resourceId);\n let isCoveredByTask = false;\n\n if (taskPromise) {\n // Check if existing task covers this session\n // Use activeTasks map + taskQueue search instead of getTaskFromQueue\n const activeTask =\n this.taskManager.activeTasks.get(resourceId) ||\n this.taskManager.taskQueue.find((t) => t.resourceId === resourceId);\n\n if (activeTask && activeTask.sessionId === options?.sessionId) {\n isCoveredByTask = true;\n }\n } else {\n // No task, we create it.\n // enqueue() will create the task with promise\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId,\n options?.isMainTrack ?? false\n );\n // Get the newly created promise\n taskPromise = this.taskManager.getTaskPromise(resourceId);\n isCoveredByTask = true;\n }\n\n // Wait for task to complete\n await taskPromise;\n\n // If we weren't the primary session for the task, transfer now from cache/OPFS\n // We must re-check resource.state here because it might have failed\n const updatedResource = this.model?.resources.get(resourceId);\n if (!isCoveredByTask && updatedResource?.state === 'ready' && options?.sessionId) {\n await transferResourceToWorker(resourceId, options.sessionId, resource.type);\n }\n }\n\n cancel(resourceId: string): void {\n this.taskManager.cancelTask(resourceId);\n this.processQueue();\n }\n\n /**\n * Check if a resource is currently being loaded\n */\n isResourceLoading(resourceId: string): boolean {\n return this.taskManager.hasActiveTask(resourceId);\n }\n\n pause(resourceId: string): void {\n const task = this.taskManager.getActiveTask(resourceId);\n if (task) {\n task.controller?.abort();\n }\n }\n\n async resume(resourceId: string, options?: ResourceLoadOptions): Promise<void> {\n const resource = this.model?.getResource(resourceId);\n if (!resource) {\n throw new Error(`Resource ${resourceId} not found`);\n }\n\n const pausedTask = this.taskManager.getActiveTask(resourceId);\n\n if (pausedTask?.pausedAt !== undefined) {\n this.enqueueLoad(\n resource,\n options?.priority || 'normal',\n options?.sessionId,\n options?.clipId,\n options?.trackId\n );\n } else {\n await this.fetch(resourceId, options);\n }\n }\n\n get activeTasks(): Map<string, LoadTask> {\n return this.taskManager.activeTasks;\n }\n\n get taskQueue(): LoadTask[] {\n return this.taskManager.taskQueue;\n }\n\n dispose(): void {\n this.taskManager.clear();\n this.byteRangeResolver.dispose();\n this.blobCache.clear();\n this.pendingTransfers.clear();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -14,10 +14,14 @@ export declare class TaskManager {
14
14
  * Check if a resource is already being loaded
15
15
  */
16
16
  hasActiveTask(resourceId: string): boolean;
17
+ /**
18
+ * Get task promise (task must already exist)
19
+ */
20
+ getTaskPromise(resourceId: string): Promise<void> | undefined;
17
21
  /**
18
22
  * Create and enqueue a new task
19
23
  */
20
- enqueue(resource: Resource, priority?: 'high' | 'normal' | 'low', sessionId?: string, trackId?: string): LoadTask;
24
+ enqueue(resource: Resource, priority?: 'high' | 'normal' | 'low', sessionId?: string, clipId?: string, trackId?: string): LoadTask;
21
25
  /**
22
26
  * Get next task from queue if under concurrent limit
23
27
  */
@@ -29,7 +33,7 @@ export declare class TaskManager {
29
33
  /**
30
34
  * Mark task as completed
31
35
  */
32
- completeTask(resourceId: string): void;
36
+ completeTask(resourceId: string, error?: Error): void;
33
37
  /**
34
38
  * Cancel a task
35
39
  */