@editframe/elements 0.18.27-beta.0 → 0.19.2-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 (73) hide show
  1. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +10 -0
  2. package/dist/elements/EFMedia/AssetMediaEngine.js +13 -1
  3. package/dist/elements/EFMedia/JitMediaEngine.d.ts +10 -0
  4. package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
  5. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +16 -12
  6. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +1 -1
  7. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -4
  8. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +1 -1
  9. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -4
  10. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -1
  11. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +3 -2
  12. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +16 -12
  13. package/dist/elements/EFMedia.d.ts +2 -3
  14. package/dist/elements/EFMedia.js +0 -4
  15. package/dist/elements/EFTemporal.d.ts +9 -6
  16. package/dist/elements/EFTemporal.js +15 -12
  17. package/dist/elements/EFTimegroup.browsertest.d.ts +26 -0
  18. package/dist/elements/EFTimegroup.d.ts +12 -9
  19. package/dist/elements/EFTimegroup.js +114 -65
  20. package/dist/elements/EFVideo.d.ts +5 -1
  21. package/dist/elements/EFVideo.js +16 -8
  22. package/dist/elements/EFWaveform.js +2 -3
  23. package/dist/elements/FetchContext.browsertest.d.ts +0 -0
  24. package/dist/elements/FetchMixin.js +14 -9
  25. package/dist/elements/TimegroupController.js +2 -1
  26. package/dist/elements/updateAnimations.browsertest.d.ts +0 -0
  27. package/dist/elements/updateAnimations.d.ts +19 -9
  28. package/dist/elements/updateAnimations.js +64 -25
  29. package/dist/gui/ContextMixin.js +34 -27
  30. package/dist/gui/EFConfiguration.d.ts +1 -1
  31. package/dist/gui/EFConfiguration.js +1 -0
  32. package/dist/gui/EFFilmstrip.d.ts +1 -0
  33. package/dist/gui/EFFilmstrip.js +12 -14
  34. package/dist/gui/TWMixin.js +1 -1
  35. package/dist/style.css +1 -1
  36. package/dist/transcoding/cache/URLTokenDeduplicator.d.ts +38 -0
  37. package/dist/transcoding/cache/URLTokenDeduplicator.js +66 -0
  38. package/dist/transcoding/cache/URLTokenDeduplicator.test.d.ts +1 -0
  39. package/dist/transcoding/types/index.d.ts +10 -0
  40. package/package.json +2 -2
  41. package/src/elements/EFMedia/AssetMediaEngine.ts +16 -2
  42. package/src/elements/EFMedia/JitMediaEngine.ts +14 -0
  43. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -1
  44. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +11 -4
  45. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -4
  46. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +4 -1
  47. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -5
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +2 -2
  49. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +7 -3
  50. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +11 -4
  51. package/src/elements/EFMedia.browsertest.ts +13 -4
  52. package/src/elements/EFMedia.ts +6 -10
  53. package/src/elements/EFTemporal.ts +21 -26
  54. package/src/elements/EFTimegroup.browsertest.ts +186 -2
  55. package/src/elements/EFTimegroup.ts +190 -94
  56. package/src/elements/EFVideo.browsertest.ts +53 -132
  57. package/src/elements/EFVideo.ts +26 -13
  58. package/src/elements/EFWaveform.ts +2 -3
  59. package/src/elements/FetchContext.browsertest.ts +396 -0
  60. package/src/elements/FetchMixin.ts +25 -8
  61. package/src/elements/TimegroupController.ts +2 -1
  62. package/src/elements/updateAnimations.browsertest.ts +559 -0
  63. package/src/elements/updateAnimations.ts +113 -50
  64. package/src/gui/ContextMixin.browsertest.ts +4 -9
  65. package/src/gui/ContextMixin.ts +52 -33
  66. package/src/gui/EFConfiguration.ts +1 -1
  67. package/src/gui/EFFilmstrip.ts +15 -18
  68. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +182 -0
  69. package/src/transcoding/cache/URLTokenDeduplicator.ts +101 -0
  70. package/src/transcoding/types/index.ts +11 -0
  71. package/test/EFVideo.framegen.browsertest.ts +1 -1
  72. package/test/setup.ts +2 -0
  73. package/types.json +1 -1
