@editframe/elements 0.26.3-beta.0 → 0.26.4-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 (132) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-css.js +3 -3
  3. package/tsdown.config.ts +1 -1
  4. package/src/elements/ContextProxiesController.ts +0 -124
  5. package/src/elements/CrossUpdateController.ts +0 -22
  6. package/src/elements/EFAudio.browsertest.ts +0 -706
  7. package/src/elements/EFAudio.ts +0 -56
  8. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  9. package/src/elements/EFCaptions.ts +0 -823
  10. package/src/elements/EFImage.browsertest.ts +0 -120
  11. package/src/elements/EFImage.ts +0 -113
  12. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  13. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  14. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  15. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  16. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  17. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  18. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  19. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  20. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  21. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  22. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  23. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  24. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  25. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  26. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  27. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  28. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  29. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  30. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  31. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  32. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  33. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  34. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  35. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  36. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  37. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  38. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  39. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  40. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  41. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  42. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  43. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  44. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  45. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  46. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  47. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  49. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  53. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  54. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  55. package/src/elements/EFMedia.browsertest.ts +0 -872
  56. package/src/elements/EFMedia.ts +0 -341
  57. package/src/elements/EFSourceMixin.ts +0 -60
  58. package/src/elements/EFSurface.browsertest.ts +0 -151
  59. package/src/elements/EFSurface.ts +0 -142
  60. package/src/elements/EFTemporal.browsertest.ts +0 -215
  61. package/src/elements/EFTemporal.ts +0 -800
  62. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  63. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  64. package/src/elements/EFThumbnailStrip.ts +0 -906
  65. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  66. package/src/elements/EFTimegroup.ts +0 -882
  67. package/src/elements/EFVideo.browsertest.ts +0 -1482
  68. package/src/elements/EFVideo.ts +0 -564
  69. package/src/elements/EFWaveform.ts +0 -547
  70. package/src/elements/FetchContext.browsertest.ts +0 -401
  71. package/src/elements/FetchMixin.ts +0 -38
  72. package/src/elements/SampleBuffer.ts +0 -94
  73. package/src/elements/TargetController.browsertest.ts +0 -230
  74. package/src/elements/TargetController.ts +0 -224
  75. package/src/elements/TimegroupController.ts +0 -26
  76. package/src/elements/durationConverter.ts +0 -35
  77. package/src/elements/parseTimeToMs.ts +0 -9
  78. package/src/elements/printTaskStatus.ts +0 -16
  79. package/src/elements/renderTemporalAudio.ts +0 -108
  80. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  81. package/src/elements/updateAnimations.ts +0 -217
  82. package/src/elements/util.ts +0 -24
  83. package/src/gui/ContextMixin.browsertest.ts +0 -860
  84. package/src/gui/ContextMixin.ts +0 -562
  85. package/src/gui/Controllable.browsertest.ts +0 -258
  86. package/src/gui/Controllable.ts +0 -41
  87. package/src/gui/EFConfiguration.ts +0 -40
  88. package/src/gui/EFControls.browsertest.ts +0 -389
  89. package/src/gui/EFControls.ts +0 -195
  90. package/src/gui/EFDial.browsertest.ts +0 -84
  91. package/src/gui/EFDial.ts +0 -172
  92. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  93. package/src/gui/EFFilmstrip.ts +0 -1349
  94. package/src/gui/EFFitScale.ts +0 -152
  95. package/src/gui/EFFocusOverlay.ts +0 -79
  96. package/src/gui/EFPause.browsertest.ts +0 -202
  97. package/src/gui/EFPause.ts +0 -73
  98. package/src/gui/EFPlay.browsertest.ts +0 -202
  99. package/src/gui/EFPlay.ts +0 -73
  100. package/src/gui/EFPreview.ts +0 -74
  101. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  102. package/src/gui/EFResizableBox.ts +0 -898
  103. package/src/gui/EFScrubber.ts +0 -151
  104. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  105. package/src/gui/EFTimeDisplay.ts +0 -55
  106. package/src/gui/EFToggleLoop.ts +0 -35
  107. package/src/gui/EFTogglePlay.ts +0 -70
  108. package/src/gui/EFWorkbench.ts +0 -115
  109. package/src/gui/PlaybackController.ts +0 -527
  110. package/src/gui/TWMixin.css +0 -6
  111. package/src/gui/TWMixin.ts +0 -61
  112. package/src/gui/TargetOrContextMixin.ts +0 -185
  113. package/src/gui/currentTimeContext.ts +0 -5
  114. package/src/gui/durationContext.ts +0 -3
  115. package/src/gui/efContext.ts +0 -6
  116. package/src/gui/fetchContext.ts +0 -5
  117. package/src/gui/focusContext.ts +0 -7
  118. package/src/gui/focusedElementContext.ts +0 -5
  119. package/src/gui/playingContext.ts +0 -5
  120. package/src/otel/BridgeSpanExporter.ts +0 -150
  121. package/src/otel/setupBrowserTracing.ts +0 -73
  122. package/src/otel/tracingHelpers.ts +0 -251
  123. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  124. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  125. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  126. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  127. package/src/transcoding/types/index.ts +0 -312
  128. package/src/transcoding/utils/MediaUtils.ts +0 -63
  129. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  130. package/src/transcoding/utils/constants.ts +0 -36
  131. package/src/utils/LRUCache.test.ts +0 -274
  132. 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
- });