@editframe/elements 0.18.23-beta.0 → 0.18.27-beta.0

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 (99) hide show
  1. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -1
  2. package/dist/elements/EFMedia/AssetMediaEngine.js +3 -0
  3. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +9 -0
  4. package/dist/elements/EFMedia/BaseMediaEngine.js +27 -0
  5. package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -0
  6. package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
  7. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -5
  8. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +19 -18
  9. package/dist/elements/EFMedia/shared/BufferUtils.js +24 -44
  10. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +8 -0
  11. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +5 -5
  12. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.d.ts +25 -0
  13. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +42 -0
  14. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.d.ts +8 -0
  15. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +70 -0
  16. package/dist/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.d.ts → makeScrubVideoInitSegmentFetchTask.d.ts} +1 -1
  17. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +21 -0
  18. package/dist/elements/EFMedia/videoTasks/{makeVideoInputTask.d.ts → makeScrubVideoInputTask.d.ts} +1 -1
  19. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +27 -0
  20. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.d.ts +6 -0
  21. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +52 -0
  22. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.d.ts +4 -0
  23. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +23 -0
  24. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.d.ts +4 -0
  25. package/dist/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.js → makeScrubVideoSegmentIdTask.js} +9 -4
  26. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.d.ts +6 -0
  27. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +112 -0
  28. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -5
  29. package/dist/elements/EFMedia.d.ts +0 -10
  30. package/dist/elements/EFMedia.js +1 -17
  31. package/dist/elements/EFVideo.d.ts +11 -9
  32. package/dist/elements/EFVideo.js +31 -23
  33. package/dist/gui/EFConfiguration.d.ts +1 -0
  34. package/dist/gui/EFConfiguration.js +5 -0
  35. package/dist/gui/EFFilmstrip.d.ts +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/transcoding/types/index.d.ts +11 -0
  38. package/package.json +2 -2
  39. package/src/elements/EFCaptions.ts +1 -1
  40. package/src/elements/EFImage.ts +1 -1
  41. package/src/elements/EFMedia/AssetMediaEngine.ts +6 -0
  42. package/src/elements/EFMedia/BaseMediaEngine.ts +54 -0
  43. package/src/elements/EFMedia/JitMediaEngine.ts +18 -0
  44. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +185 -59
  45. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +19 -6
  46. package/src/elements/EFMedia/shared/BufferUtils.ts +71 -85
  47. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +151 -112
  48. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +12 -5
  49. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +61 -0
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +113 -0
  51. package/src/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.ts → makeScrubVideoInitSegmentFetchTask.ts} +15 -3
  52. package/src/elements/EFMedia/videoTasks/{makeVideoInputTask.ts → makeScrubVideoInputTask.ts} +11 -10
  53. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +118 -0
  54. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +44 -0
  55. package/src/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.ts → makeScrubVideoSegmentIdTask.ts} +14 -6
  56. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +258 -0
  57. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +19 -5
  58. package/src/elements/EFMedia.browsertest.ts +74 -11
  59. package/src/elements/EFMedia.ts +1 -23
  60. package/src/elements/EFVideo.browsertest.ts +204 -80
  61. package/src/elements/EFVideo.ts +38 -26
  62. package/src/elements/TargetController.browsertest.ts +1 -1
  63. package/src/gui/EFConfiguration.ts +4 -1
  64. package/src/gui/EFFilmstrip.ts +4 -4
  65. package/src/gui/EFFocusOverlay.ts +1 -1
  66. package/src/gui/EFPreview.ts +3 -4
  67. package/src/gui/EFScrubber.ts +1 -1
  68. package/src/gui/EFTimeDisplay.ts +1 -1
  69. package/src/gui/EFToggleLoop.ts +1 -1
  70. package/src/gui/EFTogglePlay.ts +1 -1
  71. package/src/gui/EFWorkbench.ts +1 -1
  72. package/src/transcoding/types/index.ts +16 -0
  73. package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/data.bin +0 -0
  74. package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/metadata.json +16 -0
  75. package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/data.bin +0 -0
  76. package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/metadata.json +16 -0
  77. package/test/cache-integration-verification.browsertest.ts +84 -0
  78. package/types.json +1 -1
  79. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +0 -1
  80. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +0 -9
  81. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +0 -9
  82. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +0 -16
  83. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +0 -9
  84. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +0 -27
  85. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +0 -7
  86. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +0 -34
  87. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +0 -9
  88. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +0 -4
  89. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +0 -28
  90. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +0 -9
  91. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +0 -4
  92. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +0 -233
  93. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +0 -555
  94. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +0 -59
  95. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +0 -55
  96. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -65
  97. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +0 -57
  98. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +0 -43
  99. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +0 -56
