@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
@@ -1,5 +1,5 @@
1
1
  import { html, render } from "lit";
2
- import { beforeAll, beforeEach, describe, vi } from "vitest";
2
+ import { beforeAll, beforeEach, describe, expect, vi } from "vitest";
3
3
 
4
4
  import { test as baseTest } from "../../test/useMSW.js";
5
5
  import type { EFVideo } from "./EFVideo.js";
@@ -8,7 +8,6 @@ import "../gui/EFWorkbench.js";
8
8
  import "../gui/EFPreview.js";
9
9
  import "./EFTimegroup.js";
10
10
 
11
- import { TaskStatus } from "@lit/task";
12
11
  import type { EFTimegroup } from "./EFTimegroup.js";
13
12
 
14
13
  // Helper to wait for task completion but ignore abort errors
@@ -80,7 +79,7 @@ const test = baseTest.extend<{
80
79
  const container = document.createElement("div");
81
80
  render(
82
81
  html`
83
- <ef-configuration api-host="http://localhost:63315">
82
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
84
83
  <ef-preview>
85
84
  <ef-timegroup mode="sequence" id="barsNtoneTimegroup"
86
85
  class="relative h-[500px] w-[1000px] overflow-hidden bg-slate-500">
@@ -92,10 +91,9 @@ const test = baseTest.extend<{
92
91
  container,
93
92
  );
94
93
  document.body.appendChild(container);
95
- const configuration = container.querySelector("ef-configuration") as any;
96
- configuration.signingURL = ""; // Disable URL signing for tests
97
94
  const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
98
95
  await timegroup.updateComplete;
96
+ await timegroup.waitForMediaDurations();
99
97
  await use(timegroup);
100
98
  // Cleanup: remove from DOM
101
99
  container.remove();
@@ -104,7 +102,7 @@ const test = baseTest.extend<{
104
102
  const container = document.createElement("div");
105
103
  render(
106
104
  html`
107
- <ef-configuration api-host="http://localhost:63315">
105
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
108
106
  <ef-preview>
109
107
  <ef-timegroup mode="sequence"
110
108
  class="relative h-[500px] w-[1000px] overflow-hidden bg-slate-500">
@@ -124,10 +122,9 @@ const test = baseTest.extend<{
124
122
  container,
125
123
  );
126
124
  document.body.appendChild(container);
127
- const configuration = container.querySelector("ef-configuration") as any;
128
- configuration.signingURL = ""; // Disable URL signing for tests
129
125
  const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
130
126
  await timegroup.updateComplete;
127
+ await timegroup.waitForMediaDurations();
131
128
  await use(timegroup);
132
129
  // Cleanup: remove from DOM
133
130
  container.remove();
@@ -238,7 +235,7 @@ describe("EFVideo", () => {
238
235
 
239
236
  // Should not throw when video asset is missing
240
237
  expect(() => {
241
- video.paintTask.run();
238
+ video.paint(0);
242
239
  }).not.toThrow();
243
240
  });
244
241
  });
