@editframe/elements 0.20.4-beta.0 → 0.23.6-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 (183) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +49 -11
  4. package/dist/_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js +7 -0
  5. package/dist/attachContextRoot.d.ts +1 -0
  6. package/dist/attachContextRoot.js +9 -0
  7. package/dist/elements/ContextProxiesController.d.ts +1 -2
  8. package/dist/elements/EFAudio.js +5 -9
  9. package/dist/elements/EFCaptions.d.ts +1 -3
  10. package/dist/elements/EFCaptions.js +112 -129
  11. package/dist/elements/EFImage.js +6 -7
  12. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -5
  13. package/dist/elements/EFMedia/AssetMediaEngine.js +36 -33
  14. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
  15. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  16. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -78
  17. package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +7 -13
  19. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -3
  20. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  21. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
  22. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  23. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
  24. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  25. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
  26. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +9 -25
  27. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -17
  28. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  29. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  30. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  31. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
  32. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  33. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  35. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
  36. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
  37. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  38. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
  40. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -18
  41. package/dist/elements/EFMedia.d.ts +19 -0
  42. package/dist/elements/EFMedia.js +44 -25
  43. package/dist/elements/EFSourceMixin.js +5 -7
  44. package/dist/elements/EFSurface.js +6 -9
  45. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  46. package/dist/elements/EFTemporal.d.ts +10 -0
  47. package/dist/elements/EFTemporal.js +100 -41
  48. package/dist/elements/EFThumbnailStrip.js +23 -73
  49. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  50. package/dist/elements/EFTimegroup.d.ts +35 -14
  51. package/dist/elements/EFTimegroup.js +138 -181
  52. package/dist/elements/EFVideo.d.ts +16 -2
  53. package/dist/elements/EFVideo.js +156 -108
  54. package/dist/elements/EFWaveform.js +23 -40
  55. package/dist/elements/SampleBuffer.js +3 -7
  56. package/dist/elements/TargetController.js +5 -5
  57. package/dist/elements/durationConverter.js +4 -4
  58. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  59. package/dist/elements/renderTemporalAudio.js +35 -0
  60. package/dist/elements/updateAnimations.js +19 -43
  61. package/dist/gui/ContextMixin.d.ts +5 -5
  62. package/dist/gui/ContextMixin.js +167 -162
  63. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  64. package/dist/gui/Controllable.d.ts +15 -0
  65. package/dist/gui/Controllable.js +9 -0
  66. package/dist/gui/EFConfiguration.js +7 -7
  67. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  68. package/dist/gui/EFControls.d.ts +18 -4
  69. package/dist/gui/EFControls.js +70 -28
  70. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  71. package/dist/gui/EFDial.d.ts +18 -0
  72. package/dist/gui/EFDial.js +141 -0
  73. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  74. package/dist/gui/EFFilmstrip.d.ts +12 -2
  75. package/dist/gui/EFFilmstrip.js +214 -129
  76. package/dist/gui/EFFitScale.js +5 -8
  77. package/dist/gui/EFFocusOverlay.js +4 -4
  78. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  79. package/dist/gui/EFPause.d.ts +23 -0
  80. package/dist/gui/EFPause.js +59 -0
  81. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  82. package/dist/gui/EFPlay.d.ts +23 -0
  83. package/dist/gui/EFPlay.js +59 -0
  84. package/dist/gui/EFPreview.d.ts +4 -0
  85. package/dist/gui/EFPreview.js +18 -9
  86. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  87. package/dist/gui/EFResizableBox.d.ts +34 -0
  88. package/dist/gui/EFResizableBox.js +547 -0
  89. package/dist/gui/EFScrubber.d.ts +9 -3
  90. package/dist/gui/EFScrubber.js +13 -13
  91. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  92. package/dist/gui/EFTimeDisplay.js +8 -8
  93. package/dist/gui/EFToggleLoop.d.ts +9 -3
  94. package/dist/gui/EFToggleLoop.js +7 -5
  95. package/dist/gui/EFTogglePlay.d.ts +12 -4
  96. package/dist/gui/EFTogglePlay.js +26 -21
  97. package/dist/gui/EFWorkbench.js +5 -5
  98. package/dist/gui/PlaybackController.d.ts +67 -0
  99. package/dist/gui/PlaybackController.js +310 -0
  100. package/dist/gui/TWMixin.js +1 -1
  101. package/dist/gui/TWMixin2.js +1 -1
  102. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  103. package/dist/gui/TargetOrContextMixin.js +98 -0
  104. package/dist/gui/efContext.d.ts +2 -2
  105. package/dist/index.d.ts +5 -0
  106. package/dist/index.js +5 -1
  107. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  108. package/dist/otel/BridgeSpanExporter.js +87 -0
  109. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  110. package/dist/otel/setupBrowserTracing.js +32 -0
  111. package/dist/otel/tracingHelpers.d.ts +34 -0
  112. package/dist/otel/tracingHelpers.js +112 -0
  113. package/dist/style.css +1 -1
  114. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  115. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  116. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  117. package/dist/utils/LRUCache.js +6 -53
  118. package/package.json +13 -5
  119. package/src/elements/ContextProxiesController.ts +10 -10
  120. package/src/elements/EFAudio.ts +1 -0
  121. package/src/elements/EFCaptions.browsertest.ts +128 -56
  122. package/src/elements/EFCaptions.ts +60 -34
  123. package/src/elements/EFImage.browsertest.ts +1 -2
  124. package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
  125. package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
  126. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  127. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
  129. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  130. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  131. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
  132. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  133. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
  134. package/src/elements/EFMedia.browsertest.ts +8 -15
  135. package/src/elements/EFMedia.ts +54 -8
  136. package/src/elements/EFSurface.browsertest.ts +2 -6
  137. package/src/elements/EFSurface.ts +1 -0
  138. package/src/elements/EFTemporal.browsertest.ts +58 -1
  139. package/src/elements/EFTemporal.ts +140 -4
  140. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  141. package/src/elements/EFThumbnailStrip.ts +1 -0
  142. package/src/elements/EFTimegroup.browsertest.ts +16 -15
  143. package/src/elements/EFTimegroup.ts +281 -275
  144. package/src/elements/EFVideo.browsertest.ts +162 -74
  145. package/src/elements/EFVideo.ts +229 -101
  146. package/src/elements/FetchContext.browsertest.ts +7 -2
  147. package/src/elements/TargetController.browsertest.ts +1 -0
  148. package/src/elements/TargetController.ts +1 -0
  149. package/src/elements/renderTemporalAudio.ts +108 -0
  150. package/src/elements/updateAnimations.browsertest.ts +181 -6
  151. package/src/elements/updateAnimations.ts +6 -6
  152. package/src/gui/ContextMixin.browsertest.ts +274 -27
  153. package/src/gui/ContextMixin.ts +230 -175
  154. package/src/gui/Controllable.browsertest.ts +258 -0
  155. package/src/gui/Controllable.ts +41 -0
  156. package/src/gui/EFControls.browsertest.ts +294 -80
  157. package/src/gui/EFControls.ts +139 -28
  158. package/src/gui/EFDial.browsertest.ts +84 -0
  159. package/src/gui/EFDial.ts +172 -0
  160. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  161. package/src/gui/EFFilmstrip.ts +213 -23
  162. package/src/gui/EFPause.browsertest.ts +202 -0
  163. package/src/gui/EFPause.ts +73 -0
  164. package/src/gui/EFPlay.browsertest.ts +202 -0
  165. package/src/gui/EFPlay.ts +73 -0
  166. package/src/gui/EFPreview.ts +20 -5
  167. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  168. package/src/gui/EFResizableBox.ts +898 -0
  169. package/src/gui/EFScrubber.ts +7 -5
  170. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  171. package/src/gui/EFTimeDisplay.ts +3 -1
  172. package/src/gui/EFToggleLoop.ts +6 -5
  173. package/src/gui/EFTogglePlay.ts +30 -23
  174. package/src/gui/PlaybackController.ts +522 -0
  175. package/src/gui/TWMixin.css +3 -0
  176. package/src/gui/TargetOrContextMixin.ts +185 -0
  177. package/src/gui/efContext.ts +2 -2
  178. package/src/otel/BridgeSpanExporter.ts +150 -0
  179. package/src/otel/setupBrowserTracing.ts +73 -0
  180. package/src/otel/tracingHelpers.ts +251 -0
  181. package/test/cache-integration-verification.browsertest.ts +1 -1
  182. package/types.json +1 -1
  183. package/dist/elements/ContextProxiesController.js +0 -69