@@ -0,0 +1,559 @@
1
+ import { assert, beforeEach, describe, test } from "vitest";
2
+ import type { EFTimegroup } from "./EFTimegroup.js";
3
+ import {
4
+ type AnimatableElement,
5
+ updateAnimations,
6
+ } from "./updateAnimations.js";
7
+
8
+ import "./EFTimegroup.js";
9
+
10
+ // Import the constant for tests that need to check animation precision
11
+
12
+ beforeEach(() => {
13
+ // Clean up DOM
14
+ while (document.body.children.length) {
15
+ document.body.children[0]?.remove();
16
+ }
17
+ // Clean up localStorage
18
+ for (let i = 0; i < localStorage.length; i++) {
19
+ const key = localStorage.key(i);
20
+ if (typeof key !== "string") continue;
21
+ localStorage.removeItem(key);
22
+ }
23
+ });
24
+
25
+ function createTestElement(
26
+ props: Partial<AnimatableElement> = {},
27
+ ): AnimatableElement {
28
+ const element = document.createElement("div") as unknown as AnimatableElement;
29
+ // Override readonly properties for testing
30
+ Object.defineProperty(element, "currentTimeMs", {
31
+ value: props.currentTimeMs ?? 0,
32
+ writable: true,
33
+ });
34
+ Object.defineProperty(element, "durationMs", {
35
+ value: props.durationMs ?? 1000,
36
+ writable: true,
37
+ });
38
+ Object.defineProperty(element, "startTimeMs", {
39
+ value: props.startTimeMs ?? 0,
40
+ writable: true,
41
+ });
42
+ Object.defineProperty(element, "endTimeMs", {
43
+ value: props.endTimeMs ?? 1000,
44
+ writable: true,
45
+ });
46
+ Object.defineProperty(element, "rootTimegroup", {
47
+ value: props.rootTimegroup,
48
+ writable: true,
49
+ });
50
+ Object.defineProperty(element, "parentTimegroup", {
51
+ value: props.parentTimegroup,
52
+ writable: true,
53
+ });
54
+ document.body.appendChild(element);
55
+ return element;
56
+ }
57
+
58
+ describe("Timeline Element Synchronizer", () => {
59
+ describe("CSS custom properties", () => {
60
+ test("sets --ef-progress based on currentTimeMs/durationMs ratio", () => {
61
+ const element = createTestElement({
62
+ currentTimeMs: 250,
63
+ durationMs: 1000,
64
+ });
65
+
66
+ updateAnimations(element);
67
+
68
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "25%");
69
+ });
70
+
71
+ test("clamps --ef-progress to 0-100% range", () => {
72
+ const element1 = createTestElement({
73
+ currentTimeMs: -100,
74
+ durationMs: 1000,
75
+ });
76
+ const element2 = createTestElement({
77
+ currentTimeMs: 1500,
78
+ durationMs: 1000,
79
+ });
80
+
81
+ updateAnimations(element1);
82
+ updateAnimations(element2);
83
+
84
+ assert.equal(element1.style.getPropertyValue("--ef-progress"), "0%");
85
+ assert.equal(element2.style.getPropertyValue("--ef-progress"), "100%");
86
+ });
87
+
88
+ test("sets --ef-duration to element durationMs", () => {
89
+ const element = createTestElement({
90
+ durationMs: 2000,
91
+ });
92
+
93
+ updateAnimations(element);
94
+
95
+ assert.equal(element.style.getPropertyValue("--ef-duration"), "2000ms");
96
+ });
97
+
98
+ test("sets --ef-transition-duration based on parentTimegroup overlapMs", () => {
99
+ const parentTimegroup = document.createElement(
100
+ "ef-timegroup",
101
+ ) as EFTimegroup;
102
+ parentTimegroup.overlapMs = 500;
103
+
104
+ const element = createTestElement({
105
+ parentTimegroup,
106
+ });
107
+
108
+ updateAnimations(element);
109
+
110
+ assert.equal(
111
+ element.style.getPropertyValue("--ef-transition-duration"),
112
+ "500ms",
113
+ );
114
+ });
115
+
116
+ test("sets --ef-transition-duration to 0ms when no parentTimegroup", () => {
117
+ const element = createTestElement();
118
+
119
+ updateAnimations(element);
120
+
121
+ assert.equal(
122
+ element.style.getPropertyValue("--ef-transition-duration"),
123
+ "0ms",
124
+ );
125
+ });
126
+
127
+ test("sets --ef-transition-out-start correctly", () => {
128
+ const parentTimegroup = document.createElement(
129
+ "ef-timegroup",
130
+ ) as EFTimegroup;
131
+ parentTimegroup.overlapMs = 200;
132
+
133
+ const element = createTestElement({
134
+ durationMs: 1000,
135
+ parentTimegroup,
136
+ });
137
+
138
+ updateAnimations(element);
139
+
140
+ assert.equal(
141
+ element.style.getPropertyValue("--ef-transition-out-start"),
142
+ "800ms",
143
+ );
144
+ });
145
+
146
+ test("sets animation-related CSS properties when animations are present", () => {
147
+ const element = createTestElement({
148
+ durationMs: 2000,
149
+ });
150
+
151
+ // Create an animation to trigger CSS property setting
152
+ element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 });
153
+
154
+ updateAnimations(element);
155
+
156
+ assert.equal(element.style.getPropertyValue("--ef-duration"), "2000ms");
157
+ assert.equal(
158
+ element.style.getPropertyValue("--ef-transition-duration"),
159
+ "0ms",
160
+ );
161
+ assert.equal(
162
+ element.style.getPropertyValue("--ef-transition-out-start"),
163
+ "2000ms",
164
+ );
165
+ });
166
+ });
167
+
168
+ describe("element visibility", () => {
169
+ test("hides element when timeline is before startTimeMs", () => {
170
+ const rootTimegroup = document.createElement(
171
+ "ef-timegroup",
172
+ ) as EFTimegroup;
173
+ rootTimegroup.currentTimeMs = 100;
174
+
175
+ const element = createTestElement({
176
+ startTimeMs: 200,
177
+ endTimeMs: 800,
178
+ rootTimegroup,
179
+ });
180
+
181
+ updateAnimations(element);
182
+
183
+ assert.equal(element.style.display, "none");
184
+ });
185
+
186
+ test("hides element when timeline is after endTimeMs", () => {
187
+ const rootTimegroup = document.createElement(
188
+ "ef-timegroup",
189
+ ) as EFTimegroup;
190
+ rootTimegroup.currentTimeMs = 900;
191
+
192
+ const element = createTestElement({
193
+ startTimeMs: 200,
194
+ endTimeMs: 800,
195
+ rootTimegroup,
196
+ });
197
+
198
+ updateAnimations(element);
199
+
200
+ assert.equal(element.style.display, "none");
201
+ });
202
+
203
+ test("shows element when timeline is within start/end range (using element currentTimeMs)", () => {
204
+ const element = createTestElement({
205
+ currentTimeMs: 500,
206
+ startTimeMs: 200,
207
+ endTimeMs: 800,
208
+ });
209
+ // Start with element hidden
210
+ element.style.display = "none";
211
+
212
+ updateAnimations(element);
213
+
214
+ assert.equal(element.style.display, "");
215
+ });
216
+
217
+ test("uses element currentTimeMs when no rootTimegroup", () => {
218
+ const element = createTestElement({
219
+ currentTimeMs: 500,
220
+ startTimeMs: 200,
221
+ endTimeMs: 800,
222
+ });
223
+ element.style.display = "none";
224
+
225
+ updateAnimations(element);
226
+
227
+ assert.equal(element.style.display, "");
228
+ });
229
+
230
+ test("element at exact start boundary is visible (using element currentTimeMs)", () => {
231
+ const element = createTestElement({
232
+ currentTimeMs: 200,
233
+ startTimeMs: 200,
234
+ endTimeMs: 800,
235
+ });
236
+ element.style.display = "none";
237
+
238
+ updateAnimations(element);
239
+
240
+ assert.equal(element.style.display, "");
241
+ });
242
+
243
+ test("element at exact end boundary is hidden (using element currentTimeMs)", () => {
244
+ const element = createTestElement({
245
+ currentTimeMs: 800,
246
+ startTimeMs: 200,
247
+ endTimeMs: 800,
248
+ });
249
+ element.style.display = "";
250
+
251
+ updateAnimations(element);
252
+
253
+ assert.equal(element.style.display, "none");
254
+ });
255
+
256
+ test("element just before start boundary is hidden", () => {
257
+ const element = createTestElement({
258
+ currentTimeMs: 199,
259
+ startTimeMs: 200,
260
+ endTimeMs: 800,
261
+ });
262
+
263
+ updateAnimations(element);
264
+
265
+ assert.equal(element.style.display, "none");
266
+ });
267
+
268
+ test("element just after end boundary is hidden", () => {
269
+ const element = createTestElement({
270
+ currentTimeMs: 801,
271
+ startTimeMs: 200,
272
+ endTimeMs: 800,
273
+ });
274
+
275
+ updateAnimations(element);
276
+
277
+ assert.equal(element.style.display, "none");
278
+ });
279
+ });
280
+
281
+ describe("Web Animations API integration", () => {
282
+ test("skips animation processing when getAnimations is not available", () => {
283
+ const element = createTestElement();
284
+ // Mock missing getAnimations
285
+ delete (element as any).getAnimations;
286
+
287
+ // Should not throw and should still set CSS properties
288
+ updateAnimations(element);
289
+
290
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "0%");
291
+ });
292
+
293
+ test("pauses running animations", async () => {
294
+ const element = createTestElement();
295
+
296
+ // Create a test animation
297
+ const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
298
+ duration: 1000,
299
+ });
300
+ animation.play();
301
+
302
+ assert.equal(animation.playState, "running");
303
+
304
+ updateAnimations(element);
305
+
306
+ assert.equal(animation.playState, "paused");
307
+ });
308
+
309
+ test("ignores animations without KeyframeEffect", async () => {
310
+ const element = createTestElement();
311
+
312
+ // Create animation with non-KeyframeEffect (this is tricky to test, but we can verify no errors)
313
+ const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
314
+ duration: 1000,
315
+ });
316
+
317
+ // Mock the effect to not be a KeyframeEffect
318
+ Object.defineProperty(animation, "effect", {
319
+ value: {},
320
+ writable: false,
321
+ });
322
+
323
+ // Should not throw
324
+ updateAnimations(element);
325
+ });
326
+
327
+ test("ignores animations without target", async () => {
328
+ const element = createTestElement();
329
+
330
+ const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
331
+ duration: 1000,
332
+ });
333
+
334
+ // Mock the effect target to be null
335
+ if (animation.effect instanceof KeyframeEffect) {
336
+ Object.defineProperty(animation.effect, "target", {
337
+ value: null,
338
+ writable: false,
339
+ });
340
+ }
341
+
342
+ // Should not throw
343
+ updateAnimations(element);
344
+ });
345
+
346
+ test("handles missing timeTarget gracefully", async () => {
347
+ const element = createTestElement();
348
+
349
+ const target = document.createElement("div");
350
+ element.appendChild(target);
351
+
352
+ // Should not throw when target has no temporal parent
353
+ updateAnimations(element);
354
+ });
355
+
356
+ test("processes multiple animations on same element", async () => {
357
+ const element = createTestElement();
358
+
359
+ const animation1 = element.animate([{ opacity: 0 }, { opacity: 1 }], {
360
+ duration: 1000,
361
+ });
362
+ const animation2 = element.animate(
363
+ [{ transform: "scale(1)" }, { transform: "scale(2)" }],
364
+ { duration: 500 },
365
+ );
366
+
367
+ animation1.play();
368
+ animation2.play();
369
+
370
+ assert.equal(animation1.playState, "running");
371
+ assert.equal(animation2.playState, "running");
372
+
373
+ updateAnimations(element);
374
+
375
+ // Both animations should be paused
376
+ assert.equal(animation1.playState, "paused");
377
+ assert.equal(animation2.playState, "paused");
378
+ });
379
+
380
+ test("handles animations with zero duration", async () => {
381
+ const element = createTestElement();
382
+
383
+ // Should not throw
384
+ updateAnimations(element);
385
+ });
386
+
387
+ test("handles animations with zero iterations", async () => {
388
+ const element = createTestElement();
389
+
390
+ const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
391
+ duration: 1000,
392
+ iterations: 0,
393
+ });
394
+
395
+ updateAnimations(element);
396
+
397
+ // With 0 iterations and no timeTarget, currentIteration (0) >= iterations (0), so should be set to duration - epsilon
398
+ // But since there's no timeTarget, the code path continues and doesn't set currentTime
399
+ // The animation will continue with its default behavior
400
+ assert.equal(animation.currentTime, 0);
401
+ });
402
+
403
+ test("handles animations that are already paused", async () => {
404
+ const element = createTestElement();
405
+
406
+ const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
407
+ duration: 1000,
408
+ });
409
+ animation.pause();
410
+
411
+ assert.equal(animation.playState, "paused");
412
+
413
+ // Should not throw and should still set currentTime
414
+ updateAnimations(element);
415
+
416
+ assert.equal(animation.playState, "paused");
417
+ });
418
+ });
419
+
420
+ describe("edge cases", () => {
421
+ test("handles zero duration gracefully", () => {
422
+ const element = createTestElement({
423
+ currentTimeMs: 100,
424
+ durationMs: 0,
425
+ });
426
+
427
+ updateAnimations(element);
428
+
429
+ // Should handle division by zero
430
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "100%");
431
+ });
432
+
433
+ test("handles negative currentTimeMs", () => {
434
+ const element = createTestElement({
435
+ currentTimeMs: -100,
436
+ durationMs: 1000,
437
+ });
438
+
439
+ updateAnimations(element);
440
+
441
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "0%");
442
+ });
443
+
444
+ test("handles missing parentTimegroup overlapMs", () => {
445
+ const parentTimegroup = {} as EFTimegroup; // Missing overlapMs property
446
+
447
+ const element = createTestElement({
448
+ parentTimegroup,
449
+ durationMs: 1000,
450
+ });
451
+
452
+ updateAnimations(element);
453
+
454
+ assert.equal(
455
+ element.style.getPropertyValue("--ef-transition-duration"),
456
+ "0ms",
457
+ );
458
+ assert.equal(
459
+ element.style.getPropertyValue("--ef-transition-out-start"),
460
+ "1000ms",
461
+ );
462
+ });
463
+
464
+ test("handles large duration values", () => {
465
+ const element = createTestElement({
466
+ currentTimeMs: 5000,
467
+ durationMs: 1000000, // 1000 seconds
468
+ });
469
+
470
+ updateAnimations(element);
471
+
472
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "0.5%");
473
+ });
474
+
475
+ test("handles very small time values", () => {
476
+ const element = createTestElement({
477
+ currentTimeMs: 0.5,
478
+ durationMs: 10,
479
+ });
480
+
481
+ updateAnimations(element);
482
+
483
+ assert.equal(element.style.getPropertyValue("--ef-progress"), "5%");
484
+ });
485
+
486
+ test("does not modify display when element should remain visible", () => {
487
+ const element = createTestElement({
488
+ currentTimeMs: 500,
489
+ startTimeMs: 200,
490
+ endTimeMs: 800,
491
+ });
492
+ // Element starts visible (default state)
493
+ const initialDisplay = element.style.display;
494
+
495
+ updateAnimations(element);
496
+
497
+ // Should not have been modified
498
+ assert.equal(element.style.display, initialDisplay);
499
+ });
500
+
501
+ test("does not modify display when element should remain hidden", () => {
502
+ const element = createTestElement({
503
+ currentTimeMs: 100,
504
+ startTimeMs: 200,
505
+ endTimeMs: 800,
506
+ });
507
+ element.style.display = "none";
508
+
509
+ updateAnimations(element);
510
+
511
+ // Should still be hidden
512
+ assert.equal(element.style.display, "none");
513
+ });
514
+ });
515
+
516
+ describe("CSS variables with zero overlap", () => {
517
+ test("correctly sets transition duration to 0ms when overlap is 0ms", () => {
518
+ const element = createTestElement({
519
+ currentTimeMs: 500,
520
+ startTimeMs: 0,
521
+ endTimeMs: 1000,
522
+ durationMs: 1000,
523
+ parentTimegroup: {
524
+ overlapMs: 0, // Zero overlap case
525
+ } as any,
526
+ });
527
+
528
+ updateAnimations(element);
529
+
530
+ // When overlap is 0, transition duration should be 0ms (no overlap means no transition)
531
+ const transitionDuration = element.style.getPropertyValue(
532
+ "--ef-transition-duration",
533
+ );
534
+ assert.equal(
535
+ transitionDuration,
536
+ "0ms",
537
+ "Transition duration should be 0ms when no overlap",
538
+ );
539
+
540
+ // Transition out start should equal full duration (no transition period)
541
+ const transitionOutStart = element.style.getPropertyValue(
542
+ "--ef-transition-out-start",
543
+ );
544
+ assert.equal(
545
+ transitionOutStart,
546
+ "1000ms",
547
+ "Transition out start should equal full duration when no overlap",
548
+ );
549
+
550
+ // Duration variable should still be set correctly for within-clip animations
551
+ const duration = element.style.getPropertyValue("--ef-duration");
552
+ assert.equal(
553
+ duration,
554
+ "1000ms",
555
+ "Duration should be set for within-clip animation calculations",
556
+ );
557
+ });
558
+ });
559
+ });