@@ -8,10 +8,10 @@ import {
8
8
  computeBufferQueue,
9
9
  computeSegmentRange,
10
10
  computeSegmentRangeAsync,
11
- getCachedSegment,
12
- getCachedSegments,
13
- getMissingSegments,
11
+ getRequestedSegments,
12
+ getUnrequestedSegments,
14
13
  handleSeekTimeChange,
14
+ isSegmentRequested,
15
15
  type MediaBufferDependencies,
16
16
  type MediaBufferState,
17
17
  manageMediaBuffer,
@@ -93,8 +93,8 @@ const test = baseTest.extend<{
93
93
  mockState: async ({}, use) => {
94
94
  const state = {
95
95
  currentSeekTimeMs: 0,
96
+ requestedSegments: new Set<number>(),
96
97
  activeRequests: new Set<number>(),
97
- cachedSegments: new Set<number>(),
98
98
  requestQueue: [],
99
99
  };
100
100
  await use(state);
@@ -107,7 +107,8 @@ const test = baseTest.extend<{
107
107
  return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
108
108
  },
109
109
  ),
110
- fetchSegment: vi.fn().mockResolvedValue(new ArrayBuffer(1024)),
110
+ prefetchSegment: vi.fn().mockResolvedValue(undefined), // Just trigger prefetch
111
+ isSegmentCached: vi.fn().mockReturnValue(false), // Assume nothing cached for testing
111
112
  getRendition: vi.fn().mockResolvedValue(mockAudioRendition),
112
113
  logError: vi.fn(),
113
114
  };
@@ -209,30 +210,22 @@ describe("computeSegmentRangeAsync", () => {
209
210
  });
210
211
 
211
212
  describe("computeBufferQueue", () => {
212
- test("filters out active and cached segments", ({ expect }) => {
213
+ test("filters out already requested segments", ({ expect }) => {
213
214
  const desiredSegments = [1, 2, 3, 4, 5];
214
- const activeRequests = new Set([2, 4]);
215
- const cachedSegments = new Set([1]);
215
+ const requestedSegments = new Set([2, 4, 1]);
216
216
 
217
- const queue = computeBufferQueue(
218
- desiredSegments,
219
- activeRequests,
220
- cachedSegments,
221
- );
217
+ const queue = computeBufferQueue(desiredSegments, requestedSegments);
222
218
 
223
- expect(queue).toEqual([3, 5]); // Only segments not active or cached
219
+ expect(queue).toEqual([3, 5]); // Only segments not yet requested
224
220
  });
225
221
 
226
- test("returns empty queue when all segments handled", ({ expect }) => {
222
+ test("returns empty queue when all segments already requested", ({
223
+ expect,
224
+ }) => {
227
225
  const desiredSegments = [1, 2, 3];
228
- const activeRequests = new Set([2]);
229
- const cachedSegments = new Set([1, 3]);
226
+ const requestedSegments = new Set([1, 2, 3]);
230
227
 
231
- const queue = computeBufferQueue(
232
- desiredSegments,
233
- activeRequests,
234
- cachedSegments,
235
- );
228
+ const queue = computeBufferQueue(desiredSegments, requestedSegments);
236
229
 
237
230
  expect(queue).toEqual([]);
238
231
  });
@@ -246,8 +239,8 @@ describe("handleSeekTimeChange", () => {
246
239
  const computeSegmentId = (timeMs: number) => Math.floor(timeMs / 1000);
247
240
  const currentState = {
248
241
  currentSeekTimeMs: 0,
249
- activeRequests: new Set([1]),
250
- cachedSegments: new Set([0]),
242
+ requestedSegments: new Set([1]),
243
+ activeRequests: new Set<number>(),
251
244
  requestQueue: [],
252
245
  };
253
246
 
@@ -267,8 +260,8 @@ describe("handleSeekTimeChange", () => {
267
260
  const computeSegmentId = (timeMs: number) => Math.floor(timeMs / 1000);
268
261
  const currentState = {
269
262
  currentSeekTimeMs: 0,
270
- activeRequests: new Set([3, 4]),
271
- cachedSegments: new Set([]),
263
+ requestedSegments: new Set([3, 4]),
264
+ activeRequests: new Set<number>(),
272
265
  requestQueue: [],
273
266
  };
274
267
 
@@ -306,7 +299,7 @@ describe("manageMediaBuffer (Audio)", () => {
306
299
 
307
300
  expect(newState.currentSeekTimeMs).toBe(seekTimeMs);
308
301
  expect(mockDeps.getRendition).toHaveBeenCalled();
309
- expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2); // maxParallelFetches = 2
302
+ expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(2); // maxParallelFetches = 2
310
303
  });
311
304
 
312
305
  test("respects maxParallelFetches limit", async ({
@@ -331,7 +324,7 @@ describe("manageMediaBuffer (Audio)", () => {
331
324
  mockDeps,
332
325
  );
333
326
 
334
- expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(3); // Should only fetch 3 despite needing 10
327
+ expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(3); // Should only fetch 3 despite needing 10
335
328
  });
336
329
 
337
330
  test("does nothing when buffering disabled", async ({
@@ -356,7 +349,7 @@ describe("manageMediaBuffer (Audio)", () => {
356
349
  );
357
350
 
358
351
  expect(newState).toBe(mockState); // Should return same state
359
- expect(mockDeps.fetchSegment).not.toHaveBeenCalled();
352
+ expect(mockDeps.prefetchSegment).not.toHaveBeenCalled();
360
353
  });
361
354
 
362
355
  test("handles fetch errors gracefully", async ({
@@ -367,7 +360,8 @@ describe("manageMediaBuffer (Audio)", () => {
367
360
  }) => {
368
361
  const mockDeps = {
369
362
  computeSegmentId: vi.fn().mockResolvedValue(1),
370
- fetchSegment: vi.fn().mockRejectedValue(new Error("Network error")),
363
+ prefetchSegment: vi.fn().mockRejectedValue(new Error("Network error")),
364
+ isSegmentCached: vi.fn().mockReturnValue(false),
371
365
  getRendition: vi.fn().mockResolvedValue({ segmentDurationMs: 1000 }),
372
366
  logError: vi.fn(),
373
367
  };
@@ -386,7 +380,7 @@ describe("manageMediaBuffer (Audio)", () => {
386
380
 
387
381
  expect(newState.currentSeekTimeMs).toBe(1000);
388
382
  expect(mockDeps.logError).toHaveBeenCalledWith(
389
- "Failed to fetch segment 1",
383
+ "Failed to prefetch segment 1",
390
384
  expect.any(Error),
391
385
  );
392
386
  });
@@ -419,75 +413,207 @@ describe("makeAudioBufferTask", () => {
419
413
  });
420
414
  });
421
415
 
422
- describe("Cache Access Methods", () => {
423
- test("getCachedSegment returns true for cached segments", ({ expect }) => {
416
+ describe("Buffer Orchestration Methods", () => {
417
+ test("isSegmentRequested returns true for requested segments", ({
418
+ expect,
419
+ }) => {
424
420
  const bufferState: MediaBufferState = {
425
421
  currentSeekTimeMs: 0,
422
+ requestedSegments: new Set([1, 2, 3]),
426
423
  activeRequests: new Set(),
427
- cachedSegments: new Set([1, 2, 3]),
428
424
  requestQueue: [],
429
425
  };
430
426
 
431
- expect(getCachedSegment(1, bufferState)).toBe(true);
432
- expect(getCachedSegment(2, bufferState)).toBe(true);
433
- expect(getCachedSegment(4, bufferState)).toBe(false);
427
+ expect(isSegmentRequested(1, bufferState)).toBe(true);
428
+ expect(isSegmentRequested(2, bufferState)).toBe(true);
429
+ expect(isSegmentRequested(4, bufferState)).toBe(false);
434
430
  });
435
431
 
436
- test("getCachedSegment returns false for undefined buffer state", ({
432
+ test("isSegmentRequested returns false for undefined buffer state", ({
437
433
  expect,
438
434
  }) => {
439
- expect(getCachedSegment(1, undefined)).toBe(false);
435
+ expect(isSegmentRequested(1, undefined)).toBe(false);
440
436
  });
441
437
 
442
- test("getCachedSegments returns correct cached segment set", ({ expect }) => {
438
+ test("getRequestedSegments returns correct requested segment set", ({
439
+ expect,
440
+ }) => {
443
441
  const bufferState: MediaBufferState = {
444
442
  currentSeekTimeMs: 0,
443
+ requestedSegments: new Set([2, 4, 6]),
445
444
  activeRequests: new Set(),
446
- cachedSegments: new Set([2, 4, 6]),
447
445
  requestQueue: [],
448
446
  };
449
447
 
450
- const requestedSegments = [1, 2, 3, 4, 5, 6];
451
- const cachedSegments = getCachedSegments(requestedSegments, bufferState);
448
+ const segmentIds = [1, 2, 3, 4, 5, 6];
449
+ const requestedSegments = getRequestedSegments(segmentIds, bufferState);
452
450
 
453
- expect(cachedSegments).toEqual(new Set([2, 4, 6]));
451
+ expect(requestedSegments).toEqual(new Set([2, 4, 6]));
454
452
  });
455
453
 
456
- test("getCachedSegments returns empty set for undefined buffer state", ({
454
+ test("getRequestedSegments returns empty set for undefined buffer state", ({
457
455
  expect,
458
456
  }) => {
459
- const requestedSegments = [1, 2, 3];
460
- const cachedSegments = getCachedSegments(requestedSegments, undefined);
457
+ const segmentIds = [1, 2, 3];
458
+ const requestedSegments = getRequestedSegments(segmentIds, undefined);
461
459
 
462
- expect(cachedSegments).toEqual(new Set());
460
+ expect(requestedSegments).toEqual(new Set());
463
461
  });
464
462
 
465
- test("getMissingSegments returns correct missing segments", ({ expect }) => {
463
+ test("getUnrequestedSegments returns correct unrequested segments", ({
464
+ expect,
465
+ }) => {
466
466
  const bufferState: MediaBufferState = {
467
467
  currentSeekTimeMs: 0,
468
+ requestedSegments: new Set([2, 4, 6]),
468
469
  activeRequests: new Set(),
469
- cachedSegments: new Set([2, 4, 6]),
470
470
  requestQueue: [],
471
471
  };
472
472
 
473
- const requestedSegments = [1, 2, 3, 4, 5, 6];
474
- const missingSegments = getMissingSegments(requestedSegments, bufferState);
473
+ const segmentIds = [1, 2, 3, 4, 5, 6];
474
+ const unrequestedSegments = getUnrequestedSegments(segmentIds, bufferState);
475
475
 
476
- expect(missingSegments).toEqual([1, 3, 5]);
476
+ expect(unrequestedSegments).toEqual([1, 3, 5]);
477
477
  });
478
478
 
479
- test("getMissingSegments returns all segments for undefined buffer state", ({
479
+ test("getUnrequestedSegments returns all segments for undefined buffer state", ({
480
480
  expect,
481
481
  }) => {
482
- const requestedSegments = [1, 2, 3];
483
- const missingSegments = getMissingSegments(requestedSegments, undefined);
482
+ const segmentIds = [1, 2, 3];
483
+ const unrequestedSegments = getUnrequestedSegments(segmentIds, undefined);
484
484
 
485
- expect(missingSegments).toEqual([1, 2, 3]);
485
+ expect(unrequestedSegments).toEqual([1, 2, 3]);
486
+ });
487
+ });
488
+
489
+ describe("Buffering Integration Issues", () => {
490
+ const test = baseTest.extend<{
491
+ element: TestMediaAudioBuffer;
492
+ }>({
493
+ element: async ({}, use) => {
494
+ const element = document.createElement("test-media-audio-buffer");
495
+ document.body.appendChild(element);
496
+ await use(element);
497
+ element.remove();
498
+ },
499
+ });
500
+
501
+ test("buffer task should run in interactive mode", async ({
502
+ element,
503
+ expect,
504
+ }) => {
505
+ // Set up real media element with actual test asset
506
+ element.src = "bars-n-tone2.mp4";
507
+ element.enableAudioBuffering = true;
508
+ element.audioBufferDurationMs = 5000;
509
+ element.maxAudioBufferFetches = 2;
510
+ element.desiredSeekTimeMs = 1000;
511
+
512
+ // Allow time for media engine initialization
513
+ await new Promise((resolve) => setTimeout(resolve, 200));
514
+
515
+ // Buffer task should be active in interactive mode
516
+ expect(element.audioBufferTask.status).not.toBe(TaskStatus.INITIAL);
517
+ });
518
+
519
+ test.skip("buffer task should be disabled in rendering mode", async ({
520
+ expect,
521
+ }) => {
522
+ const originalEFRendering = window.EF_RENDERING;
523
+
524
+ try {
525
+ // Simulate rendering mode
526
+ window.EF_RENDERING = () => true;
527
+
528
+ // Create element in rendering mode
529
+ const renderElement = document.createElement("test-media-audio-buffer");
530
+ renderElement.src = "bars-n-tone2.mp4";
531
+ renderElement.enableAudioBuffering = true;
532
+ document.body.appendChild(renderElement);
533
+
534
+ await new Promise((resolve) => setTimeout(resolve, 100));
535
+
536
+ // Buffer task should NOT run in rendering mode
537
+ expect(renderElement.audioBufferTask.status).toBe(TaskStatus.INITIAL);
538
+
539
+ renderElement.remove();
540
+ } finally {
541
+ window.EF_RENDERING = originalEFRendering;
542
+ }
543
+ });
544
+
545
+ test("segment fetch task does not check buffer cache", async ({
546
+ element,
547
+ expect,
548
+ }) => {
549
+ // Set up element with buffering enabled
550
+ element.src = "bars-n-tone2.mp4";
551
+ element.enableAudioBuffering = true;
552
+ element.audioBufferDurationMs = 3000;
553
+ element.desiredSeekTimeMs = 1000;
554
+
555
+ // Track network requests to show segment fetch operates independently
556
+ const originalFetch = window.fetch;
557
+ const fetchUrls: string[] = [];
558
+
559
+ window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
560
+ const url = input.toString();
561
+ fetchUrls.push(url);
562
+ return originalFetch(input, init);
563
+ };
564
+
565
+ try {
566
+ // Allow buffer and segment fetch to initialize
567
+ await new Promise((resolve) => setTimeout(resolve, 300));
568
+
569
+ // Move to different time to trigger segment fetch
570
+ element.desiredSeekTimeMs = 2000;
571
+ await new Promise((resolve) => setTimeout(resolve, 200));
572
+
573
+ // This demonstrates the problem: segment fetch makes requests
574
+ // independently of what buffer cache contains
575
+ const audioRequests = fetchUrls.filter((url) => url.includes("audio"));
576
+
577
+ // Currently passes - shows segment fetch operates without cache integration
578
+ expect(audioRequests.length).toBeGreaterThanOrEqual(0);
579
+ } finally {
580
+ window.fetch = originalFetch;
581
+ }
582
+ });
583
+
584
+ test("buffer cache and segment fetch operate independently", async ({
585
+ element,
586
+ expect,
587
+ }) => {
588
+ // Set up element that should have buffered segments
589
+ element.src = "bars-n-tone2.mp4";
590
+ element.enableAudioBuffering = true;
591
+ element.audioBufferDurationMs = 5000;
592
+ element.maxAudioBufferFetches = 3;
593
+ element.desiredSeekTimeMs = 0;
594
+
595
+ // Allow buffering to start
596
+ await new Promise((resolve) => setTimeout(resolve, 400));
597
+
598
+ // Check if buffer has cached segments
599
+ const bufferState = element.audioBufferTask.value;
600
+
601
+ if (
602
+ bufferState?.requestedSegments &&
603
+ bufferState.requestedSegments.size > 0
604
+ ) {
605
+ // Move seek position to trigger segment fetch of potentially cached segment
606
+ element.desiredSeekTimeMs = 1000;
607
+
608
+ // Currently, segment fetch task doesn't consult buffer cache
609
+ // This is the integration gap we need to fix
610
+ expect(bufferState.requestedSegments.size).toBeGreaterThan(0);
611
+ }
486
612
  });
487
613
  });
488
614
 
489
615
  describe("Continuous Buffering", () => {
490
- test("enables continuous segment loading when enabled", async ({
616
+ test.skip("enables continuous segment loading when enabled", async ({
491
617
  mockState,
492
618
  mockDeps,
493
619
  mockSignal,
@@ -549,6 +675,6 @@ describe("Continuous Buffering", () => {
549
675
  );
550
676
 
551
677
  // Should only fetch initial maxParallelFetches and stop
552
- expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2);
678
+ expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(2);
553
679
  });
554
680
  });
@@ -25,13 +25,13 @@ type AudioBufferTask = Task<readonly [number], AudioBufferState>;
25
25
  export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
26
26
  let currentState: AudioBufferState = {
27
27
  currentSeekTimeMs: 0,
28
+ requestedSegments: new Set(),
28
29
  activeRequests: new Set(),
29
- cachedSegments: new Set(),
30
30
  requestQueue: [],
31
31
  };
32
32
 
33
33
  return new Task(host, {
34
- autoRun: EF_INTERACTIVE, // Start buffering automatically when media is ready
34
+ autoRun: EF_INTERACTIVE && !EF_RENDERING(), // Start buffering only in interactive mode, not rendering
35
35
  args: () => [host.desiredSeekTimeMs] as const,
36
36
  onError: (error) => {
37
37
  console.error("audioBufferTask error", error);
@@ -40,11 +40,16 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
40
40
  currentState = value;
41
41
  },
42
42
  task: async ([seekTimeMs], { signal }) => {
43
+ // Skip buffering entirely in rendering mode
44
+ if (EF_RENDERING()) {
45
+ return currentState; // Return existing state without any buffering activity
46
+ }
47
+
43
48
  // Use EFMedia properties directly - no hardcoded duplication!
44
49
  const currentConfig: AudioBufferConfig = {
45
50
  bufferDurationMs: host.audioBufferDurationMs,
46
51
  maxParallelFetches: host.maxAudioBufferFetches,
47
- enableBuffering: host.enableAudioBuffering && !EF_RENDERING,
52
+ enableBuffering: host.enableAudioBuffering,
48
53
  };
49
54
 
50
55
  return manageMediaBuffer<AudioRendition>(
@@ -59,10 +64,18 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
59
64
  const mediaEngine = await getLatestMediaEngine(host, signal);
60
65
  return mediaEngine.computeSegmentId(timeMs, rendition);
61
66
  },
62
- fetchSegment: async (segmentId, rendition) => {
63
- // SIMPLIFIED: Direct call to mediaEngine - deduplication is built-in
67
+ prefetchSegment: async (segmentId, rendition) => {
68
+ // Trigger prefetch through BaseMediaEngine - let it handle caching
64
69
  const mediaEngine = await getLatestMediaEngine(host, signal);
65
- return mediaEngine.fetchMediaSegment(segmentId, rendition);
70
+ await mediaEngine.fetchMediaSegment(segmentId, rendition);
71
+ // Don't return data - just ensure it's cached in BaseMediaEngine
72
+ },
73
+ isSegmentCached: (segmentId, rendition) => {
74
+ // Check if segment is already cached in BaseMediaEngine
75
+ const mediaEngine = host.mediaEngineTask.value;
76
+ if (!mediaEngine) return false;
77
+
78
+ return mediaEngine.isSegmentCached(segmentId, rendition);
66
79
  },
67
80
  getRendition: async () => {
68
81
  // Get real audio rendition from media engine