@@ -12,11 +12,10 @@ beforeEach(() => {
12
12
  describe("EFCaptions", () => {
13
13
  describe("when rendering", () => {
14
14
  beforeEach(() => {
15
- // @ts-ignore
15
+ // @ts-expect-error
16
16
  window.FRAMEGEN_BRIDGE = true;
17
17
  });
18
18
  afterEach(() => {
19
- // @ts-ignore
20
19
  delete window.FRAMEGEN_BRIDGE;
21
20
  });
22
21
  test("captionsPath uses http:// protocol", () => {
@@ -84,7 +83,7 @@ describe("EFCaptions", () => {
84
83
  captions.captionsSrc = "test-captions-simple.json";
85
84
  document.body.appendChild(captions);
86
85
 
87
- await captions.updateComplete;
86
+ await captions.frameTask.taskComplete;
88
87
  // @ts-expect-error accessing private property for testing
89
88
  const captionsTask = captions.customCaptionsDataTask;
90
89
 
@@ -123,7 +122,7 @@ describe("EFCaptions", () => {
123
122
  captions.captionsScript = scriptId;
124
123
  document.body.appendChild(captions);
125
124
 
126
- await captions.updateComplete;
125
+ await captions.frameTask.taskComplete;
127
126
 
128
127
  // @ts-expect-error accessing private property for testing
129
128
  const captionsTask = captions.customCaptionsDataTask;
@@ -160,7 +159,7 @@ describe("EFCaptions", () => {
160
159
  captions.captionsData = testData;
161
160
  document.body.appendChild(captions);
162
161
 
163
- await captions.updateComplete;
162
+ await captions.frameTask.taskComplete;
164
163
  await captions.unifiedCaptionsDataTask.taskComplete;
165
164
 
166
165
  expect(captions.unifiedCaptionsDataTask.value).toEqual(testData);
@@ -205,7 +204,7 @@ describe("EFCaptions", () => {
205
204
  captions.captionsData = directData;
206
205
  document.body.appendChild(captions);
207
206
 
208
- await captions.updateComplete;
207
+ await captions.frameTask.taskComplete;
209
208
  await captions.unifiedCaptionsDataTask.taskComplete;
210
209
 
211
210
  // Should use direct property data, not script or file
@@ -227,7 +226,7 @@ describe("EFCaptions", () => {
227
226
  captions.captionsSrc = "nonexistent-file.json";
228
227
  document.body.appendChild(captions);
229
228
 
230
- await captions.updateComplete;
229
+ await captions.frameTask.taskComplete;
231
230
  await captions.unifiedCaptionsDataTask.taskComplete;
232
231
 
233
232
  // @ts-expect-error accessing private property for testing
@@ -256,7 +255,7 @@ describe("EFCaptions", () => {
256
255
  captions.captionsScript = scriptId;
257
256
  document.body.appendChild(captions);
258
257
 
259
- await captions.updateComplete;
258
+ await captions.frameTask.taskComplete;
260
259
 
261
260
  // @ts-expect-error accessing private property for testing
262
261
  const captionsTask = captions.customCaptionsDataTask;
@@ -290,6 +289,8 @@ describe("EFCaptions", () => {
290
289
  // Test at t=0 (first segment)
291
290
  timegroup.currentTimeMs = 0;
292
291
  await timegroup.seekTask.taskComplete;
292
+ await captions.frameTask.taskComplete;
293
+ await segmentContainer.updateComplete;
293
294
  expect(segmentContainer.segmentText).toBe("First test segment");
294
295
  expect(segmentContainer.segmentStartMs).toBe(0);
295
296
  expect(segmentContainer.segmentEndMs).toBe(3000);
@@ -297,6 +298,8 @@ describe("EFCaptions", () => {
297
298
  // Test at t=4000ms (second segment)
298
299
  timegroup.currentTimeMs = 4000;
299
300
  await timegroup.seekTask.taskComplete;
301
+ await captions.frameTask.taskComplete;
302
+ await segmentContainer.updateComplete;
300
303
  expect(segmentContainer.segmentText).toBe("Second test segment");
301
304
  expect(segmentContainer.segmentStartMs).toBe(3000);
302
305
  expect(segmentContainer.segmentEndMs).toBe(6000);
@@ -304,6 +307,8 @@ describe("EFCaptions", () => {
304
307
  // Test at t=7500ms (third segment)
305
308
  timegroup.currentTimeMs = 7500;
306
309
  await timegroup.seekTask.taskComplete;
310
+ await captions.frameTask.taskComplete;
311
+ await segmentContainer.updateComplete;
307
312
 
308
313
  expect(segmentContainer.segmentText).toBe("Third test segment");
309
314
  expect(segmentContainer.segmentStartMs).toBe(6000);
@@ -334,6 +339,8 @@ describe("EFCaptions", () => {
334
339
  // Test at t=0.3s (should be "First")
335
340
  timegroup.currentTimeMs = 300;
336
341
  await timegroup.seekTask.taskComplete;
342
+ await captions.frameTask.taskComplete;
343
+ await wordContainer.updateComplete;
337
344
  expect(wordContainer.wordText).toBe("First");
338
345
  expect(wordContainer.wordStartMs).toBe(0);
339
346
  expect(wordContainer.wordEndMs).toBe(600);
@@ -341,6 +348,8 @@ describe("EFCaptions", () => {
341
348
  // Test at t=0.9s (should be " test")
342
349
  timegroup.currentTimeMs = 900;
343
350
  await timegroup.seekTask.taskComplete;
351
+ await captions.frameTask.taskComplete;
352
+ await wordContainer.updateComplete;
344
353
  expect(wordContainer.wordText).toBe(" test");
345
354
  expect(wordContainer.wordStartMs).toBe(600);
346
355
  expect(wordContainer.wordEndMs).toBe(1200);
@@ -348,6 +357,8 @@ describe("EFCaptions", () => {
348
357
  // Test at t=1.8s (should be " segment")
349
358
  timegroup.currentTimeMs = 1800;
350
359
  await timegroup.seekTask.taskComplete;
360
+ await captions.frameTask.taskComplete;
361
+ await wordContainer.updateComplete;
351
362
  expect(wordContainer.wordText).toBe(" segment");
352
363
  expect(wordContainer.wordStartMs).toBe(1200);
353
364
  expect(wordContainer.wordEndMs).toBe(3000);
@@ -378,6 +389,7 @@ describe("EFCaptions", () => {
378
389
  captions.appendChild(afterContainer);
379
390
  timegroup.appendChild(captions);
380
391
  document.body.appendChild(timegroup);
392
+ await timegroup.waitForMediaDurations();
381
393
 
382
394
  // @ts-expect-error accessing private property for testing
383
395
  const captionsTask = captions.customCaptionsDataTask;
@@ -386,6 +398,8 @@ describe("EFCaptions", () => {
386
398
  // Test at t=1.0s (active word: "longer", context should be available)
387
399
  timegroup.currentTimeMs = 1000;
388
400
  await timegroup.seekTask.taskComplete;
401
+ await captions.frameTask.taskComplete;
402
+ await activeContainer.updateComplete;
389
403
 
390
404
  expect(activeContainer.wordText).toBe(" longer");
391
405
  expect(beforeContainer.segmentText).toBe("This is a");
@@ -451,8 +465,8 @@ describe("EFCaptions", () => {
451
465
  });
452
466
  });
453
467
 
454
- describe("display modes", () => {
455
- test("segment mode shows segment text", async () => {
468
+ describe("child element types", () => {
469
+ test("segment containers show segment text", async () => {
456
470
  const id = v4();
457
471
  const timegroup = document.createElement("ef-timegroup");
458
472
  const target = document.createElement("ef-video");
@@ -462,7 +476,6 @@ describe("EFCaptions", () => {
462
476
 
463
477
  const captions = document.createElement("ef-captions");
464
478
  captions.setAttribute("target", id);
465
- captions.displayMode = "segment";
466
479
  captions.captionsSrc = "test-captions-simple.json";
467
480
 
468
481
  const segmentContainer = document.createElement("ef-captions-segment");
@@ -476,10 +489,11 @@ describe("EFCaptions", () => {
476
489
 
477
490
  timegroup.currentTimeMs = 1500;
478
491
  await timegroup.seekTask.taskComplete;
492
+ await captions.frameTask.taskComplete;
479
493
  expect(segmentContainer.segmentText).toBe("First test segment");
480
494
  });
481
495
 
482
- test("word mode shows active word", async () => {
496
+ test("word containers show active word", async () => {
483
497
  const id = v4();
484
498
  const timegroup = document.createElement("ef-timegroup");
485
499
  const target = document.createElement("ef-video");
@@ -489,7 +503,6 @@ describe("EFCaptions", () => {
489
503
 
490
504
  const captions = document.createElement("ef-captions");
491
505
  captions.setAttribute("target", id);
492
- captions.displayMode = "word";
493
506
  captions.captionsSrc = "test-captions-simple.json";
494
507
 
495
508
  const wordContainer = document.createElement("ef-captions-active-word");
@@ -497,16 +510,19 @@ describe("EFCaptions", () => {
497
510
  timegroup.appendChild(captions);
498
511
  document.body.appendChild(timegroup);
499
512
 
513
+ await timegroup.waitForMediaDurations();
514
+
500
515
  // @ts-expect-error accessing private property for testing
501
516
  const captionsTask = captions.customCaptionsDataTask;
502
517
  await captionsTask.taskComplete;
503
518
 
504
519
  timegroup.currentTimeMs = 900;
505
520
  await timegroup.seekTask.taskComplete;
521
+ await captions.frameTask.taskComplete;
506
522
  expect(wordContainer.wordText).toBe(" test");
507
523
  });
508
524
 
509
- test("context mode shows before/active/after words", async () => {
525
+ test("context containers show before/active/after words", async () => {
510
526
  const id = v4();
511
527
  const timegroup = document.createElement("ef-timegroup");
512
528
  const target = document.createElement("ef-video");
@@ -516,7 +532,6 @@ describe("EFCaptions", () => {
516
532
 
517
533
  const captions = document.createElement("ef-captions");
518
534
  captions.setAttribute("target", id);
519
- captions.displayMode = "context";
520
535
  captions.captionsSrc = "test-captions-complex.json";
521
536
 
522
537
  const beforeContainer = document.createElement(
@@ -532,6 +547,7 @@ describe("EFCaptions", () => {
532
547
  captions.appendChild(afterContainer);
533
548
  timegroup.appendChild(captions);
534
549
  document.body.appendChild(timegroup);
550
+ await timegroup.waitForMediaDurations();
535
551
 
536
552
  // @ts-expect-error accessing private property for testing
537
553
  const captionsTask = captions.customCaptionsDataTask;
@@ -540,6 +556,7 @@ describe("EFCaptions", () => {
540
556
  // Test middle of first segment
541
557
  timegroup.currentTimeMs = 2400; // during "multiple"
542
558
  await timegroup.seekTask.taskComplete;
559
+ await captions.frameTask.taskComplete;
543
560
 
544
561
  expect(activeContainer.wordText).toBe(" multiple");
545
562
  expect(beforeContainer.segmentText).toBeTruthy();
@@ -577,6 +594,7 @@ describe("EFCaptions", () => {
577
594
  // Test first word "First" (0-0.6s)
578
595
  timegroup.currentTimeMs = 300;
579
596
  await timegroup.seekTask.taskComplete;
597
+ await captions.frameTask.taskComplete;
580
598
 
581
599
  expect(wordContainer.startTimeMs).toBe(0);
582
600
  expect(wordContainer.durationMs).toBe(600);
@@ -584,6 +602,7 @@ describe("EFCaptions", () => {
584
602
  // Test second word " test" (0.6-1.2s)
585
603
  timegroup.currentTimeMs = 900;
586
604
  await timegroup.seekTask.taskComplete;
605
+ await captions.frameTask.taskComplete;
587
606
 
588
607
  expect(wordContainer.startTimeMs).toBe(600);
589
608
  expect(wordContainer.durationMs).toBe(600);
@@ -613,6 +632,7 @@ describe("EFCaptions", () => {
613
632
  // Test first segment (0-3s)
614
633
  timegroup.currentTimeMs = 1500;
615
634
  await timegroup.seekTask.taskComplete;
635
+ await captions.frameTask.taskComplete;
616
636
 
617
637
  expect(segmentContainer.startTimeMs).toBe(0);
618
638
  expect(segmentContainer.durationMs).toBe(3000);
@@ -620,6 +640,7 @@ describe("EFCaptions", () => {
620
640
  // Test second segment (3-6s)
621
641
  timegroup.currentTimeMs = 4500;
622
642
  await timegroup.seekTask.taskComplete;
643
+ await captions.frameTask.taskComplete;
623
644
 
624
645
  expect(segmentContainer.startTimeMs).toBe(3000);
625
646
  expect(segmentContainer.durationMs).toBe(3000);
@@ -650,6 +671,7 @@ describe("EFCaptions", () => {
650
671
  captions.appendChild(afterContainer);
651
672
  timegroup.appendChild(captions);
652
673
  document.body.appendChild(timegroup);
674
+ await timegroup.waitForMediaDurations();
653
675
 
654
676
  // @ts-expect-error accessing private property for testing
655
677
  const captionsTask = captions.customCaptionsDataTask;
@@ -658,6 +680,7 @@ describe("EFCaptions", () => {
658
680
  // Test during "longer" word (0.8-1.3s) in first segment (0-4s)
659
681
  timegroup.currentTimeMs = 1000;
660
682
  await timegroup.seekTask.taskComplete;
683
+ await captions.frameTask.taskComplete;
661
684
 
662
685
  // Active word timing
663
686
  expect(activeContainer.startTimeMs).toBe(800);
@@ -705,6 +728,7 @@ describe("EFCaptions", () => {
705
728
  for (const step of timingSteps) {
706
729
  timegroup.currentTimeMs = step.time;
707
730
  await timegroup.seekTask.taskComplete;
731
+ await captions.frameTask.taskComplete;
708
732
 
709
733
  expect(wordContainer.startTimeMs).toBe(step.expectedStart);
710
734
  expect(wordContainer.durationMs).toBe(step.expectedDuration);
@@ -850,21 +874,23 @@ describe("EFCaptions", () => {
850
874
  const captionsTask = captions.customCaptionsDataTask;
851
875
  await captionsTask.taskComplete;
852
876
 
853
- // Test at exact boundary 2.6s - should show " test" (the starting word)
854
- timegroup.currentTimeMs = 2600;
855
- await timegroup.seekTask.taskComplete;
856
- await wordContainer.updateComplete;
857
-
858
- console.log(`At 2600ms: wordText="${wordContainer.wordText}"`);
859
- expect(wordContainer.wordText).toBe(" test");
860
-
861
877
  // Test just before boundary 2.599s - should show " timing"
862
878
  timegroup.currentTimeMs = 2599;
863
879
  await timegroup.seekTask.taskComplete;
880
+ await captions.frameTask.taskComplete;
864
881
  await wordContainer.updateComplete;
865
882
 
866
883
  console.log(`At 2599ms: wordText="${wordContainer.wordText}"`);
867
884
  expect(wordContainer.wordText).toBe(" timing");
885
+
886
+ // Test at exact boundary 2.6s - should show " test" (the starting word)
887
+ timegroup.currentTimeMs = 2600;
888
+ await timegroup.seekTask.taskComplete;
889
+ await captions.frameTask.taskComplete;
890
+ await wordContainer.updateComplete;
891
+
892
+ console.log(`At 2600ms: wordText="${wordContainer.wordText}"`);
893
+ expect(wordContainer.wordText).toBe(" test");
868
894
  });
869
895
 
870
896
  test("handles demo captions data boundary correctly", async () => {
@@ -890,26 +916,28 @@ describe("EFCaptions", () => {
890
916
  const captionsTask = captions.customCaptionsDataTask;
891
917
  await captionsTask.taskComplete;
892
918
 
893
- // Test at exact boundary 2.6s from user's example
894
- timegroup.currentTimeMs = 2600;
919
+ // Test just before boundary
920
+ timegroup.currentTimeMs = 2590;
895
921
  await timegroup.seekTask.taskComplete;
922
+ await captions.frameTask.taskComplete;
896
923
  await wordContainer.updateComplete;
897
924
 
898
925
  console.log(
899
- `Demo case - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
926
+ `Demo case - At 2590ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
900
927
  );
901
- expect(wordContainer.wordText).toBe(" demo!");
902
- expect(wordContainer.hidden).toBe(false);
928
+ expect(wordContainer.wordText).toBe(" captions");
903
929
 
904
- // Test just before boundary
905
- timegroup.currentTimeMs = 2590;
930
+ // Test at exact boundary 2.6s from user's example
931
+ timegroup.currentTimeMs = 2600;
906
932
  await timegroup.seekTask.taskComplete;
933
+ await captions.frameTask.taskComplete;
907
934
  await wordContainer.updateComplete;
908
935
 
909
936
  console.log(
910
- `Demo case - At 2590ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
937
+ `Demo case - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
911
938
  );
912
- expect(wordContainer.wordText).toBe(" captions");
939
+ expect(wordContainer.wordText).toBe(" demo!");
940
+ expect(wordContainer.hidden).toBe(false);
913
941
  });
914
942
 
915
943
  test("standalone captions sync with timegroup (demo structure)", async () => {
@@ -958,7 +986,8 @@ describe("EFCaptions", () => {
958
986
  // Test the problematic timing
959
987
  rootTimegroup.currentTimeMs = 2600;
960
988
  await rootTimegroup.seekTask.taskComplete;
961
- await captions.updateComplete;
989
+ await captions.frameTask.taskComplete;
990
+ await wordContainer.updateComplete;
962
991
 
963
992
  console.log(
964
993
  `After seek: rootTimegroup.currentTimeMs=${rootTimegroup.currentTimeMs}, captions.ownCurrentTimeMs=${captions.ownCurrentTimeMs}`,
@@ -1010,7 +1039,10 @@ describe("EFCaptions", () => {
1010
1039
  // Test during " custom" word (1.2-1.8s) - should have before/after context
1011
1040
  timegroup.currentTimeMs = 1500;
1012
1041
  await timegroup.seekTask.taskComplete;
1013
- await captions.updateComplete;
1042
+ await captions.frameTask.taskComplete;
1043
+ await activeContainer.updateComplete;
1044
+ await beforeContainer.updateComplete;
1045
+ await afterContainer.updateComplete;
1014
1046
 
1015
1047
  console.log("Context test - At 1500ms:");
1016
1048
  console.log(` activeWord: "${activeContainer.wordText}"`);
@@ -1081,7 +1113,7 @@ describe("EFCaptions", () => {
1081
1113
  // Test during " custom" word (1.2-1.8s) - within first segment (0-4s)
1082
1114
  timegroup.currentTimeMs = 1500;
1083
1115
  await timegroup.seekTask.taskComplete;
1084
- await captions.updateComplete;
1116
+ await captions.frameTask.taskComplete;
1085
1117
 
1086
1118
  console.log("Demo debug - At 1500ms:");
1087
1119
  console.log(
@@ -1100,7 +1132,7 @@ describe("EFCaptions", () => {
1100
1132
  // Try different timing in second segment
1101
1133
  timegroup.currentTimeMs = 5000; // During "demonstrates" in second segment
1102
1134
  await timegroup.seekTask.taskComplete;
1103
- await captions.updateComplete;
1135
+ await captions.frameTask.taskComplete;
1104
1136
 
1105
1137
  console.log("Demo debug - At 5000ms (second segment):");
1106
1138
  console.log(` activeWord: "${activeContainer.wordText}"`);
@@ -1156,7 +1188,10 @@ describe("EFCaptions", () => {
1156
1188
  // Test during " test" word (2.5-3.5s)
1157
1189
  timegroup.currentTimeMs = 3000;
1158
1190
  await timegroup.seekTask.taskComplete;
1159
- await captions.updateComplete;
1191
+ await captions.frameTask.taskComplete;
1192
+ await activeContainer.updateComplete;
1193
+ await beforeContainer.updateComplete;
1194
+ await afterContainer.updateComplete;
1160
1195
 
1161
1196
  console.log("Timing sync test - At 3000ms:");
1162
1197
  console.log(
@@ -1276,7 +1311,13 @@ describe("EFCaptions", () => {
1276
1311
  for (const test of testWords) {
1277
1312
  timegroup.currentTimeMs = test.time;
1278
1313
  await timegroup.seekTask.taskComplete;
1279
- await captions.updateComplete;
1314
+ await captions.frameTask.taskComplete;
1315
+ await activeContainer.updateComplete;
1316
+ await beforeContainer.updateComplete;
1317
+ await afterContainer.updateComplete;
1318
+
1319
+ // Wait for browser to paint before measuring
1320
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1280
1321
 
1281
1322
  const measurement = measureElements();
1282
1323
  measurements.push({
@@ -1302,19 +1343,35 @@ describe("EFCaptions", () => {
1302
1343
  }
1303
1344
 
1304
1345
  // Check if total width stays consistent
1305
- const firstTotal = measurements[0]?.measurements.totalContentWidth;
1306
- const allTotalsConsistent = measurements.every(
1307
- (m) => Math.abs(m.measurements.totalContentWidth - firstTotal) < 2, // Allow 1-2px tolerance
1346
+ // const firstTotal = measurements[0]?.measurements.totalContentWidth;
1347
+ // const allTotalsConsistent = measurements.every(
1348
+ // (m) => Math.abs(m.measurements.totalContentWidth - firstTotal) < 2, // Allow 1-2px tolerance
1349
+ // );
1350
+ //
1351
+ // if (!allTotalsConsistent) {
1352
+ // console.log("Width inconsistency detected:");
1353
+ // measurements.forEach((m) => {
1354
+ // console.log(` ${m.word}: ${m.measurements.totalContentWidth}px`);
1355
+ // });
1356
+ // }
1357
+ //
1358
+ // expect(allTotalsConsistent).toBe(true);
1359
+
1360
+ // Check if the overall container width stays consistent (the real measure of stability)
1361
+ const firstContainerWidth = measurements[0]?.measurements.captions.width;
1362
+ const allContainerWidthsConsistent = measurements.every(
1363
+ (m) =>
1364
+ Math.abs(m.measurements.captions.width - firstContainerWidth) < 0.1,
1308
1365
  );
1309
1366
 
1310
- if (!allTotalsConsistent) {
1311
- console.log("Width inconsistency detected:");
1367
+ if (!allContainerWidthsConsistent) {
1368
+ console.log("Container width inconsistency detected:");
1312
1369
  measurements.forEach((m) => {
1313
- console.log(` ${m.word}: ${m.measurements.totalContentWidth}px`);
1370
+ console.log(` ${m.word}: ${m.measurements.captions.width}px`);
1314
1371
  });
1315
1372
  }
1316
1373
 
1317
- expect(allTotalsConsistent).toBe(true);
1374
+ expect(allContainerWidthsConsistent).toBe(true);
1318
1375
  });
1319
1376
 
1320
1377
  test("measures font weight differences causing layout shifts", async () => {
@@ -1366,7 +1423,8 @@ describe("EFCaptions", () => {
1366
1423
  // Test with consistent font weight
1367
1424
  timegroup.currentTimeMs = 300;
1368
1425
  await timegroup.seekTask.taskComplete;
1369
- await captions.updateComplete;
1426
+ await captions.frameTask.taskComplete;
1427
+ await activeContainer.updateComplete;
1370
1428
 
1371
1429
  const welcomeRect = activeContainer.getBoundingClientRect();
1372
1430
  console.log(
@@ -1375,7 +1433,8 @@ describe("EFCaptions", () => {
1375
1433
 
1376
1434
  timegroup.currentTimeMs = 750;
1377
1435
  await timegroup.seekTask.taskComplete;
1378
- await captions.updateComplete;
1436
+ await captions.frameTask.taskComplete;
1437
+ await activeContainer.updateComplete;
1379
1438
 
1380
1439
  const toRect = activeContainer.getBoundingClientRect();
1381
1440
  console.log(`" to" with consistent bold: width=${toRect.width}px`);
@@ -1388,7 +1447,10 @@ describe("EFCaptions", () => {
1388
1447
 
1389
1448
  timegroup.currentTimeMs = 1050;
1390
1449
  await timegroup.seekTask.taskComplete;
1391
- await captions.updateComplete;
1450
+ await captions.frameTask.taskComplete;
1451
+ await activeContainer.updateComplete;
1452
+ await beforeContainer.updateComplete;
1453
+ await afterContainer.updateComplete;
1392
1454
 
1393
1455
  const totalWidth2 =
1394
1456
  beforeContainer.getBoundingClientRect().width +
@@ -1446,7 +1508,7 @@ describe("EFCaptions", () => {
1446
1508
  // Measure widths with natural component behavior
1447
1509
  timegroup.currentTimeMs = 500;
1448
1510
  await timegroup.seekTask.taskComplete;
1449
- await captions.updateComplete;
1511
+ await captions.frameTask.taskComplete;
1450
1512
 
1451
1513
  const naturalRect1 = captions.getBoundingClientRect();
1452
1514
  console.log(
@@ -1455,7 +1517,7 @@ describe("EFCaptions", () => {
1455
1517
 
1456
1518
  timegroup.currentTimeMs = 1500;
1457
1519
  await timegroup.seekTask.taskComplete;
1458
- await captions.updateComplete;
1520
+ await captions.frameTask.taskComplete;
1459
1521
 
1460
1522
  const naturalRect2 = captions.getBoundingClientRect();
1461
1523
  console.log(`Natural captions width at " flow": ${naturalRect2.width}px`);
@@ -1514,7 +1576,7 @@ describe("EFCaptions", () => {
1514
1576
  for (const test of positionTests) {
1515
1577
  timegroup.currentTimeMs = test.time;
1516
1578
  await timegroup.seekTask.taskComplete;
1517
- await captions.updateComplete;
1579
+ await captions.frameTask.taskComplete;
1518
1580
 
1519
1581
  const beforeRect = beforeContainer.getBoundingClientRect();
1520
1582
  const activeRect = activeContainer.getBoundingClientRect();
@@ -1600,7 +1662,8 @@ describe("EFCaptions", () => {
1600
1662
 
1601
1663
  timegroup.currentTimeMs = test.time;
1602
1664
  await timegroup.seekTask.taskComplete;
1603
- await captions.updateComplete;
1665
+ await captions.frameTask.taskComplete;
1666
+ await activeContainer.updateComplete;
1604
1667
 
1605
1668
  // Check that element is visible and has animation properties
1606
1669
  const computedStyle = getComputedStyle(activeContainer);
@@ -1625,7 +1688,7 @@ describe("EFCaptions", () => {
1625
1688
  // Test that element is hidden when outside time range
1626
1689
  timegroup.currentTimeMs = 3500; // After all words
1627
1690
  await timegroup.seekTask.taskComplete;
1628
- await captions.updateComplete;
1691
+ await captions.frameTask.taskComplete;
1629
1692
 
1630
1693
  console.log("\nAfter time range (3500ms):");
1631
1694
  console.log(` Element hidden: ${activeContainer.hidden}`);
@@ -1726,6 +1789,7 @@ describe("EFCaptions", () => {
1726
1789
  // Set timeline to be in second timegroup (7s = 2s into second captions)
1727
1790
  sequence.currentTimeMs = 7000;
1728
1791
  await sequence.seekTask.taskComplete;
1792
+ await captions2.frameTask.taskComplete;
1729
1793
 
1730
1794
  console.log(
1731
1795
  `Timeline at 7000ms - Second captions word: "${word2.wordText}", hidden: ${word2.hidden}`,
@@ -1768,7 +1832,8 @@ describe("EFCaptions", () => {
1768
1832
  // Test at 2.5s - should show " text" word normally
1769
1833
  timegroup.currentTimeMs = 2500;
1770
1834
  await timegroup.seekTask.taskComplete;
1771
- await captions.updateComplete;
1835
+ await captions.frameTask.taskComplete;
1836
+ await wordContainer.updateComplete;
1772
1837
 
1773
1838
  expect(wordContainer.wordText).toBe(" text");
1774
1839
  expect(wordContainer.hidden).toBe(false);
@@ -1776,7 +1841,10 @@ describe("EFCaptions", () => {
1776
1841
  // Test at 4s - after all words finished but segment still active
1777
1842
  timegroup.currentTimeMs = 4000;
1778
1843
  await timegroup.seekTask.taskComplete;
1779
- await captions.updateComplete;
1844
+ await captions.frameTask.taskComplete;
1845
+ await wordContainer.updateComplete;
1846
+ await beforeContainer.updateComplete;
1847
+ await segmentContainer.updateComplete;
1780
1848
 
1781
1849
  console.log(
1782
1850
  ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
@@ -1847,7 +1915,11 @@ describe("EFCaptions", () => {
1847
1915
  // Test at 1s - in segment but before first word starts
1848
1916
  timegroup.currentTimeMs = 1000;
1849
1917
  await timegroup.seekTask.taskComplete;
1850
- await captions.updateComplete;
1918
+ await captions.frameTask.taskComplete;
1919
+ await wordContainer.updateComplete;
1920
+ await beforeContainer.updateComplete;
1921
+ await afterContainer.updateComplete;
1922
+ await segmentContainer.updateComplete;
1851
1923
 
1852
1924
  console.log(
1853
1925
  ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,