@editframe/elements 0.19.2-beta.0 → 0.20.0-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.
- package/dist/elements/ContextProxiesController.d.ts +40 -0
- package/dist/elements/ContextProxiesController.js +69 -0
- package/dist/elements/EFCaptions.d.ts +45 -6
- package/dist/elements/EFCaptions.js +220 -26
- package/dist/elements/EFImage.js +4 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +2 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +9 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +11 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +13 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +9 -0
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +7 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +24 -0
- package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +39 -0
- package/dist/elements/EFMedia/shared/GlobalInputCache.js +57 -0
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +27 -0
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +106 -0
- package/dist/elements/EFMedia.js +25 -1
- package/dist/elements/EFSurface.browsertest.d.ts +0 -0
- package/dist/elements/EFSurface.d.ts +30 -0
- package/dist/elements/EFSurface.js +96 -0
- package/dist/elements/EFTemporal.js +7 -6
- package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
- package/dist/elements/EFThumbnailStrip.d.ts +86 -0
- package/dist/elements/EFThumbnailStrip.js +490 -0
- package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
- package/dist/elements/EFTimegroup.d.ts +7 -7
- package/dist/elements/EFTimegroup.js +59 -16
- package/dist/elements/updateAnimations.browsertest.d.ts +13 -0
- package/dist/elements/updateAnimations.d.ts +5 -0
- package/dist/elements/updateAnimations.js +37 -13
- package/dist/getRenderInfo.js +1 -1
- package/dist/gui/ContextMixin.js +27 -14
- package/dist/gui/EFControls.browsertest.d.ts +0 -0
- package/dist/gui/EFControls.d.ts +38 -0
- package/dist/gui/EFControls.js +51 -0
- package/dist/gui/EFFilmstrip.d.ts +40 -1
- package/dist/gui/EFFilmstrip.js +240 -3
- package/dist/gui/EFPreview.js +2 -1
- package/dist/gui/EFScrubber.d.ts +6 -5
- package/dist/gui/EFScrubber.js +31 -21
- package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
- package/dist/gui/EFTimeDisplay.d.ts +2 -6
- package/dist/gui/EFTimeDisplay.js +13 -23
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/currentTimeContext.d.ts +3 -0
- package/dist/gui/currentTimeContext.js +3 -0
- package/dist/gui/durationContext.d.ts +3 -0
- package/dist/gui/durationContext.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -1
- package/dist/style.css +1 -1
- package/dist/transcoding/types/index.d.ts +11 -0
- package/dist/utils/LRUCache.d.ts +46 -0
- package/dist/utils/LRUCache.js +382 -1
- package/dist/utils/LRUCache.test.d.ts +1 -0
- package/package.json +2 -2
- package/src/elements/ContextProxiesController.ts +123 -0
- package/src/elements/EFCaptions.browsertest.ts +1820 -0
- package/src/elements/EFCaptions.ts +373 -36
- package/src/elements/EFImage.ts +4 -1
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +30 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +33 -0
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +3 -8
- package/src/elements/EFMedia/BaseMediaEngine.ts +35 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +48 -0
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
- package/src/elements/EFMedia.ts +38 -1
- package/src/elements/EFSurface.browsertest.ts +155 -0
- package/src/elements/EFSurface.ts +141 -0
- package/src/elements/EFTemporal.ts +14 -8
- package/src/elements/EFThumbnailStrip.browsertest.ts +591 -0
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +713 -0
- package/src/elements/EFThumbnailStrip.ts +905 -0
- package/src/elements/EFTimegroup.browsertest.ts +56 -7
- package/src/elements/EFTimegroup.ts +88 -18
- package/src/elements/updateAnimations.browsertest.ts +361 -12
- package/src/elements/updateAnimations.ts +68 -19
- package/src/gui/ContextMixin.browsertest.ts +0 -25
- package/src/gui/ContextMixin.ts +44 -20
- package/src/gui/EFControls.browsertest.ts +175 -0
- package/src/gui/EFControls.ts +84 -0
- package/src/gui/EFFilmstrip.ts +323 -4
- package/src/gui/EFPreview.ts +2 -1
- package/src/gui/EFScrubber.ts +29 -25
- package/src/gui/EFTimeDisplay.browsertest.ts +237 -0
- package/src/gui/EFTimeDisplay.ts +12 -40
- package/src/gui/currentTimeContext.ts +5 -0
- package/src/gui/durationContext.ts +3 -0
- package/src/transcoding/types/index.ts +13 -0
- package/src/utils/LRUCache.test.ts +272 -0
- package/src/utils/LRUCache.ts +543 -0
- package/types.json +1 -1
- package/dist/transcoding/cache/CacheManager.d.ts +0 -73
- package/src/transcoding/cache/CacheManager.ts +0 -208
|
@@ -420,13 +420,57 @@ describe("setting currentTime", () => {
|
|
|
420
420
|
await timegroup.waitForMediaDurations();
|
|
421
421
|
|
|
422
422
|
timegroup.currentTime = 5_000; // 5000 seconds, should clamp to 10s
|
|
423
|
-
await timegroup.seekTask.
|
|
423
|
+
await timegroup.seekTask.run();
|
|
424
424
|
|
|
425
425
|
const storedValue = localStorage.getItem(timegroup.storageKey);
|
|
426
426
|
assert.equal(storedValue, "10"); // Should store 10 (clamped from 5000 to duration)
|
|
427
427
|
timegroup.remove();
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
test("root timegroup remains visible when currentTime equals duration exactly", async () => {
|
|
431
|
+
const timegroup = renderTimegroup(
|
|
432
|
+
html`<ef-timegroup id="end-time-test" mode="fixed" duration="10s"></ef-timegroup>`,
|
|
433
|
+
);
|
|
434
|
+
await timegroup.waitForMediaDurations();
|
|
435
|
+
|
|
436
|
+
// Set currentTime to exactly the duration
|
|
437
|
+
timegroup.currentTime = 10; // 10 seconds
|
|
438
|
+
await timegroup.seekTask.taskComplete;
|
|
439
|
+
await timegroup.frameTask.taskComplete;
|
|
440
|
+
|
|
441
|
+
// The root timegroup should still be visible at the exact end time
|
|
442
|
+
assert.notEqual(
|
|
443
|
+
timegroup.style.display,
|
|
444
|
+
"none",
|
|
445
|
+
"Root timegroup should be visible at exact end time",
|
|
446
|
+
);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("root timegroup becomes hidden only after currentTime exceeds duration", async () => {
|
|
450
|
+
const timegroup = renderTimegroup(
|
|
451
|
+
html`<ef-timegroup id="beyond-end-test" mode="fixed" duration="10s"></ef-timegroup>`,
|
|
452
|
+
);
|
|
453
|
+
await timegroup.waitForMediaDurations();
|
|
454
|
+
|
|
455
|
+
// Set currentTime beyond the duration (should be clamped to duration)
|
|
456
|
+
timegroup.currentTime = 15; // 15 seconds, should clamp to 10s
|
|
457
|
+
await timegroup.seekTask.taskComplete;
|
|
458
|
+
await timegroup.frameTask.taskComplete;
|
|
459
|
+
|
|
460
|
+
// Even when clamped, it should still be visible at the end
|
|
461
|
+
assert.notEqual(
|
|
462
|
+
timegroup.style.display,
|
|
463
|
+
"none",
|
|
464
|
+
"Root timegroup should be visible even when time is clamped to duration",
|
|
465
|
+
);
|
|
466
|
+
// Verify that the time was actually clamped
|
|
467
|
+
assert.equal(
|
|
468
|
+
timegroup.currentTime,
|
|
469
|
+
10,
|
|
470
|
+
"Time should be clamped to duration",
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
430
474
|
test("does not persist in localStorage if the timegroup has no id", async () => {
|
|
431
475
|
const timegroup = renderTimegroup(
|
|
432
476
|
html`<ef-timegroup mode="fixed" duration="10s"></ef-timegroup>`,
|
|
@@ -572,19 +616,23 @@ describe("Dynamic content updates", () => {
|
|
|
572
616
|
const frameTaskB = timegroup.querySelector("test-frame-task-b")!;
|
|
573
617
|
const frameTaskC = timegroup.querySelector("test-frame-task-c")!;
|
|
574
618
|
|
|
575
|
-
|
|
619
|
+
// following the initial update, the first frame tasks have run once.
|
|
620
|
+
await timegroup.updateComplete;
|
|
621
|
+
|
|
622
|
+
assert.equal(frameTaskA.frameTaskCount, 1);
|
|
576
623
|
assert.equal(frameTaskB.frameTaskCount, 0);
|
|
577
|
-
assert.equal(frameTaskC.frameTaskCount,
|
|
624
|
+
assert.equal(frameTaskC.frameTaskCount, 1);
|
|
578
625
|
|
|
626
|
+
// Then we run them manually.
|
|
579
627
|
await timegroup.frameTask.run();
|
|
580
628
|
|
|
581
629
|
// At timeline time 0ms:
|
|
582
630
|
// - frameTaskA (0-1000ms) should run
|
|
583
631
|
// - frameTaskB (1000-2000ms) should NOT run
|
|
584
632
|
// - frameTaskC (0-1000ms) should run (inherits root positioning)
|
|
585
|
-
assert.equal(frameTaskA.frameTaskCount,
|
|
633
|
+
assert.equal(frameTaskA.frameTaskCount, 2);
|
|
586
634
|
assert.equal(frameTaskB.frameTaskCount, 0); // Not visible at time 0
|
|
587
|
-
assert.equal(frameTaskC.frameTaskCount,
|
|
635
|
+
assert.equal(frameTaskC.frameTaskCount, 2); // Nested in B but inherits root positioning
|
|
588
636
|
});
|
|
589
637
|
});
|
|
590
638
|
|
|
@@ -599,9 +647,10 @@ describe("Dynamic content updates", () => {
|
|
|
599
647
|
);
|
|
600
648
|
const nonRootTimegroup = timegroup.querySelector("ef-timegroup")!;
|
|
601
649
|
const frameTaskA = timegroup.querySelector("test-frame-task-a")!;
|
|
602
|
-
|
|
650
|
+
await timegroup.updateComplete;
|
|
651
|
+
assert.equal(frameTaskA.frameTaskCount, 1);
|
|
603
652
|
await nonRootTimegroup.seekTask.run();
|
|
604
|
-
assert.equal(frameTaskA.frameTaskCount,
|
|
653
|
+
assert.equal(frameTaskA.frameTaskCount, 1);
|
|
605
654
|
});
|
|
606
655
|
|
|
607
656
|
test("waits for media durations", async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { provide } from "@lit/context";
|
|
2
2
|
import { Task, TaskStatus } from "@lit/task";
|
|
3
3
|
import debug from "debug";
|
|
4
|
-
import { css, html, LitElement } from "lit";
|
|
4
|
+
import { css, html, LitElement, type PropertyValues } from "lit";
|
|
5
5
|
import { customElement, property } from "lit/decorators.js";
|
|
6
6
|
|
|
7
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
timegroupContext,
|
|
18
18
|
} from "./EFTemporal.js";
|
|
19
19
|
import { TimegroupController } from "./TimegroupController.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
evaluateTemporalStateForAnimation,
|
|
22
|
+
updateAnimations,
|
|
23
|
+
} from "./updateAnimations.ts";
|
|
21
24
|
|
|
22
25
|
const log = debug("ef:elements:EFTimegroup");
|
|
23
26
|
|
|
@@ -104,8 +107,49 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
104
107
|
|
|
105
108
|
#processingPendingSeek = false;
|
|
106
109
|
|
|
110
|
+
#frameTaskInProgress = false;
|
|
111
|
+
|
|
112
|
+
#pendingFrameTaskRun = false;
|
|
113
|
+
|
|
114
|
+
#processingPendingFrameTask = false;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Throttles frameTask execution to ensure only one runs at a time while preserving the last request
|
|
118
|
+
*/
|
|
119
|
+
private async runThrottledFrameTask(): Promise<void> {
|
|
120
|
+
if (this.#frameTaskInProgress) {
|
|
121
|
+
this.#pendingFrameTaskRun = true;
|
|
122
|
+
// Wait for the current frame task to complete
|
|
123
|
+
while (this.#frameTaskInProgress) {
|
|
124
|
+
await this.frameTask.taskComplete;
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.#frameTaskInProgress = true;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await this.frameTask.run();
|
|
133
|
+
} finally {
|
|
134
|
+
this.#frameTaskInProgress = false;
|
|
135
|
+
|
|
136
|
+
if (this.#pendingFrameTaskRun && !this.#processingPendingFrameTask) {
|
|
137
|
+
this.#pendingFrameTaskRun = false;
|
|
138
|
+
this.#processingPendingFrameTask = true;
|
|
139
|
+
try {
|
|
140
|
+
await this.runThrottledFrameTask();
|
|
141
|
+
} finally {
|
|
142
|
+
this.#processingPendingFrameTask = false;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
this.#pendingFrameTaskRun = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
@property({ type: Number, attribute: "currenttime" })
|
|
108
151
|
set currentTime(time: number) {
|
|
152
|
+
time = Math.max(0, time);
|
|
109
153
|
if (!this.isRootTimegroup) {
|
|
110
154
|
return;
|
|
111
155
|
}
|
|
@@ -120,15 +164,12 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
120
164
|
}
|
|
121
165
|
|
|
122
166
|
if (this.#seekInProgress) {
|
|
123
|
-
console.trace("pending seek to", time);
|
|
124
167
|
this.#pendingSeekTime = time;
|
|
125
168
|
this.#currentTime = time;
|
|
126
169
|
return;
|
|
127
170
|
}
|
|
128
|
-
console.trace("seeking to", time);
|
|
129
171
|
|
|
130
172
|
this.#currentTime = time;
|
|
131
|
-
// This will be set to false in the seekTask
|
|
132
173
|
this.#seekInProgress = true;
|
|
133
174
|
|
|
134
175
|
this.seekTask.run().finally(() => {
|
|
@@ -212,14 +253,17 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
212
253
|
|
|
213
254
|
connectedCallback() {
|
|
214
255
|
super.connectedCallback();
|
|
215
|
-
|
|
216
|
-
this.
|
|
256
|
+
this.waitForMediaDurations().then(() => {
|
|
257
|
+
if (this.id) {
|
|
217
258
|
const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
|
|
218
259
|
if (maybeLoadedTime !== undefined) {
|
|
219
260
|
this.currentTime = maybeLoadedTime;
|
|
220
261
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
262
|
+
}
|
|
263
|
+
if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {
|
|
264
|
+
this.seekTask.run();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
223
267
|
|
|
224
268
|
if (this.parentTimegroup) {
|
|
225
269
|
new TimegroupController(this.parentTimegroup, this);
|
|
@@ -230,6 +274,15 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
230
274
|
}
|
|
231
275
|
}
|
|
232
276
|
|
|
277
|
+
#previousDurationMs = 0;
|
|
278
|
+
|
|
279
|
+
protected updated(_changedProperties: PropertyValues): void {
|
|
280
|
+
if (this.#previousDurationMs !== this.durationMs) {
|
|
281
|
+
this.#previousDurationMs = this.durationMs;
|
|
282
|
+
this.runThrottledFrameTask();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
233
286
|
disconnectedCallback() {
|
|
234
287
|
super.disconnectedCallback();
|
|
235
288
|
this.#resizeObserver?.disconnect();
|
|
@@ -331,7 +384,14 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
331
384
|
const startTimeMs = (temporal as any).startTimeMs as number;
|
|
332
385
|
const endTimeMs = (temporal as any).endTimeMs as number;
|
|
333
386
|
const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
|
|
334
|
-
|
|
387
|
+
// Root timegroups should remain visible at exact end time, but other elements use exclusive end for clean transitions
|
|
388
|
+
const isRootTimegroup =
|
|
389
|
+
temporal.tagName.toLowerCase() === "ef-timegroup" &&
|
|
390
|
+
!(temporal as any).parentTimegroup;
|
|
391
|
+
const useInclusiveEnd = isRootTimegroup;
|
|
392
|
+
const elementEndsAfterStart = useInclusiveEnd
|
|
393
|
+
? endTimeMs >= timelineTimeMs
|
|
394
|
+
: endTimeMs > timelineTimeMs;
|
|
335
395
|
return elementStartsBeforeEnd && elementEndsAfterStart;
|
|
336
396
|
});
|
|
337
397
|
|
|
@@ -364,25 +424,33 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
364
424
|
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
365
425
|
|
|
366
426
|
// Filter to only include temporally visible elements for frame processing
|
|
427
|
+
// Use animation-friendly visibility to prevent animation jumps at exact boundaries
|
|
367
428
|
const visibleElements = temporalElements.filter((element) => {
|
|
368
|
-
const
|
|
369
|
-
return
|
|
429
|
+
const animationState = evaluateTemporalStateForAnimation(element);
|
|
430
|
+
return animationState.isVisible;
|
|
370
431
|
});
|
|
371
432
|
|
|
372
433
|
await Promise.all(
|
|
373
|
-
visibleElements.map((element) =>
|
|
374
|
-
return element.frameTask.run();
|
|
375
|
-
}),
|
|
434
|
+
visibleElements.map((element) => element.frameTask.run()),
|
|
376
435
|
);
|
|
377
436
|
}
|
|
378
437
|
|
|
438
|
+
mediaDurationsPromise: Promise<void> | undefined = undefined;
|
|
439
|
+
|
|
440
|
+
async waitForMediaDurations() {
|
|
441
|
+
if (!this.mediaDurationsPromise) {
|
|
442
|
+
this.mediaDurationsPromise = this.#waitForMediaDurations();
|
|
443
|
+
}
|
|
444
|
+
return this.mediaDurationsPromise;
|
|
445
|
+
}
|
|
446
|
+
|
|
379
447
|
/**
|
|
380
448
|
* Wait for all media elements to load their initial segments.
|
|
381
449
|
* Ideally we would only need the extracted index json data, but
|
|
382
450
|
* that caused issues with constructing audio data. We had negative durations
|
|
383
451
|
* in calculations and it was not clear why.
|
|
384
452
|
*/
|
|
385
|
-
async waitForMediaDurations() {
|
|
453
|
+
async #waitForMediaDurations() {
|
|
386
454
|
// We must await updateComplete to ensure all media elements inside this are connected
|
|
387
455
|
// and will match deepGetMediaElements
|
|
388
456
|
await this.updateComplete;
|
|
@@ -668,9 +736,11 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
668
736
|
0,
|
|
669
737
|
Math.min(targetTime ?? 0, this.durationMs / 1000),
|
|
670
738
|
);
|
|
739
|
+
// Apply the clamped time back to currentTime
|
|
740
|
+
this.#currentTime = newTime;
|
|
671
741
|
this.requestUpdate("currentTime");
|
|
672
|
-
await this.
|
|
673
|
-
this.#saveTimeToLocalStorage(
|
|
742
|
+
await this.runThrottledFrameTask();
|
|
743
|
+
this.#saveTimeToLocalStorage(this.#currentTime);
|
|
674
744
|
// This has to be set false here so any following seeks are not treated as pending
|
|
675
745
|
this.#seekInProgress = false;
|
|
676
746
|
return newTime;
|