@meframe/core 0.0.31 → 0.0.33

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 (72) hide show
  1. package/dist/Meframe.d.ts +2 -2
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +3 -2
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +12 -17
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +18 -280
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/AudioL1Cache.d.ts +36 -19
  10. package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
  11. package/dist/cache/l1/AudioL1Cache.js +182 -282
  12. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  13. package/dist/controllers/PlaybackController.d.ts +5 -3
  14. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  15. package/dist/controllers/PlaybackController.js +58 -16
  16. package/dist/controllers/PlaybackController.js.map +1 -1
  17. package/dist/event/events.d.ts +1 -1
  18. package/dist/event/events.d.ts.map +1 -1
  19. package/dist/event/events.js.map +1 -1
  20. package/dist/model/CompositionModel.d.ts +8 -0
  21. package/dist/model/CompositionModel.d.ts.map +1 -1
  22. package/dist/model/CompositionModel.js +18 -0
  23. package/dist/model/CompositionModel.js.map +1 -1
  24. package/dist/model/types.d.ts +0 -4
  25. package/dist/model/types.d.ts.map +1 -1
  26. package/dist/model/types.js.map +1 -1
  27. package/dist/orchestrator/ExportScheduler.d.ts +10 -0
  28. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  29. package/dist/orchestrator/ExportScheduler.js +66 -83
  30. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  31. package/dist/orchestrator/GlobalAudioSession.d.ts +35 -28
  32. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  33. package/dist/orchestrator/GlobalAudioSession.js +213 -422
  34. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  35. package/dist/orchestrator/OnDemandVideoSession.d.ts +3 -3
  36. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
  37. package/dist/orchestrator/OnDemandVideoSession.js +4 -4
  38. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
  39. package/dist/orchestrator/Orchestrator.d.ts +11 -4
  40. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  41. package/dist/orchestrator/Orchestrator.js +75 -68
  42. package/dist/orchestrator/Orchestrator.js.map +1 -1
  43. package/dist/orchestrator/VideoClipSession.d.ts +0 -2
  44. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  45. package/dist/orchestrator/VideoClipSession.js +0 -49
  46. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  47. package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
  48. package/dist/stages/compose/OfflineAudioMixer.js +13 -18
  49. package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
  50. package/dist/stages/decode/AudioChunkDecoder.js +169 -0
  51. package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
  52. package/dist/stages/demux/MP3FrameParser.js +186 -0
  53. package/dist/stages/demux/MP3FrameParser.js.map +1 -0
  54. package/dist/stages/load/ResourceLoader.d.ts +49 -30
  55. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  56. package/dist/stages/load/ResourceLoader.js +255 -189
  57. package/dist/stages/load/ResourceLoader.js.map +1 -1
  58. package/dist/stages/load/TaskManager.d.ts +4 -0
  59. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  60. package/dist/stages/load/TaskManager.js +11 -0
  61. package/dist/stages/load/TaskManager.js.map +1 -1
  62. package/dist/stages/load/types.d.ts +1 -0
  63. package/dist/stages/load/types.d.ts.map +1 -1
  64. package/dist/utils/audio-data.d.ts +16 -0
  65. package/dist/utils/audio-data.d.ts.map +1 -0
  66. package/dist/utils/audio-data.js +111 -0
  67. package/dist/utils/audio-data.js.map +1 -0
  68. package/package.json +1 -1
  69. package/dist/cache/resource/ImageBitmapCache.d.ts +0 -65
  70. package/dist/cache/resource/ImageBitmapCache.d.ts.map +0 -1
  71. package/dist/cache/resource/ImageBitmapCache.js +0 -101
  72. package/dist/cache/resource/ImageBitmapCache.js.map +0 -1
@@ -4,12 +4,7 @@ import { StreamFactory } from "./StreamFactory.js";
4
4
  import { MeframeEvent } from "../../event/events.js";
5
5
  import { createImageBitmapFromBlob } from "../../utils/image-utils.js";
6
6
  import { MP4IndexParser } from "../demux/MP4IndexParser.js";
