@editframe/elements 0.26.3-beta.0 → 0.30.0-beta.13

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 (191) hide show
  1. package/dist/elements/EFSourceMixin.js +1 -1
  2. package/dist/elements/EFSourceMixin.js.map +1 -1
  3. package/dist/elements/EFSurface.d.ts +4 -4
  4. package/dist/elements/EFText.d.ts +52 -0
  5. package/dist/elements/EFText.js +319 -0
  6. package/dist/elements/EFText.js.map +1 -0
  7. package/dist/elements/EFTextSegment.d.ts +30 -0
  8. package/dist/elements/EFTextSegment.js +94 -0
  9. package/dist/elements/EFTextSegment.js.map +1 -0
  10. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  11. package/dist/elements/EFWaveform.d.ts +4 -4
  12. package/dist/elements/FetchMixin.js +22 -7
  13. package/dist/elements/FetchMixin.js.map +1 -1
  14. package/dist/elements/easingUtils.js +62 -0
  15. package/dist/elements/easingUtils.js.map +1 -0
  16. package/dist/elements/updateAnimations.js +57 -10
  17. package/dist/elements/updateAnimations.js.map +1 -1
  18. package/dist/gui/ContextMixin.js +11 -2
  19. package/dist/gui/ContextMixin.js.map +1 -1
  20. package/dist/gui/EFConfiguration.d.ts +4 -4
  21. package/dist/gui/EFControls.d.ts +2 -2
  22. package/dist/gui/EFDial.d.ts +4 -4
  23. package/dist/gui/EFDial.js +4 -2
  24. package/dist/gui/EFDial.js.map +1 -1
  25. package/dist/gui/EFFilmstrip.d.ts +32 -6
  26. package/dist/gui/EFFilmstrip.js +314 -50
  27. package/dist/gui/EFFilmstrip.js.map +1 -1
  28. package/dist/gui/EFFitScale.js +39 -15
  29. package/dist/gui/EFFitScale.js.map +1 -1
  30. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  31. package/dist/gui/EFPause.d.ts +4 -4
  32. package/dist/gui/EFPlay.d.ts +4 -4
  33. package/dist/gui/EFPreview.d.ts +4 -4
  34. package/dist/gui/EFPreview.js +2 -2
  35. package/dist/gui/EFPreview.js.map +1 -1
  36. package/dist/gui/EFResizableBox.d.ts +4 -4
  37. package/dist/gui/EFResizableBox.js +6 -3
  38. package/dist/gui/EFResizableBox.js.map +1 -1
  39. package/dist/gui/EFScrubber.d.ts +8 -5
  40. package/dist/gui/EFScrubber.js +64 -12
  41. package/dist/gui/EFScrubber.js.map +1 -1
  42. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  43. package/dist/gui/EFToggleLoop.d.ts +4 -4
  44. package/dist/gui/EFTogglePlay.d.ts +4 -4
  45. package/dist/gui/EFWorkbench.d.ts +4 -4
  46. package/dist/gui/EFWorkbench.js +16 -3
  47. package/dist/gui/EFWorkbench.js.map +1 -1
  48. package/dist/gui/TWMixin.js +1 -1
  49. package/dist/gui/TWMixin.js.map +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/style.css +7 -120
  54. package/package.json +3 -3
  55. package/scripts/build-css.js +5 -9
  56. package/test/constants.ts +8 -0
  57. package/test/recordReplayProxyPlugin.js +76 -10
  58. package/test/setup.ts +32 -0
  59. package/test/useMSW.ts +3 -0
  60. package/test/useTranscodeMSW.ts +191 -0
  61. package/tsdown.config.ts +7 -5
  62. package/types.json +1 -1
  63. package/src/elements/ContextProxiesController.ts +0 -124
  64. package/src/elements/CrossUpdateController.ts +0 -22
  65. package/src/elements/EFAudio.browsertest.ts +0 -706
  66. package/src/elements/EFAudio.ts +0 -56
  67. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  68. package/src/elements/EFCaptions.ts +0 -823
  69. package/src/elements/EFImage.browsertest.ts +0 -120
  70. package/src/elements/EFImage.ts +0 -113
  71. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  72. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  73. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  74. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  75. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  76. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  77. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  78. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  79. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  80. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  81. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  82. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  83. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  84. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  85. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  86. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  87. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  88. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  89. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  90. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  91. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  92. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  93. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  94. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  95. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  96. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  97. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  98. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  99. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  100. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  101. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  102. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  103. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  104. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  105. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  106. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  107. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  108. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  109. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  110. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  111. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  112. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  113. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  114. package/src/elements/EFMedia.browsertest.ts +0 -872
  115. package/src/elements/EFMedia.ts +0 -341
  116. package/src/elements/EFSourceMixin.ts +0 -60
  117. package/src/elements/EFSurface.browsertest.ts +0 -151
  118. package/src/elements/EFSurface.ts +0 -142
  119. package/src/elements/EFTemporal.browsertest.ts +0 -215
  120. package/src/elements/EFTemporal.ts +0 -800
  121. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  122. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  123. package/src/elements/EFThumbnailStrip.ts +0 -906
  124. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  125. package/src/elements/EFTimegroup.ts +0 -882
  126. package/src/elements/EFVideo.browsertest.ts +0 -1482
  127. package/src/elements/EFVideo.ts +0 -564
  128. package/src/elements/EFWaveform.ts +0 -547
  129. package/src/elements/FetchContext.browsertest.ts +0 -401
  130. package/src/elements/FetchMixin.ts +0 -38
  131. package/src/elements/SampleBuffer.ts +0 -94
  132. package/src/elements/TargetController.browsertest.ts +0 -230
  133. package/src/elements/TargetController.ts +0 -224
  134. package/src/elements/TimegroupController.ts +0 -26
  135. package/src/elements/durationConverter.ts +0 -35
  136. package/src/elements/parseTimeToMs.ts +0 -9
  137. package/src/elements/printTaskStatus.ts +0 -16
  138. package/src/elements/renderTemporalAudio.ts +0 -108
  139. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  140. package/src/elements/updateAnimations.ts +0 -217
  141. package/src/elements/util.ts +0 -24
  142. package/src/gui/ContextMixin.browsertest.ts +0 -860
  143. package/src/gui/ContextMixin.ts +0 -562
  144. package/src/gui/Controllable.browsertest.ts +0 -258
  145. package/src/gui/Controllable.ts +0 -41
  146. package/src/gui/EFConfiguration.ts +0 -40
  147. package/src/gui/EFControls.browsertest.ts +0 -389
  148. package/src/gui/EFControls.ts +0 -195
  149. package/src/gui/EFDial.browsertest.ts +0 -84
  150. package/src/gui/EFDial.ts +0 -172
  151. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  152. package/src/gui/EFFilmstrip.ts +0 -1349
  153. package/src/gui/EFFitScale.ts +0 -152
  154. package/src/gui/EFFocusOverlay.ts +0 -79
  155. package/src/gui/EFPause.browsertest.ts +0 -202
  156. package/src/gui/EFPause.ts +0 -73
  157. package/src/gui/EFPlay.browsertest.ts +0 -202
  158. package/src/gui/EFPlay.ts +0 -73
  159. package/src/gui/EFPreview.ts +0 -74
  160. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  161. package/src/gui/EFResizableBox.ts +0 -898
  162. package/src/gui/EFScrubber.ts +0 -151
  163. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  164. package/src/gui/EFTimeDisplay.ts +0 -55
  165. package/src/gui/EFToggleLoop.ts +0 -35
  166. package/src/gui/EFTogglePlay.ts +0 -70
  167. package/src/gui/EFWorkbench.ts +0 -115
  168. package/src/gui/PlaybackController.ts +0 -527
  169. package/src/gui/TWMixin.css +0 -6
  170. package/src/gui/TWMixin.ts +0 -61
  171. package/src/gui/TargetOrContextMixin.ts +0 -185
  172. package/src/gui/currentTimeContext.ts +0 -5
  173. package/src/gui/durationContext.ts +0 -3
  174. package/src/gui/efContext.ts +0 -6
  175. package/src/gui/fetchContext.ts +0 -5
  176. package/src/gui/focusContext.ts +0 -7
  177. package/src/gui/focusedElementContext.ts +0 -5
  178. package/src/gui/playingContext.ts +0 -5
  179. package/src/otel/BridgeSpanExporter.ts +0 -150
  180. package/src/otel/setupBrowserTracing.ts +0 -73
  181. package/src/otel/tracingHelpers.ts +0 -251
  182. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  183. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  184. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  185. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  186. package/src/transcoding/types/index.ts +0 -312
  187. package/src/transcoding/utils/MediaUtils.ts +0 -63
  188. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  189. package/src/transcoding/utils/constants.ts +0 -36
  190. package/src/utils/LRUCache.test.ts +0 -274
  191. package/src/utils/LRUCache.ts +0 -696