@@ -267,7 +264,7 @@ describe("EFVideo", () => {
267
264
  close: vi.fn(),
268
265
  } as unknown as VideoFrame;
269
266
 
270
- // Simulate frame painting (this would normally happen through paintTask)
267
+ // Simulate frame painting (this would normally happen through paint method)
271
268
  const ctx = canvas.getContext("2d");
272
269
  if (ctx && mockFrame.codedWidth && mockFrame.codedHeight) {
273
270
  canvas.width = mockFrame.codedWidth;
@@ -355,20 +352,16 @@ describe("EFVideo", () => {
355
352
 
356
353
  // Simulate the decoder being in use
357
354
  if (decoderLockDescriptor) {
358
- // We can't directly access private fields in tests, but we can test
359
- // that multiple paint calls don't cause issues
360
- const paintPromise1 = video.paintTask.run();
361
- const paintPromise2 = video.paintTask.run();
362
- const paintPromise3 = video.paintTask.run();
363
-
364
- // All should complete without throwing
365
- await expect(
366
- Promise.allSettled([paintPromise1, paintPromise2, paintPromise3]),
367
- ).resolves.toBeDefined();
355
+ // We can test that multiple paint calls don't cause issues
356
+ expect(() => {
357
+ video.paint(0);
358
+ video.paint(0);
359
+ video.paint(0);
360
+ }).not.toThrow();
368
361
  }
369
362
  });
370
363
 
371
- test("paintTask handles missing canvas gracefully", ({ expect }) => {
364
+ test("paint handles missing canvas gracefully", ({ expect }) => {
372
365
  const container = document.createElement("div");
373
366
  render(html`<ef-video></ef-video>`, container);
374
367
  document.body.appendChild(container);
@@ -379,23 +372,19 @@ describe("EFVideo", () => {
379
372
  const canvas = video.canvasElement;
380
373
  canvas?.remove();
381
374
 
382
- // Paint task should handle missing canvas
383
- expect(() => {
384
- video.paintTask.run();
385
- }).not.toThrow();
375
+ // Paint should handle missing canvas
376
+ expect(() => video.paint(0)).not.toThrow();
386
377
  });
387
378
 
388
- test("handles paint task with no video asset", ({ expect }) => {
379
+ test("handles paint with no video asset", ({ expect }) => {
389
380
  const container = document.createElement("div");
390
381
  render(html`<ef-video></ef-video>`, container);
391
382
  document.body.appendChild(container);
392
383
 
393
384
  const video = container.querySelector("ef-video") as EFVideo;
394
385
 
395
- // Paint task should handle missing video asset gracefully
396
- expect(() => {
397
- video.paintTask.run();
398
- }).not.toThrow();
386
+ // Paint should handle missing video asset gracefully
387
+ expect(() => video.paint(0)).not.toThrow();
399
388
  });
400
389
  });
401
390
 
@@ -445,12 +434,12 @@ describe("EFVideo", () => {
445
434
  // Should handle invalid seek times gracefully
446
435
  expect(() => {
447
436
  video.desiredSeekTimeMs = -1000; // Invalid negative time
448
- video.paintTask.run();
437
+ video.paint(-1000);
449
438
  }).not.toThrow();
450
439
 
451
440
  expect(() => {
452
441
  video.desiredSeekTimeMs = Number.POSITIVE_INFINITY;
453
- video.paintTask.run();
442
+ video.paint(Number.POSITIVE_INFINITY);
454
443
  }).not.toThrow();
455
444
  });
456
445
 
@@ -462,14 +451,14 @@ describe("EFVideo", () => {
462
451
  const video = container.querySelector("ef-video") as EFVideo;
463
452
 
464
453
  // Start some operations
465
- video.paintTask.run();
454
+ video.paint(0);
466
455
 
467
456
  // Remove element
468
457
  video.remove();
469
458
 
470
459
  // Should not cause errors
471
460
  expect(() => {
472
- video.paintTask.run();
461
+ video.paint(0);
473
462
  }).not.toThrow();
474
463
  });
475
464
 
@@ -491,7 +480,7 @@ describe("EFVideo", () => {
491
480
 
492
481
  // Should handle context loss gracefully
493
482
  expect(() => {
494
- video.paintTask.run();
483
+ video.paint(0);
495
484
  }).not.toThrow();
496
485
 
497
486
  // Restore original method
@@ -572,6 +561,54 @@ describe("EFVideo", () => {
572
561
  // The video should have loaded successfully within the timegroup
573
562
  expect(video.intrinsicDurationMs).toBeGreaterThan(0);
574
563
  });
564
+
565
+ test("works as standalone root temporal in ef-preview", async ({
566
+ expect,
567
+ }) => {
568
+ const container = document.createElement("div");
569
+ render(
570
+ html`
571
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
572
+ <ef-preview id="test-preview">
573
+ <ef-video src="bars-n-tone.mp4" mode="asset" id="standalone-video"></ef-video>
574
+ </ef-preview>
575
+ </ef-configuration>
576
+ `,
577
+ container,
578
+ );
579
+ document.body.appendChild(container);
580
+
581
+ const preview = container.querySelector("ef-preview") as any;
582
+ const video = container.querySelector("ef-video") as EFVideo;
583
+
584
+ await preview.updateComplete;
585
+ await video.updateComplete;
586
+
587
+ // Wait for media to be ready
588
+ await video.mediaEngineTask.taskComplete;
589
+
590
+ // Video should have loaded successfully
591
+ expect(video.intrinsicDurationMs).toBeGreaterThan(0);
592
+
593
+ // Preview should recognize the video as its root temporal
594
+ expect(preview.targetTemporal).toBe(video);
595
+
596
+ // Video should have a playback controller as a root element
597
+ expect(video.playbackController).toBeDefined();
598
+
599
+ // Preview should be able to control playback
600
+ expect(preview.playing).toBe(false);
601
+
602
+ // Seek the video through the preview
603
+ preview.currentTimeMs = 1000;
604
+ await video.frameTask.taskComplete;
605
+
606
+ // Video should have seeked
607
+ expect(video.ownCurrentTimeMs).toBeCloseTo(1000, 0);
608
+
609
+ // Cleanup
610
+ container.remove();
611
+ });
575
612
  });
576
613
 
577
614
  describe.skip("loading indicator", () => {
@@ -837,17 +874,13 @@ describe("EFVideo", () => {
837
874
  barsNtoneTimegroup,
838
875
  expect,
839
876
  }) => {
840
- barsNtoneTimegroup.currentTimeMs = 8000;
841
- await barsNtoneTimegroup.seekTask.taskComplete;
877
+ await barsNtoneTimegroup.seek(8000);
842
878
  expect(barsNtone.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
843
879
  8.066,
844
880
  );
845
881
 
846
- expect(barsNtoneTimegroup.seekTask.status).toBe(TaskStatus.COMPLETE);
847
882
  // Then seek backward
848
- barsNtoneTimegroup.currentTimeMs = 2000;
849
- expect(barsNtoneTimegroup.seekTask.status).toBe(TaskStatus.PENDING);
850
- await barsNtoneTimegroup.seekTask.taskComplete;
883
+ await barsNtoneTimegroup.seek(2000);
851
884
  expect(barsNtone.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
852
885
  2.066,
853
886
  );
@@ -1025,9 +1058,7 @@ describe("EFVideo", () => {
1025
1058
  barsNtoneTimegroup,
1026
1059
  expect,
1027
1060
  }) => {
1028
- // await barsNtoneTimegroup.frameTask.taskComplete;
1029
- barsNtoneTimegroup.currentTimeMs = 7975;
1030
- await barsNtoneTimegroup.seekTask.taskComplete;
1061
+ await barsNtoneTimegroup.seek(7975);
1031
1062
 
1032
1063
  expect(barsNtone.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1033
1064
  8.033,
@@ -1039,9 +1070,8 @@ describe("EFVideo", () => {
1039
1070
  barsNtoneTimegroup,
1040
1071
  expect,
1041
1072
  }) => {
1042
- await barsNtoneTimegroup.frameTask.taskComplete;
1043
- barsNtoneTimegroup.currentTimeMs = 8041.667;
1044
1073
  await barsNtoneTimegroup.seekTask.taskComplete;
1074
+ await barsNtoneTimegroup.seek(8041.667);
1045
1075
  expect(barsNtone.unifiedVideoSeekTask.value?.timestamp).toBe(8.1);
1046
1076
  });
1047
1077
 
@@ -1085,26 +1115,22 @@ describe("EFVideo", () => {
1085
1115
  });
1086
1116
 
1087
1117
  test("seeks to 1000ms", async ({ timegroup, headMoov480p, expect }) => {
1088
- timegroup.currentTimeMs = 1000;
1089
- await timegroup.seekTask.taskComplete;
1118
+ await timegroup.seek(1000);
1090
1119
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(1);
1091
1120
  });
1092
1121
 
1093
1122
  test("seeks to 3000ms", async ({ timegroup, headMoov480p, expect }) => {
1094
- timegroup.currentTimeMs = 3000;
1095
- await timegroup.seekTask.taskComplete;
1123
+ await timegroup.seek(3000);
1096
1124
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(3);
1097
1125
  });
1098
1126
 
1099
1127
  test("seeks to 5000ms", async ({ timegroup, headMoov480p, expect }) => {
1100
- timegroup.currentTimeMs = 5000;
1101
- await timegroup.seekTask.taskComplete;
1128
+ await timegroup.seek(5000);
1102
1129
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(5);
1103
1130
  });
1104
1131
 
1105
1132
  test("seeks to 7500ms", async ({ timegroup, headMoov480p, expect }) => {
1106
- timegroup.currentTimeMs = 7500;
1107
- await timegroup.seekTask.taskComplete;
1133
+ await timegroup.seek(7500);
1108
1134
 
1109
1135
  // JIT transcoding returns actual video frame timestamps, not idealized segment boundaries
1110
1136
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
@@ -1114,8 +1140,7 @@ describe("EFVideo", () => {
1114
1140
  });
1115
1141
 
1116
1142
  test("seeks to 8500ms", async ({ timegroup, headMoov480p, expect }) => {
1117
- timegroup.currentTimeMs = 8500;
1118
- await timegroup.seekTask.taskComplete;
1143
+ await timegroup.seek(8500);
1119
1144
 
1120
1145
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1121
1146
  8.5,
@@ -1128,8 +1153,7 @@ describe("EFVideo", () => {
1128
1153
  headMoov480p,
1129
1154
  expect,
1130
1155
  }) => {
1131
- timegroup.currentTimeMs = 9000;
1132
- await timegroup.seekTask.taskComplete;
1156
+ await timegroup.seek(9000);
1133
1157
 
1134
1158
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(9);
1135
1159
  });
@@ -1139,12 +1163,10 @@ describe("EFVideo", () => {
1139
1163
  headMoov480p,
1140
1164
  expect,
1141
1165
  }) => {
1142
- timegroup.currentTime = 7;
1143
- await expect(timegroup.seekTask.taskComplete).resolves.toBe(7);
1166
+ await timegroup.seek(7000);
1144
1167
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(7);
1145
1168
 
1146
- timegroup.currentTime = 2;
1147
- await expect(timegroup.seekTask.taskComplete).resolves.toBe(2);
1169
+ await timegroup.seek(2000);
1148
1170
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(2);
1149
1171
  });
1150
1172
 
@@ -1157,8 +1179,7 @@ describe("EFVideo", () => {
1157
1179
  const expectedTimestamps = [1, 3, 5, 2, 6, 0];
1158
1180
 
1159
1181
  for (let i = 0; i < seekPoints.length; i++) {
1160
- timegroup.currentTimeMs = seekPoints[i]!;
1161
- await timegroup.seekTask.taskComplete;
1182
+ await timegroup.seek(seekPoints[i]!);
1162
1183
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1163
1184
  expectedTimestamps[i]!,
1164
1185
  1,
@@ -1175,8 +1196,7 @@ describe("EFVideo", () => {
1175
1196
  const expectedTimestamps = [1.234567, 3.456789, 5.678901];
1176
1197
 
1177
1198
  for (let i = 0; i < fractionalTimes.length; i++) {
1178
- timegroup.currentTimeMs = fractionalTimes[i]!;
1179
- await timegroup.seekTask.taskComplete;
1199
+ await timegroup.seek(fractionalTimes[i]!);
1180
1200
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1181
1201
  expectedTimestamps[i]!,
1182
1202
  1,
@@ -1189,22 +1209,19 @@ describe("EFVideo", () => {
1189
1209
  headMoov480p,
1190
1210
  expect,
1191
1211
  }) => {
1192
- timegroup.currentTimeMs = 0;
1193
- await timegroup.seekTask.taskComplete;
1212
+ await timegroup.seek(0);
1194
1213
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1195
1214
  0,
1196
1215
  1,
1197
1216
  );
1198
1217
 
1199
- timegroup.currentTimeMs = 1000;
1200
- await timegroup.seekTask.taskComplete;
1218
+ await timegroup.seek(1000);
1201
1219
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1202
1220
  1,
1203
1221
  1,
1204
1222
  );
1205
1223
 
1206
- timegroup.currentTimeMs = 4000;
1207
- await timegroup.seekTask.taskComplete;
1224
+ await timegroup.seek(4000);
1208
1225
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBeCloseTo(
1209
1226
  4,
1210
1227
  1,
@@ -1216,8 +1233,7 @@ describe("EFVideo", () => {
1216
1233
  headMoov480p,
1217
1234
  expect,
1218
1235
  }) => {
1219
- timegroup.currentTimeMs = 1000;
1220
- await timegroup.seekTask.taskComplete;
1236
+ await timegroup.seek(1000);
1221
1237
  expect(headMoov480p.unifiedVideoSeekTask.value?.timestamp).toBe(1);
1222
1238
 
1223
1239
  // // Track frameTask executions using a spy on the run method
@@ -1391,4 +1407,76 @@ describe("EFVideo", () => {
1391
1407
  expect(true).toBe(true);
1392
1408
  });
1393
1409
  });
1410
+
1411
+ describe("loop attribute", () => {
1412
+ test(
1413
+ "standalone ef-video respects loop attribute",
1414
+ { timeout: 1000 },
1415
+ async () => {
1416
+ const container = document.createElement("div");
1417
+ render(
1418
+ html`
1419
+ <ef-video
1420
+ loop
1421
+ id="loop-video"
1422
+ src="bars-n-tone.mp4"
1423
+ sourceout="2s"
1424
+ ></ef-video>
1425
+ `,
1426
+ container,
1427
+ );
1428
+ document.body.appendChild(container);
1429
+
1430
+ const video = container.querySelector("#loop-video") as EFVideo;
1431
+ await video.updateComplete;
1432
+
1433
+ expect(video.loop).toBe(true);
1434
+ expect(video.playbackController).toBeDefined();
1435
+ expect(video.playbackController?.loop).toBe(true);
1436
+
1437
+ container.remove();
1438
+ },
1439
+ );
1440
+
1441
+ test(
1442
+ "loop property is reactive after initialization",
1443
+ { timeout: 1000 },
1444
+ async () => {
1445
+ const container = document.createElement("div");
1446
+ render(
1447
+ html`
1448
+ <ef-video
1449
+ id="reactive-loop-video"
1450
+ src="bars-n-tone.mp4"
1451
+ sourceout="2s"
1452
+ ></ef-video>
1453
+ `,
1454
+ container,
1455
+ );
1456
+ document.body.appendChild(container);
1457
+
1458
+ const video = container.querySelector(
1459
+ "#reactive-loop-video",
1460
+ ) as EFVideo;
1461
+ await video.updateComplete;
1462
+
1463
+ expect(video.loop).toBe(false);
1464
+ expect(video.playbackController?.loop).toBe(false);
1465
+
1466
+ video.loop = true;
1467
+ await video.updateComplete;
1468
+
1469
+ expect(video.loop).toBe(true);
1470
+ expect(video.playbackController?.loop).toBe(true);
1471
+
1472
+ video.loop = false;
1473
+ await video.updateComplete;
1474
+
1475
+ expect(video.loop).toBe(false);
1476
+ expect(video.playbackController?.loop).toBe(false);
1477
+
1478
+ container.remove();
1479
+ },
1480
+ );
1481
+ });
1394
1482
  });