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