@@ -1,714 +0,0 @@
1
- import { beforeAll, beforeEach, describe, vi } from "vitest";
2
- import { test as baseTest } from "../../test/useMSW.js";
3
- import type { EFTimegroup } from "./EFTimegroup.js";
4
- import type { EFVideo } from "./EFVideo.js";
5
- import "./EFVideo.js";
6
- import "./EFTimegroup.js";
7
- import "./EFThumbnailStrip.js"; // Import to register the custom element
8
- import "../gui/EFWorkbench.js";
9
- import "../gui/EFPreview.js";
10
- import type { EFConfiguration } from "../gui/EFConfiguration.js";
11
- import { AssetMediaEngine } from "./EFMedia/AssetMediaEngine.js";
12
- import { JitMediaEngine } from "./EFMedia/JitMediaEngine.js";
13
-
14
- beforeAll(async () => {
15
- console.clear();
16
- await fetch("/@ef-clear-cache", {
17
- method: "DELETE",
18
- });
19
- });
20
-
21
- beforeEach(() => {
22
- localStorage.clear();
23
- });
24
-
25
- const test = baseTest.extend<{
26
- configuration: EFConfiguration;
27
- timegroup: EFTimegroup;
28
- jitVideo: EFVideo;
29
- assetVideo: EFVideo;
30
- }>({
31
- configuration: async ({ expect }, use) => {
32
- const configuration = document.createElement("ef-configuration");
33
- configuration.innerHTML = `<h1 style="font: 10px monospace">${expect.getState().currentTestName}</h1>`;
34
- // Use integrated proxy server (same host/port as test runner)
35
- const apiHost = `${window.location.protocol}//${window.location.host}`;
36
- configuration.setAttribute("api-host", apiHost);
37
- configuration.apiHost = apiHost;
38
- configuration.signingURL = "";
39
- document.body.appendChild(configuration);
40
- await use(configuration);
41
- },
42
- timegroup: async ({}, use) => {
43
- const timegroup = document.createElement("ef-timegroup");
44
- timegroup.setAttribute("mode", "contain");
45
- await use(timegroup);
46
- },
47
- jitVideo: async ({ configuration, timegroup }, use) => {
48
- const video = document.createElement("ef-video");
49
- video.src = "http://web:3000/head-moov-480p.mp4";
50
- timegroup.appendChild(video);
51
- configuration.appendChild(timegroup);
52
- await video.mediaEngineTask.run();
53
- await use(video);
54
- },
55
- assetVideo: async ({ configuration, timegroup }, use) => {
56
- const video = document.createElement("ef-video");
57
- video.src = "bars-n-tone.mp4";
58
- timegroup.appendChild(video);
59
- configuration.appendChild(timegroup);
60
- await video.mediaEngineTask.run();
61
- await use(video);
62
- },
63
- });
64
-
65
- describe("MediaEngine Thumbnail Extraction", () => {
66
- describe("JitMediaEngine", () => {
67
- test("initializes with JitMediaEngine", async ({ jitVideo, expect }) => {
68
- const mediaEngine = jitVideo.mediaEngineTask.value;
69
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
70
- expect(jitVideo.intrinsicDurationMs).toBe(10_000);
71
- });
72
-
73
- test("extracts single thumbnail at timestamp", async ({
74
- jitVideo,
75
- expect,
76
- }) => {
77
- const mediaEngine = jitVideo.mediaEngineTask.value!;
78
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
79
-
80
- const timestamps = [2000]; // 2 seconds
81
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
82
-
83
- expect(thumbnails).toHaveLength(1);
84
- expect(thumbnails[0]).toBeTruthy();
85
- expect(thumbnails[0]?.timestamp).toBe(2000);
86
- expect(thumbnails[0]?.thumbnail).toBeDefined();
87
-
88
- // Verify it's a valid canvas
89
- const canvas = thumbnails[0]!.thumbnail;
90
- expect(
91
- canvas instanceof HTMLCanvasElement ||
92
- canvas instanceof OffscreenCanvas,
93
- ).toBe(true);
94
- expect(canvas.width).toBeGreaterThan(0);
95
- expect(canvas.height).toBeGreaterThan(0);
96
- });
97
-
98
- test("extracts multiple thumbnails in batch", async ({
99
- jitVideo,
100
- expect,
101
- }) => {
102
- const mediaEngine = jitVideo.mediaEngineTask.value!;
103
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
104
-
105
- const timestamps = [1000, 3000, 5000, 7000]; // Multiple timestamps
106
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
107
-
108
- expect(thumbnails).toHaveLength(4);
109
-
110
- for (let i = 0; i < timestamps.length; i++) {
111
- const thumbnail = thumbnails[i];
112
- expect(thumbnail).toBeTruthy();
113
- expect(thumbnail?.timestamp).toBe(timestamps[i]);
114
-
115
- const canvas = thumbnail!.thumbnail;
116
- expect(
117
- canvas instanceof HTMLCanvasElement ||
118
- canvas instanceof OffscreenCanvas,
119
- ).toBe(true);
120
- expect(canvas.width).toBeGreaterThan(0);
121
- expect(canvas.height).toBeGreaterThan(0);
122
- }
123
- });
124
-
125
- test("handles timestamps in same segment efficiently", async ({
126
- jitVideo,
127
- expect,
128
- }) => {
129
- const mediaEngine = jitVideo.mediaEngineTask.value as JitMediaEngine;
130
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
131
-
132
- // Get segment duration to ensure timestamps are in same segment
133
- const videoRendition =
134
- mediaEngine.getScrubVideoRendition() || mediaEngine.getVideoRendition();
135
- expect(videoRendition).toBeDefined();
136
- const segmentDurationMs = videoRendition!.segmentDurationMs || 2000;
137
-
138
- // Pick timestamps within the first segment - avoid edge cases near boundaries
139
- const timestamps = [
140
- 100,
141
- 500,
142
- 1000,
143
- Math.min(1500, segmentDurationMs - 200),
144
- ];
145
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
146
-
147
- expect(thumbnails).toHaveLength(4);
148
-
149
- // Most should succeed since they're in the same segment
150
- const successfulThumbnails = thumbnails.filter((t) => t !== null);
151
- expect(successfulThumbnails.length).toBeGreaterThan(2); // At least 3 out of 4
152
-
153
- for (const thumbnail of successfulThumbnails) {
154
- expect(thumbnail!.thumbnail).toBeDefined();
155
- }
156
- });
157
-
158
- test("handles timestamps across different segments", async ({
159
- jitVideo,
160
- expect,
161
- }) => {
162
- const mediaEngine = jitVideo.mediaEngineTask.value as JitMediaEngine;
163
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
164
-
165
- // Pick timestamps that span multiple segments
166
- const timestamps = [500, 2500, 4500, 6500, 8500]; // Across different 2s segments
167
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
168
-
169
- expect(thumbnails).toHaveLength(5);
170
-
171
- // All should succeed
172
- for (let i = 0; i < timestamps.length; i++) {
173
- const thumbnail = thumbnails[i];
174
- expect(thumbnail).toBeTruthy();
175
- expect(thumbnail?.timestamp).toBe(timestamps[i]);
176
- }
177
- });
178
-
179
- test("handles invalid timestamps gracefully", async ({
180
- jitVideo,
181
- expect,
182
- }) => {
183
- const mediaEngine = jitVideo.mediaEngineTask.value!;
184
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
185
-
186
- const timestamps = [-1000, 15000]; // Before start and after end
187
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
188
-
189
- expect(thumbnails).toHaveLength(2);
190
-
191
- // Invalid timestamps should return null
192
- expect(thumbnails[0]).toBeNull();
193
- expect(thumbnails[1]).toBeNull();
194
- });
195
-
196
- test("handles mix of valid and invalid timestamps", async ({
197
- jitVideo,
198
- expect,
199
- }) => {
200
- const mediaEngine = jitVideo.mediaEngineTask.value!;
201
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
202
-
203
- const timestamps = [-1000, 2000, 15000, 5000]; // Invalid, valid, invalid, valid
204
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
205
-
206
- expect(thumbnails).toHaveLength(4);
207
-
208
- expect(thumbnails[0]).toBeNull(); // Invalid
209
- expect(thumbnails[1]).toBeTruthy(); // Valid
210
- expect(thumbnails[1]?.timestamp).toBe(2000);
211
- expect(thumbnails[2]).toBeNull(); // Invalid
212
- expect(thumbnails[3]).toBeTruthy(); // Valid
213
- expect(thumbnails[3]?.timestamp).toBe(5000);
214
- });
215
-
216
- test("handles empty timestamp array", async ({ jitVideo, expect }) => {
217
- const mediaEngine = jitVideo.mediaEngineTask.value!;
218
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
219
-
220
- const timestamps: number[] = [];
221
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
222
-
223
- expect(thumbnails).toHaveLength(0);
224
- });
225
-
226
- test("uses scrub rendition when available", async ({
227
- jitVideo,
228
- expect,
229
- }) => {
230
- const mediaEngine = jitVideo.mediaEngineTask.value!;
231
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
232
-
233
- // Check if scrub rendition exists
234
- const scrubRendition = mediaEngine.getScrubVideoRendition();
235
- const mainRendition = mediaEngine.getVideoRendition();
236
-
237
- expect(scrubRendition).toBeDefined();
238
- expect(mainRendition).toBeDefined();
239
-
240
- // Extract thumbnail to ensure it works with scrub rendition
241
- const timestamps = [3000];
242
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
243
-
244
- expect(thumbnails).toHaveLength(1);
245
- expect(thumbnails[0]).toBeTruthy();
246
- });
247
- });
248
-
249
- describe("AssetMediaEngine", () => {
250
- test("initializes with AssetMediaEngine", async ({
251
- assetVideo,
252
- expect,
253
- }) => {
254
- const mediaEngine = assetVideo.mediaEngineTask.value;
255
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
256
- expect(assetVideo.intrinsicDurationMs).toBeGreaterThan(0);
257
- });
258
-
259
- test("attempts thumbnail extraction (currently has implementation issues)", async ({
260
- assetVideo,
261
- expect,
262
- }) => {
263
- const mediaEngine = assetVideo.mediaEngineTask.value!;
264
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
265
-
266
- const timestamps = [2000]; // 2 seconds
267
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
268
-
269
- expect(thumbnails).toHaveLength(1);
270
-
271
- // NOTE: AssetMediaEngine thumbnail extraction currently has issues
272
- // This test documents the current behavior for the refactor
273
- if (thumbnails[0]) {
274
- expect(thumbnails[0].timestamp).toBe(2000);
275
- expect(thumbnails[0].thumbnail).toBeDefined();
276
-
277
- const canvas = thumbnails[0].thumbnail;
278
- expect(
279
- canvas instanceof HTMLCanvasElement ||
280
- canvas instanceof OffscreenCanvas,
281
- ).toBe(true);
282
- expect(canvas.width).toBeGreaterThan(0);
283
- expect(canvas.height).toBeGreaterThan(0);
284
- }
285
- // If it returns null, that's also acceptable given current implementation issues
286
- });
287
-
288
- test("attempts batch thumbnail extraction (documents current behavior)", async ({
289
- assetVideo,
290
- expect,
291
- }) => {
292
- const mediaEngine = assetVideo.mediaEngineTask.value!;
293
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
294
-
295
- const timestamps = [1000, 3000, 5000, 7000]; // Multiple timestamps
296
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
297
-
298
- expect(thumbnails).toHaveLength(4);
299
-
300
- // Document current behavior - some may be null due to implementation issues
301
- let successCount = 0;
302
- for (let i = 0; i < timestamps.length; i++) {
303
- const thumbnail = thumbnails[i];
304
- if (thumbnail) {
305
- successCount++;
306
- expect(thumbnail.timestamp).toBe(timestamps[i]);
307
-
308
- const canvas = thumbnail.thumbnail;
309
- expect(
310
- canvas instanceof HTMLCanvasElement ||
311
- canvas instanceof OffscreenCanvas,
312
- ).toBe(true);
313
- expect(canvas.width).toBeGreaterThan(0);
314
- expect(canvas.height).toBeGreaterThan(0);
315
- }
316
- }
317
-
318
- // Track success rate for refactor planning
319
- console.log(
320
- `AssetMediaEngine batch extraction: ${successCount}/${timestamps.length} successful`,
321
- );
322
- });
323
-
324
- test("documents that AssetMediaEngine is not yet supported", async ({
325
- assetVideo,
326
- expect,
327
- }) => {
328
- const mediaEngine = assetVideo.mediaEngineTask.value as AssetMediaEngine;
329
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
330
-
331
- // AssetMediaEngine now properly returns nulls for all requests
332
- const timestamps = [500, 2500, 4500, 6500];
333
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
334
-
335
- expect(thumbnails).toHaveLength(4);
336
-
337
- // All should be null since AssetMediaEngine is not yet supported
338
- const successfulThumbnails = thumbnails.filter((t) => t !== null);
339
- expect(successfulThumbnails.length).toBe(0); // Consistent behavior now
340
- });
341
-
342
- test("handles invalid timestamps (reveals current boundary issues)", async ({
343
- assetVideo,
344
- expect,
345
- }) => {
346
- const mediaEngine = assetVideo.mediaEngineTask.value!;
347
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
348
-
349
- const timestamps = [-1000, 50000]; // Before start and well after end
350
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
351
-
352
- expect(thumbnails).toHaveLength(2);
353
-
354
- // Document current behavior - there seem to be boundary checking issues
355
- // that should be fixed in the refactor
356
- console.log(
357
- `Invalid timestamps result: ${thumbnails[0] ? "non-null" : "null"}, ${thumbnails[1] ? "non-null" : "null"}`,
358
- );
359
-
360
- // At minimum, negative timestamps should be null
361
- expect(thumbnails[0]).toBeNull();
362
-
363
- // The second one might unexpectedly succeed due to current implementation issues
364
- // This documents the current behavior for the refactor
365
- if (thumbnails[1]) {
366
- console.log(
367
- "WARNING: Timestamp 50000ms unexpectedly returned a thumbnail - boundary checking issue",
368
- );
369
- }
370
- });
371
-
372
- test("no scrub rendition fallback to main video (documents current behavior)", async ({
373
- assetVideo,
374
- expect,
375
- }) => {
376
- const mediaEngine = assetVideo.mediaEngineTask.value as AssetMediaEngine;
377
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
378
-
379
- // AssetMediaEngine doesn't have scrub rendition
380
- const scrubRendition = mediaEngine.getScrubVideoRendition();
381
- expect(scrubRendition).toBeUndefined();
382
-
383
- // Attempt to extract thumbnails using main video rendition
384
- const timestamps = [2000];
385
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
386
-
387
- expect(thumbnails).toHaveLength(1);
388
-
389
- // Document current behavior - may return null due to implementation issues
390
- if (thumbnails[0]) {
391
- expect(thumbnails[0].timestamp).toBe(2000);
392
- expect(thumbnails[0].thumbnail).toBeDefined();
393
- } else {
394
- console.log(
395
- "AssetMediaEngine fallback to main video rendition currently returns null",
396
- );
397
- }
398
- });
399
-
400
- test("documents segment boundary behavior for refactor", async ({
401
- assetVideo,
402
- expect,
403
- }) => {
404
- const mediaEngine = assetVideo.mediaEngineTask.value as AssetMediaEngine;
405
- expect(mediaEngine).toBeInstanceOf(AssetMediaEngine);
406
-
407
- // Test around known segment boundaries from bars-n-tone.mp4
408
- // These are approximate - the actual boundaries depend on the asset
409
- const timestamps = [2066, 4033, 6066, 8033];
410
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
411
-
412
- expect(thumbnails).toHaveLength(4);
413
-
414
- // Document current success rate for refactor planning
415
- const successfulThumbnails = thumbnails.filter((t) => t !== null);
416
- console.log(
417
- `AssetMediaEngine segment boundary test: ${successfulThumbnails.length}/${timestamps.length} successful`,
418
- );
419
-
420
- // Current implementation may have issues - document for refactor
421
- // In an ideal implementation, most of these should succeed
422
- for (const thumbnail of successfulThumbnails) {
423
- expect(thumbnail.thumbnail).toBeDefined();
424
- const canvas = thumbnail.thumbnail;
425
- expect(
426
- canvas instanceof HTMLCanvasElement ||
427
- canvas instanceof OffscreenCanvas,
428
- ).toBe(true);
429
- }
430
- });
431
- });
432
-
433
- describe("AssetMediaEngine Incompatibility Warning", () => {
434
- test("logs warning when EFThumbnailStrip targets AssetMediaEngine", async ({
435
- assetVideo,
436
- expect,
437
- }) => {
438
- // Spy on console.warn to capture the warning
439
- const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
440
-
441
- // Create a thumbnail strip and add it to DOM so it gets properly initialized
442
- const thumbnailStrip = document.createElement("ef-thumbnail-strip");
443
- thumbnailStrip.thumbnailWidth = 80;
444
- document.body.appendChild(thumbnailStrip);
445
-
446
- // Wait for both elements to complete their setup
447
- await Promise.all([
448
- assetVideo.updateComplete,
449
- thumbnailStrip.updateComplete,
450
- ]);
451
-
452
- // Directly set the target element to bypass TargetController complexity in tests
453
- assetVideo.id = "asset-video"; // For the warning message
454
- thumbnailStrip.targetElement = assetVideo;
455
-
456
- // Trigger the layout task through the normal flow by setting stripWidth
457
- // This mimics what ResizeObserver would do and triggers the warning
458
- (thumbnailStrip as any).stripWidth = 400;
459
-
460
- // Wait for the warning to be logged using vi.waitFor for event-driven testing
461
- await vi.waitFor(
462
- () => {
463
- expect(consoleSpy).toHaveBeenCalledWith(
464
- expect.stringContaining(
465
- "AssetMediaEngine: extractThumbnails not properly implemented",
466
- ),
467
- );
468
- },
469
- { timeout: 2000 },
470
- );
471
-
472
- // Clean up
473
- thumbnailStrip.remove();
474
-
475
- // Restore console.warn
476
- consoleSpy.mockRestore();
477
- });
478
-
479
- test("does NOT log warning when EFThumbnailStrip targets JitMediaEngine", async ({
480
- jitVideo,
481
- expect,
482
- }) => {
483
- // Spy on console.warn to ensure no warning is logged
484
- const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
485
-
486
- // Create a thumbnail strip and add it to DOM so it gets properly initialized
487
- const thumbnailStrip = document.createElement("ef-thumbnail-strip");
488
- thumbnailStrip.thumbnailWidth = 80;
489
- document.body.appendChild(thumbnailStrip);
490
-
491
- // Wait for elements to complete setup
492
- await Promise.all([
493
- jitVideo.updateComplete,
494
- thumbnailStrip.updateComplete,
495
- ]);
496
-
497
- // Directly set the target element to bypass TargetController complexity in tests
498
- jitVideo.id = "jit-video"; // For consistency
499
- thumbnailStrip.targetElement = jitVideo;
500
-
501
- // Trigger the layout task through the normal flow by setting stripWidth
502
- (thumbnailStrip as any).stripWidth = 400;
503
-
504
- // Wait for the layout task to complete using vi.waitFor
505
- await vi.waitFor(
506
- () => {
507
- // @ts-expect-error testing private task
508
- const layout = thumbnailStrip.thumbnailLayoutTask?.value;
509
- expect(layout?.count).toBeGreaterThan(0);
510
- },
511
- { timeout: 2000 },
512
- );
513
-
514
- // Check that NO AssetMediaEngine warning was logged
515
- const warningCalls = consoleSpy.mock.calls.filter((call) =>
516
- call[0].includes("AssetMediaEngine is not currently supported"),
517
- );
518
- expect(warningCalls).toHaveLength(0);
519
-
520
- // Clean up
521
- thumbnailStrip.remove();
522
-
523
- // Restore console.warn
524
- consoleSpy.mockRestore();
525
- });
526
- });
527
-
528
- describe("Caching Behavior", () => {
529
- test("global input cache is accessible for debugging", async ({
530
- expect,
531
- }) => {
532
- // Verify that the global Input cache is accessible
533
- expect((globalThis as any).debugInputCache).toBeDefined();
534
-
535
- const cache = (globalThis as any).debugInputCache;
536
- expect(cache.getStats).toBeDefined();
537
- expect(cache.clear).toBeDefined();
538
- });
539
-
540
- test("input instances are cached globally for efficiency", async ({
541
- jitVideo,
542
- expect,
543
- }) => {
544
- const mediaEngine = jitVideo.mediaEngineTask.value as JitMediaEngine;
545
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
546
-
547
- // Clear cache to start fresh
548
- (globalThis as any).debugInputCache.clear();
549
-
550
- // Extract thumbnails from same segment multiple times
551
- const firstBatch = await mediaEngine.extractThumbnails([1000, 1500]);
552
- const secondBatch = await mediaEngine.extractThumbnails([1200, 1800]); // Same segment
553
-
554
- expect(firstBatch).toHaveLength(2);
555
- expect(secondBatch).toHaveLength(2);
556
-
557
- // All should succeed
558
- expect(firstBatch.every((t) => t !== null)).toBe(true);
559
- expect(secondBatch.every((t) => t !== null)).toBe(true);
560
-
561
- // Verify that Input objects are being cached globally
562
- const cacheStats = (globalThis as any).debugInputCache.getStats();
563
- expect(cacheStats.size).toBeGreaterThan(0);
564
- console.log("Global Input cache stats:", cacheStats);
565
- });
566
-
567
- test("different segments create separate input cache entries", async ({
568
- jitVideo,
569
- expect,
570
- }) => {
571
- const mediaEngine = jitVideo.mediaEngineTask.value as JitMediaEngine;
572
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
573
-
574
- // Extract thumbnails from different segments
575
- const segment1 = await mediaEngine.extractThumbnails([1000]);
576
- const segment2 = await mediaEngine.extractThumbnails([3000]); // Different segment
577
- const segment3 = await mediaEngine.extractThumbnails([5000]); // Different segment
578
-
579
- expect(segment1).toHaveLength(1);
580
- expect(segment2).toHaveLength(1);
581
- expect(segment3).toHaveLength(1);
582
-
583
- // All should succeed
584
- expect(segment1[0]).toBeTruthy();
585
- expect(segment2[0]).toBeTruthy();
586
- expect(segment3[0]).toBeTruthy();
587
- });
588
- });
589
-
590
- describe("Error Handling", () => {
591
- test("handles media engine without video track", async ({ expect }) => {
592
- // Create a video element but don't wait for it to fully load
593
- const video = document.createElement("ef-video");
594
- video.src = "nonexistent.mp4";
595
- document.body.appendChild(video);
596
-
597
- try {
598
- await video.mediaEngineTask.run();
599
- const mediaEngine = video.mediaEngineTask.value;
600
-
601
- if (mediaEngine) {
602
- const timestamps = [1000];
603
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
604
-
605
- // Should handle gracefully with nulls
606
- expect(thumbnails).toHaveLength(1);
607
- expect(thumbnails[0]).toBeNull();
608
- }
609
- } catch (error) {
610
- // Media engine creation might fail for nonexistent file - that's expected
611
- expect(error).toBeDefined();
612
- } finally {
613
- video.remove();
614
- }
615
- });
616
-
617
- test("handles concurrent thumbnail extraction requests", async ({
618
- jitVideo,
619
- expect,
620
- }) => {
621
- const mediaEngine = jitVideo.mediaEngineTask.value!;
622
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
623
-
624
- // Start multiple concurrent extractions
625
- const promise1 = mediaEngine.extractThumbnails([1000, 2000]);
626
- const promise2 = mediaEngine.extractThumbnails([3000, 4000]);
627
- const promise3 = mediaEngine.extractThumbnails([5000, 6000]);
628
-
629
- const [result1, result2, result3] = await Promise.all([
630
- promise1,
631
- promise2,
632
- promise3,
633
- ]);
634
-
635
- expect(result1).toHaveLength(2);
636
- expect(result2).toHaveLength(2);
637
- expect(result3).toHaveLength(2);
638
-
639
- // All should succeed
640
- expect(result1.every((t) => t !== null)).toBe(true);
641
- expect(result2.every((t) => t !== null)).toBe(true);
642
- expect(result3.every((t) => t !== null)).toBe(true);
643
- });
644
- });
645
-
646
- describe("Performance Characteristics", () => {
647
- test("batch extraction is more efficient than individual calls", async ({
648
- jitVideo,
649
- expect,
650
- }) => {
651
- const mediaEngine = jitVideo.mediaEngineTask.value!;
652
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
653
-
654
- const timestamps = [1000, 2000, 3000, 4000];
655
-
656
- // Time batch extraction
657
- const batchStart = performance.now();
658
- const batchResults = await mediaEngine.extractThumbnails(timestamps);
659
- const batchEnd = performance.now();
660
-
661
- // Time individual extractions
662
- const individualStart = performance.now();
663
- const individualResults = [];
664
- for (const timestamp of timestamps) {
665
- const result = await mediaEngine.extractThumbnails([timestamp]);
666
- individualResults.push(result[0]);
667
- }
668
- const individualEnd = performance.now();
669
-
670
- const batchTime = batchEnd - batchStart;
671
- const individualTime = individualEnd - individualStart;
672
-
673
- expect(batchResults).toHaveLength(4);
674
- expect(individualResults).toHaveLength(4);
675
-
676
- // Results should be equivalent
677
- for (let i = 0; i < timestamps.length; i++) {
678
- expect(batchResults[i]?.timestamp).toBe(
679
- individualResults[i]?.timestamp,
680
- );
681
- }
682
-
683
- console.log(
684
- `Batch time: ${batchTime.toFixed(2)}ms, Individual time: ${individualTime.toFixed(2)}ms`,
685
- );
686
-
687
- // Batch should generally be faster (though this might vary in test environments)
688
- // We don't enforce this as a hard requirement since test timing can be variable
689
- expect(batchTime).toBeGreaterThan(0);
690
- expect(individualTime).toBeGreaterThan(0);
691
- });
692
-
693
- test("segment grouping optimizes cross-segment extraction", async ({
694
- jitVideo,
695
- expect,
696
- }) => {
697
- const mediaEngine = jitVideo.mediaEngineTask.value!;
698
- expect(mediaEngine).toBeInstanceOf(JitMediaEngine);
699
-
700
- // Extract thumbnails that span multiple segments but in an order
701
- // that would be inefficient without segment grouping
702
- const timestamps = [1000, 5000, 1500, 5500, 2000, 6000]; // Alternating segments
703
- const thumbnails = await mediaEngine.extractThumbnails(timestamps);
704
-
705
- expect(thumbnails).toHaveLength(6);
706
-
707
- // All should succeed despite the inefficient ordering
708
- for (let i = 0; i < timestamps.length; i++) {
709
- expect(thumbnails[i]).toBeTruthy();
710
- expect(thumbnails[i]?.timestamp).toBe(timestamps[i]);
711
- }
712
- });
713
- });
714
- });