7
- class ResourceConflictError extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = "ResourceConflictError";
11
- }
12
- }
7
+ import { MP3FrameParser } from "../demux/MP3FrameParser.js";
13
8
  class ResourceLoader {
14
9
  cacheManager;
15
10
  workerPool;
@@ -19,19 +14,16 @@ class ResourceLoader {
19
14
  eventBus;
20
15
  onStateChange;
21
16
  blobCache = /* @__PURE__ */ new Map();
22
- pendingTransfers = /* @__PURE__ */ new Map();
23
17
  parsingIndexes = /* @__PURE__ */ new Set();
24
18
  // Track in-progress index parsing
25
19
  // Preloading state
26
20
  isPreloadingEnabled = true;
27
21
  preloadQueue = [];
28
- activePreloads = /* @__PURE__ */ new Set();
29
- // TODO: make this configurable
30
- IDLE_PRELOAD_CONCURRENCY = 2;
31
22
  constructor(options) {
32
- const maxConcurrent = options.config?.maxConcurrent ?? 4;
23
+ const config = options.config || {};
24
+ const maxConcurrent = config.maxConcurrent ?? 2;
33
25
  this.taskManager = new TaskManager(maxConcurrent);
34
- this.streamFactory = new StreamFactory(options.onProgress, options.config);
26
+ this.streamFactory = new StreamFactory(options.onProgress, config);
35
27
  this.eventBus = options.eventBus;
36
28
  this.onStateChange = options.onStateChange;
37
29
  this.cacheManager = options.cacheManager;
@@ -41,7 +33,7 @@ class ResourceLoader {
41
33
  this.model = model;
42
34
  const mainTrack = model.tracks.find((track) => track.id === (model.mainTrackId || "main"));
43
35
  if (mainTrack?.clips?.[0] && hasResourceId(mainTrack.clips[0])) {
44
- await this.fetch(mainTrack.clips[0].resourceId, {
36
+ await this.load(mainTrack.clips[0].resourceId, {
45
37
  priority: "high",
46
38
  clipId: mainTrack.clips[0].id,
47
39
  trackId: mainTrack.id
@@ -53,8 +45,6 @@ class ResourceLoader {
53
45
  this.isPreloadingEnabled = enabled;
54
46
  if (enabled) {
55
47
  this.startPreloading();
56
- } else {
57
- this.preloadQueue = [];
58
48
  }
59
49
  }
60
50
  startPreloading() {
@@ -63,7 +53,6 @@ class ResourceLoader {
63
53
  (track) => track.id === (this.model?.mainTrackId || "main")
64
54
  );
65
55
  if (!mainTrack) return;
66
- const newQueue = [];
67
56
  for (const clip of mainTrack.clips) {
68
57
  if (!hasResourceId(clip)) continue;
69
58
  const resource = this.model.getResource(clip.resourceId);
@@ -71,56 +60,27 @@ class ResourceLoader {
71
60
  if (!resource || resource.state === "ready" || resource.state === "loading" || resource.state === "error") {
72
61
  continue;
73
62
  }
74
- if (this.activePreloads.has(resource.id) || this.taskManager.hasActiveTask(resource.id)) {
75
- continue;
76
- }
77
- newQueue.push(resource.id);
63
+ this.load(resource.id, { priority: "low" });
78
64
  }
79
- this.preloadQueue = newQueue;
80
- this.processPreloadQueue();
81
65
  }
82
66
  processPreloadQueue() {
83
67
  if (!this.isPreloadingEnabled || this.preloadQueue.length === 0) return;
84
- while (this.activePreloads.size < this.IDLE_PRELOAD_CONCURRENCY && this.preloadQueue.length > 0) {
68
+ while (this.preloadQueue.length > 0) {
85
69
  const resourceId = this.preloadQueue.shift();
86
70
  if (!resourceId) break;
87
- this.activePreloads.add(resourceId);
88
- this.fetch(resourceId, { priority: "low" }).finally(() => {
89
- this.activePreloads.delete(resourceId);
71
+ this.load(resourceId, { priority: "low" }).finally(() => {
90
72
  this.processPreloadQueue();
91
73
  });
92
74
  }
93
75
  }
94
- enqueueLoad(resource, priority = "normal", sessionId, clipId, trackId, isMainTrack = false) {
95
- if (this.taskManager.hasActiveTask(resource.id)) {
96
- if (priority === "high") ;
97
- else if (isMainTrack) {
98
- throw new ResourceConflictError(
99
- `Resource ${resource.id} is being loaded by another session. Preview channel has priority.`
100
- );
101
- } else {
102
- if (this.blobCache.has(resource.id)) {
103
- void this.transferCachedImage(resource.id, sessionId);
104
- return;
105
- } else {
106
- this.registerPendingTransfer(resource.id, sessionId);
107
- console.debug(
108
- `[ResourceLoader] Attachment resource ${resource.id} loading, registered for pending transfer`
109
- );
110
- return;
111
- }
112
- }
76
+ enqueueLoad(resource, priority = "normal", sessionId, clipId, trackId) {
77
+ const existingTask = this.taskManager.getActiveTask(resource.id);
78
+ if (existingTask) {
79
+ return existingTask;
113
80
  }
114
- this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);
81
+ const task = this.taskManager.enqueue(resource, priority, sessionId, clipId, trackId);
115
82
  this.processQueue();
116
- }
117
- registerPendingTransfer(resourceId, sessionId) {
118
- if (!sessionId) return;
119
- const pending = this.pendingTransfers.get(resourceId) || [];
120
- if (!pending.includes(sessionId)) {
121
- pending.push(sessionId);
122
- this.pendingTransfers.set(resourceId, pending);
123
- }
83
+ return task;
124
84
  }
125
85
  processQueue() {
126
86
  while (this.taskManager.canProcess) {
@@ -129,6 +89,103 @@ class ResourceLoader {
129
89
  this.startLoad(task);
130
90
  }
131
91
  }
92
+ /**
93
+ * Check if resource is cached and ready (without loading)
94
+ */
95
+ async isResourceCached(resourceId, type) {
96
+ switch (type) {
97
+ case "video": {
98
+ const hasOPFS = await this.cacheManager.hasResourceInCache(resourceId);
99
+ const hasIndex = this.cacheManager.mp4IndexCache.has(resourceId);
100
+ return hasOPFS && hasIndex;
101
+ }
102
+ case "audio":
103
+ return this.cacheManager.audioSampleCache.has(resourceId);
104
+ case "image":
105
+ return this.blobCache.has(resourceId);
106
+ case "json":
107
+ case "text":
108
+ return this.blobCache.has(resourceId);
109
+ default:
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * Transfer video stream to worker
115
+ */
116
+ async transferVideoToWorker(resourceId, sessionId) {
117
+ const stream = await this.createOPFSReadStream(resourceId);
118
+ const demuxWorker = await this.workerPool.get("videoDemux", sessionId);
119
+ if (demuxWorker) {
120
+ await demuxWorker.sendStream(stream, {
121
+ sessionId,
122
+ resourceId
123
+ });
124
+ } else {
125
+ stream.cancel();
126
+ }
127
+ }
128
+ /**
129
+ * Transfer image to worker
130
+ */
131
+ async transferImageToWorker(resourceId, sessionId, imageBitmap) {
132
+ const composeWorker = await this.workerPool.get("videoCompose", sessionId);
133
+ await composeWorker?.send?.(
134
+ "receive_image",
135
+ { resourceId, sessionId, imageBitmap },
136
+ { transfer: [imageBitmap] }
137
+ );
138
+ }
139
+ /**
140
+ * Load video resource (download + cache or read from cache)
141
+ */
142
+ async loadVideoResource(task) {
143
+ const cached = await this.cacheManager.hasResourceInCache(task.resourceId);
144
+ if (cached) {
145
+ await this.ensureIndexParsed(task.resourceId);
146
+ if (task.sessionId) {
147
+ await this.transferVideoToWorker(task.resourceId, task.sessionId);
148
+ }
149
+ } else {
150
+ await this.loadWithOPFSCache(task);
151
+ }
152
+ }
153
+ /**
154
+ * Load audio resource (download + parse or reuse cache)
155
+ */
156
+ async loadAudioResource(task) {
157
+ if (!this.cacheManager.audioSampleCache.has(task.resourceId)) {
158
+ await this.loadAndParseAudioFile(task);
159
+ }
160
+ }
161
+ /**
162
+ * Load image resource (download + cache or read from cache) and transfer to worker
163
+ */
164
+ async loadImageResource(task) {
165
+ let blob = this.blobCache.get(task.resourceId);
166
+ if (!blob) {
167
+ if (task.controller) {
168
+ blob = await this.fetchBlob(task.resource.uri, task.controller.signal);
169
+ this.blobCache.set(task.resourceId, blob);
170
+ } else {
171
+ return null;
172
+ }
173
+ }
174
+ const imageBitmap = await createImageBitmapFromBlob(blob);
175
+ if (task.sessionId) {
176
+ await this.transferImageToWorker(task.resourceId, task.sessionId, imageBitmap);
177
+ return null;
178
+ }
179
+ return imageBitmap;
180
+ }
181
+ /**
182
+ * Load text resource (json/text)
183
+ */
184
+ async loadTextResource(task) {
185
+ if (task.controller) {
186
+ await this.fetchBlob(task.resource.uri, task.controller.signal);
187
+ }
188
+ }
132
189
  /**
133
190
  * Start loading a resource
134
191
  * Handles state management (loading → ready/error) for all resource types
@@ -139,29 +196,24 @@ class ResourceLoader {
139
196
  try {
140
197
  this.updateResourceState(task.resourceId, "loading");
141
198
  task.controller = new AbortController();
142
- if (task.resource.type === "image") {
143
- await this.loadImageBitmap(task);
144
- } else if (task.resource.type === "video") {
145
- const cached = await this.cacheManager.hasResourceInCache(task.resourceId);
146
- if (cached) {
147
- await this.ensureIndexParsed(task.resourceId);
148
- if (task.sessionId) {
149
- const stream = await this.createOPFSReadStream(task.resourceId);
150
- task.stream = stream;
151
- await this.transferToDemuxWorker(task);
199
+ switch (task.resource.type) {
200
+ case "image": {
201
+ const image = await this.loadImageResource(task);
202
+ if (image) {
203
+ image.close();
152
204
  }
153
- } else {
154
- await this.loadWithOPFSCache(task);
155
- }
156
- } else if (task.resource.type === "audio") {
157
- const stream = await this.streamFactory.createRegularStream(task);
158
- if (!stream) {
159
- throw new Error(`Failed to create stream for ${task.resourceId}`);
205
+ break;
160
206
  }
161
- task.stream = stream;
162
- await this.transferToDemuxWorker(task);
163
- } else if (task.resource.type === "json" || task.resource.type === "text") {
164
- await this.loadTextResource(task);
207
+ case "video":
208
+ await this.loadVideoResource(task);
209
+ break;
210
+ case "audio":
211
+ await this.loadAudioResource(task);
212
+ break;
213
+ case "json":
214
+ case "text":
215
+ await this.loadTextResource(task);
216
+ break;
165
217
  }
166
218
  this.updateResourceState(task.resourceId, "ready");
167
219
  } catch (error) {
@@ -258,6 +310,44 @@ class ResourceLoader {
258
310
  async writeToOPFS(resourceId, stream) {
259
311
  await this.cacheManager.resourceCache.writeResource(resourceId, stream);
260
312
  }
313
+ /**
314
+ * Load and parse audio file (MP3/WAV) in main thread
315
+ * Extract EncodedAudioChunk and cache to AudioSampleCache
316
+ * Aligned with video audio track extraction (unified architecture)
317
+ */
318
+ async loadAndParseAudioFile(task) {
319
+ const { resourceId } = task;
320
+ try {
321
+ const blob = await this.fetchBlob(task.resource.uri, task.controller.signal);
322
+ const arrayBuffer = await blob.arrayBuffer();
323
+ const uint8Array = new Uint8Array(arrayBuffer);
324
+ const parser = new MP3FrameParser();
325
+ const { frames, config } = parser.push(uint8Array);
326
+ const remainingFrames = parser.flush();
327
+ const allFrames = [...frames, ...remainingFrames];
328
+ if (!config) {
329
+ throw new Error(`Failed to parse audio config for ${resourceId}`);
330
+ }
331
+ const audioChunks = allFrames.map((frame) => {
332
+ return new EncodedAudioChunk({
333
+ type: "key",
334
+ // MP3 frames are all key frames
335
+ timestamp: frame.timestampUs,
336
+ duration: frame.durationUs,
337
+ data: frame.data
338
+ });
339
+ });
340
+ const audioConfig = {
341
+ codec: "mp3",
342
+ sampleRate: config.sampleRate,
343
+ numberOfChannels: config.channels
344
+ };
345
+ this.cacheManager.audioSampleCache.set(resourceId, audioChunks, audioConfig);
346
+ } catch (error) {
347
+ console.error(`[ResourceLoader] Failed to parse audio file ${resourceId}:`, error);
348
+ throw error;
349
+ }
350
+ }
261
351
  /**
262
352
  * Parse moov from stream and cache index + audio samples + decode first frame
263
353
  */
@@ -296,30 +386,21 @@ class ResourceLoader {
296
386
  throw error;
297
387
  }
298
388
  }
299
- /**
300
- * Load text-based resources (json, text)
301
- * Just download the content - state management is handled by startLoad()
302
- */
303
- async loadTextResource(task) {
304
- await this.fetchBlob(task.resource.uri, task.controller.signal);
305
- }
306
- /**
307
- * Load image resource: fetch blob → create ImageBitmap → cache in CacheManager
308
- * Note: Images don't need streaming (typically < 5MB)
309
- *
310
- * Strategy for window cache architecture:
311
- * - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)
312
- * - OnDemandComposeSession retrieves from cache for composition
313
- * - No longer transfer to Worker (composition is in main thread)
314
- */
315
- async loadImageBitmap(task) {
316
- let blob = this.blobCache.get(task.resourceId);
317
- if (!blob) {
318
- blob = await this.fetchBlob(task.resource.uri, task.controller.signal);
319
- this.blobCache.set(task.resourceId, blob);
389
+ async loadImage(resource) {
390
+ const task = {
391
+ resourceId: resource.id,
392
+ resource,
393
+ bytesLoaded: 0,
394
+ totalBytes: 0,
395
+ startTime: Date.now(),
396
+ priority: "normal",
397
+ controller: new AbortController()
398
+ };
399
+ const imageBitmap = await this.loadImageResource(task);
400
+ if (!imageBitmap) {
401
+ throw new Error(`Failed to load image ${resource.id}`);
320
402
  }
321
- const imageBitmap = await createImageBitmapFromBlob(blob);
322
- this.cacheManager.imageBitmapCache.set(task.resourceId, imageBitmap);
403
+ return imageBitmap;
323
404
  }
324
405
  /**
325
406
  * Fetch resource as blob (for images, json, etc.)
@@ -331,57 +412,14 @@ class ResourceLoader {
331
412
  }
332
413
  return response.blob();
333
414
  }
334
- /**
335
- * Transfer ImageBitmap to VideoComposeWorker
336
- * Legacy: Not used in window cache architecture (images accessed via CacheManager)
337
- */
338
- // private async transferImageToWorker(task: LoadTask, imageBitmap: ImageBitmap): Promise<void> {
339
- // if (!this.orchestrator) return;
340
- // if (!task.sessionId) {
341
- // throw new Error(
342
- // `[ResourceLoader] sessionId required for resource ${task.resourceId}. ` +
343
- // `In Clip-based architecture, use fetch(resourceId, { sessionId })`
344
- // );
345
- // }
346
- // const composeWorker = await this.orchestrator.workers.get('videoCompose', task.sessionId);
347
- // await composeWorker?.send?.(
348
- // 'receive_image',
349
- // {
350
- // resourceId: task.resourceId,
351
- // sessionId: task.sessionId,
352
- // imageBitmap,
353
- // },
354
- // { transfer: [imageBitmap] }
355
- // );
356
- // }
357
- /**
358
- * Transfer cached image to a session
359
- * Creates new ImageBitmap from cached Blob and transfers to worker
360
- */
361
- async transferCachedImage(resourceId, sessionId) {
362
- const blob = this.blobCache.get(resourceId);
363
- if (!blob || !sessionId) return;
364
- const imageBitmap = await createImageBitmapFromBlob(blob);
365
- const composeWorker = await this.workerPool.get("videoCompose", sessionId);
366
- await composeWorker?.send?.(
367
- "receive_image",
368
- {
369
- resourceId,
370
- sessionId,
371
- imageBitmap
372
- },
373
- { transfer: [imageBitmap] }
374
- );
375
- }
376
415
  /**
377
416
  * Transfer stream to demux worker (for audio files)
378
417
  */
379
418
  async transferToDemuxWorker(task) {
380
419
  if (!task.stream) return;
381
420
  if (!task.sessionId) {
382
- throw new Error(
383
- `[ResourceLoader] sessionId required for resource ${task.resourceId}. In Clip-based architecture, use fetch(resourceId, { sessionId })`
384
- );
421
+ task.stream.cancel();
422
+ return;
385
423
  }
386
424
  const workerType = task.resource.type === "video" ? "videoDemux" : "audioDemux";
387
425
  const demuxWorker = await this.workerPool.get(workerType, task.sessionId);
@@ -410,7 +448,19 @@ class ResourceLoader {
410
448
  }
411
449
  this.onStateChange?.(resourceId, state);
412
450
  }
413
- async fetch(resourceId, options) {
451
+ /**
452
+ * Fetch a resource and wait for loading + parsing to complete
453
+ *
454
+ * Returns a Promise that resolves when:
455
+ * - Resource is fully loaded, parsed, and cached (state='ready')
456
+ * - Or rejects if loading/parsing fails
457
+ *
458
+ * Promise lifecycle:
459
+ * 1. enqueueLoad() creates LoadTask with promise/resolve/reject (or reuses existing)
460
+ * 2. processQueue() → startLoad() executes async in background
461
+ * 3. startLoad() completes → finally → completeTask() → task.resolve()/reject()
462
+ */
463
+ async load(resourceId, options) {
414
464
  if (!resourceId) {
415
465
  return;
416
466
  }
@@ -419,54 +469,72 @@ class ResourceLoader {
419
469
  console.warn(`Resource ${resourceId} not found in model`);
420
470
  return;
421
471
  }
422
- const transferResourceToWorker = async (rId, sId, type) => {
423
- if (type === "video") {
424
- const stream = await this.createOPFSReadStream(rId);
425
- const task = {
426
- resourceId: rId,
427
- sessionId: sId,
428
- resource: this.model?.resources.get(rId),
429
- stream,
430
- bytesLoaded: 0,
431
- totalBytes: 0,
432
- startTime: Date.now(),
433
- priority: "normal"
434
- };
435
- await this.transferToDemuxWorker(task);
436
- } else if (type === "image") {
437
- await this.transferCachedImage(rId, sId);
438
- }
439
- };
440
472
  if (resource.state === "ready") {
441
- if (options?.sessionId) {
442
- await transferResourceToWorker(resourceId, options.sessionId, resource.type);
473
+ if (!options?.sessionId) {
474
+ return;
475
+ }
476
+ const isCached = await this.isResourceCached(resourceId, resource.type);
477
+ const hasActiveTaskForSession = this.taskManager.hasActiveTaskForSession(
478
+ resourceId,
479
+ options.sessionId
480
+ );
481
+ if (isCached && !hasActiveTaskForSession) {
482
+ switch (resource.type) {
483
+ case "video":
484
+ await this.transferVideoToWorker(resourceId, options.sessionId);
485
+ break;
486
+ case "image": {
487
+ const blob = this.blobCache.get(resourceId);
488
+ if (blob) {
489
+ const imageBitmap = await createImageBitmapFromBlob(blob);
490
+ await this.transferImageToWorker(resourceId, options.sessionId, imageBitmap);
491
+ }
492
+ break;
493
+ }
494
+ }
495
+ return;
443
496
  }
444
- return;
445
497
  }
446
- let taskPromise = this.taskManager.getTaskPromise(resourceId);
447
- let isCoveredByTask = false;
448
- if (taskPromise) {
449
- const activeTask = this.taskManager.activeTasks.get(resourceId) || this.taskManager.taskQueue.find((t) => t.resourceId === resourceId);
450
- if (activeTask && activeTask.sessionId === options?.sessionId) {
451
- isCoveredByTask = true;
498
+ if (resource.state === "loading") {
499
+ const existingTask2 = this.taskManager.getActiveTask(resourceId);
500
+ if (existingTask2) {
501
+ if (!options?.sessionId || existingTask2.sessionId === options.sessionId) {
502
+ return existingTask2.promise;
503
+ }
452
504
  }
453
- } else {
454
- this.enqueueLoad(
455
- resource,
456
- options?.priority || "normal",
457
- options?.sessionId,
458
- options?.clipId,
459
- options?.trackId,
460
- options?.isMainTrack ?? false
461
- );
462
- taskPromise = this.taskManager.getTaskPromise(resourceId);
463
- isCoveredByTask = true;
464
505
  }
465
- await taskPromise;
466
- const updatedResource = this.model?.resources.get(resourceId);
467
- if (!isCoveredByTask && updatedResource?.state === "ready" && options?.sessionId) {
468
- await transferResourceToWorker(resourceId, options.sessionId, resource.type);
506
+ const existingTask = this.taskManager.getActiveTask(resourceId);
507
+ if (existingTask) {
508
+ if (options?.sessionId && !existingTask.sessionId) {
509
+ await existingTask.promise;
510
+ const isCached = await this.isResourceCached(resourceId, resource.type);
511
+ if (isCached) {
512
+ switch (resource.type) {
513
+ case "video":
514
+ await this.transferVideoToWorker(resourceId, options.sessionId);
515
+ break;
516
+ case "image": {
517
+ const blob = this.blobCache.get(resourceId);
518
+ if (blob) {
519
+ const imageBitmap = await createImageBitmapFromBlob(blob);
520
+ await this.transferImageToWorker(resourceId, options.sessionId, imageBitmap);
521
+ }
522
+ break;
523
+ }
524
+ }
525
+ }
526
+ return;
527
+ }
528
+ return existingTask.promise;
469
529
  }
530
+ const task = this.enqueueLoad(
531
+ resource,
532
+ options?.priority || "normal",
533
+ options?.sessionId,
534
+ options?.clipId,
535
+ options?.trackId
536
+ );
537
+ return task.promise;
470
538
  }
471
539
  cancel(resourceId) {
472
540
  this.taskManager.cancelTask(resourceId);
@@ -499,7 +567,7 @@ class ResourceLoader {
499
567
  options?.trackId
500
568
  );
501
569
  } else {
502
- await this.fetch(resourceId, options);
570
+ await this.load(resourceId, options);
503
571
  }
504
572
  }
505
573
  get activeTasks() {
@@ -511,11 +579,9 @@ class ResourceLoader {
511
579
  dispose() {
512
580
  this.taskManager.clear();
513
581
  this.blobCache.clear();
514
- this.pendingTransfers.clear();
515
582
  }
516
583
  }
517
584
  export {
518
- ResourceConflictError,
519
585
  ResourceLoader
520
586
  };
521
587
  //# sourceMappingURL=ResourceLoader.js.map