@editframe/elements 0.25.1-beta.0 → 0.26.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/EFAudio.d.ts +4 -4
- package/dist/elements/EFCaptions.d.ts +12 -12
- package/dist/elements/EFImage.d.ts +4 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +13 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +2 -1
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +16 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -4
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +2 -2
- package/dist/elements/EFSurface.d.ts +4 -4
- package/dist/elements/EFTemporal.js +16 -2
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +4 -4
- package/dist/elements/EFTimegroup.d.ts +22 -0
- package/dist/elements/EFTimegroup.js +35 -0
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/elements/updateAnimations.js +3 -1
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +2 -2
- package/dist/gui/EFPlay.d.ts +2 -2
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFToggleLoop.d.ts +2 -2
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFWorkbench.d.ts +6 -6
- package/dist/style.css +10 -0
- package/dist/transcoding/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/elements/EFMedia/AssetMediaEngine.ts +1 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +20 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +68 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +1 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +12 -0
- package/src/elements/EFMedia/shared/BufferUtils.ts +42 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +12 -0
- package/src/elements/EFTemporal.ts +20 -4
- package/src/elements/EFTimegroup.browsertest.ts +198 -0
- package/src/elements/EFTimegroup.ts +57 -0
- package/src/elements/updateAnimations.browsertest.ts +801 -0
- package/src/elements/updateAnimations.ts +12 -1
- package/src/transcoding/types/index.ts +1 -0
- package/types.json +1 -1
|
@@ -256,10 +256,18 @@ export const deepGetElementsWithFrameTasks = (
|
|
|
256
256
|
};
|
|
257
257
|
|
|
258
258
|
let temporalCache: Map<Element, TemporalMixinInterface[]>;
|
|
259
|
+
let temporalCacheResetScheduled = false;
|
|
259
260
|
export const resetTemporalCache = () => {
|
|
260
261
|
temporalCache = new Map();
|
|
261
|
-
if (
|
|
262
|
-
requestAnimationFrame
|
|
262
|
+
if (
|
|
263
|
+
typeof requestAnimationFrame !== "undefined" &&
|
|
264
|
+
!temporalCacheResetScheduled
|
|
265
|
+
) {
|
|
266
|
+
temporalCacheResetScheduled = true;
|
|
267
|
+
requestAnimationFrame(() => {
|
|
268
|
+
temporalCacheResetScheduled = false;
|
|
269
|
+
resetTemporalCache();
|
|
270
|
+
});
|
|
263
271
|
}
|
|
264
272
|
};
|
|
265
273
|
resetTemporalCache();
|
|
@@ -303,10 +311,18 @@ export class OwnCurrentTimeController implements ReactiveController {
|
|
|
303
311
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
304
312
|
|
|
305
313
|
let startTimeMsCache = new WeakMap<Element, number>();
|
|
314
|
+
let startTimeMsCacheResetScheduled = false;
|
|
306
315
|
const resetStartTimeMsCache = () => {
|
|
307
316
|
startTimeMsCache = new WeakMap();
|
|
308
|
-
if (
|
|
309
|
-
requestAnimationFrame
|
|
317
|
+
if (
|
|
318
|
+
typeof requestAnimationFrame !== "undefined" &&
|
|
319
|
+
!startTimeMsCacheResetScheduled
|
|
320
|
+
) {
|
|
321
|
+
startTimeMsCacheResetScheduled = true;
|
|
322
|
+
requestAnimationFrame(() => {
|
|
323
|
+
startTimeMsCacheResetScheduled = false;
|
|
324
|
+
resetStartTimeMsCache();
|
|
325
|
+
});
|
|
310
326
|
}
|
|
311
327
|
};
|
|
312
328
|
resetStartTimeMsCache();
|
|
@@ -669,4 +669,202 @@ describe("Dynamic content updates", () => {
|
|
|
669
669
|
assert.equal(media.mediaEngineTaskCount, 1);
|
|
670
670
|
});
|
|
671
671
|
});
|
|
672
|
+
|
|
673
|
+
describe("custom frame tasks", () => {
|
|
674
|
+
test("executes registered callback on frame update", async () => {
|
|
675
|
+
const timegroup = renderTimegroup(
|
|
676
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
let callbackExecuted = false;
|
|
680
|
+
const callback = () => {
|
|
681
|
+
callbackExecuted = true;
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
timegroup.addFrameTask(callback);
|
|
685
|
+
await timegroup.seek(1000);
|
|
686
|
+
|
|
687
|
+
assert.equal(callbackExecuted, true);
|
|
688
|
+
}, 1000);
|
|
689
|
+
|
|
690
|
+
test("callback receives correct timing information", async () => {
|
|
691
|
+
const timegroup = renderTimegroup(
|
|
692
|
+
html`<ef-timegroup mode="fixed" duration="5000ms"></ef-timegroup>`,
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
let receivedInfo: any = null;
|
|
696
|
+
const callback = (info: any) => {
|
|
697
|
+
receivedInfo = info;
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
timegroup.addFrameTask(callback);
|
|
701
|
+
await timegroup.seek(2000);
|
|
702
|
+
|
|
703
|
+
assert.equal(receivedInfo.ownCurrentTimeMs, 2000);
|
|
704
|
+
assert.equal(receivedInfo.currentTimeMs, 2000);
|
|
705
|
+
assert.equal(receivedInfo.durationMs, 5000);
|
|
706
|
+
assert.equal(receivedInfo.percentComplete, 0.4);
|
|
707
|
+
assert.equal(receivedInfo.element, timegroup);
|
|
708
|
+
}, 1000);
|
|
709
|
+
|
|
710
|
+
test("executes multiple callbacks in parallel", async () => {
|
|
711
|
+
const timegroup = renderTimegroup(
|
|
712
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
let callback1Executed = false;
|
|
716
|
+
let callback2Executed = false;
|
|
717
|
+
let callback3Executed = false;
|
|
718
|
+
|
|
719
|
+
timegroup.addFrameTask(() => {
|
|
720
|
+
callback1Executed = true;
|
|
721
|
+
});
|
|
722
|
+
timegroup.addFrameTask(() => {
|
|
723
|
+
callback2Executed = true;
|
|
724
|
+
});
|
|
725
|
+
timegroup.addFrameTask(() => {
|
|
726
|
+
callback3Executed = true;
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
await timegroup.seek(1000);
|
|
730
|
+
|
|
731
|
+
assert.equal(callback1Executed, true);
|
|
732
|
+
assert.equal(callback2Executed, true);
|
|
733
|
+
assert.equal(callback3Executed, true);
|
|
734
|
+
}, 1000);
|
|
735
|
+
|
|
736
|
+
test("async callbacks block frame pipeline", async () => {
|
|
737
|
+
const timegroup = renderTimegroup(
|
|
738
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
let asyncCallbackCompleted = false;
|
|
742
|
+
const executionOrder: string[] = [];
|
|
743
|
+
|
|
744
|
+
const asyncCallback = async () => {
|
|
745
|
+
executionOrder.push("async-start");
|
|
746
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
747
|
+
asyncCallbackCompleted = true;
|
|
748
|
+
executionOrder.push("async-end");
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
timegroup.addFrameTask(asyncCallback);
|
|
752
|
+
|
|
753
|
+
const seekPromise = timegroup.seek(1000);
|
|
754
|
+
executionOrder.push("seek-called");
|
|
755
|
+
|
|
756
|
+
await seekPromise;
|
|
757
|
+
executionOrder.push("seek-complete");
|
|
758
|
+
|
|
759
|
+
assert.equal(asyncCallbackCompleted, true);
|
|
760
|
+
assert.deepEqual(executionOrder, [
|
|
761
|
+
"seek-called",
|
|
762
|
+
"async-start",
|
|
763
|
+
"async-end",
|
|
764
|
+
"seek-complete",
|
|
765
|
+
]);
|
|
766
|
+
}, 1000);
|
|
767
|
+
|
|
768
|
+
test("cleanup function removes callback", async () => {
|
|
769
|
+
const timegroup = renderTimegroup(
|
|
770
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
let callbackExecutionCount = 0;
|
|
774
|
+
const cleanup = timegroup.addFrameTask(() => {
|
|
775
|
+
callbackExecutionCount++;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
await timegroup.seek(1000);
|
|
779
|
+
assert.equal(callbackExecutionCount, 1);
|
|
780
|
+
|
|
781
|
+
cleanup();
|
|
782
|
+
await timegroup.seek(2000);
|
|
783
|
+
assert.equal(callbackExecutionCount, 1);
|
|
784
|
+
}, 1000);
|
|
785
|
+
|
|
786
|
+
test("removeFrameTask removes callback", async () => {
|
|
787
|
+
const timegroup = renderTimegroup(
|
|
788
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
let callbackExecutionCount = 0;
|
|
792
|
+
const callback = () => {
|
|
793
|
+
callbackExecutionCount++;
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
timegroup.addFrameTask(callback);
|
|
797
|
+
await timegroup.seek(1000);
|
|
798
|
+
assert.equal(callbackExecutionCount, 1);
|
|
799
|
+
|
|
800
|
+
timegroup.removeFrameTask(callback);
|
|
801
|
+
await timegroup.seek(2000);
|
|
802
|
+
assert.equal(callbackExecutionCount, 1);
|
|
803
|
+
}, 1000);
|
|
804
|
+
|
|
805
|
+
test("addFrameTask throws error for non-function", () => {
|
|
806
|
+
const timegroup = renderTimegroup(
|
|
807
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
assert.throws(() => {
|
|
811
|
+
timegroup.addFrameTask("not a function" as any);
|
|
812
|
+
}, "Frame task callback must be a function");
|
|
813
|
+
}, 1000);
|
|
814
|
+
|
|
815
|
+
test("custom frame tasks persist after disconnect and reconnect", async () => {
|
|
816
|
+
const container = document.createElement("div");
|
|
817
|
+
document.body.appendChild(container);
|
|
818
|
+
|
|
819
|
+
const timegroup = document.createElement("ef-timegroup") as EFTimegroup;
|
|
820
|
+
timegroup.setAttribute("mode", "fixed");
|
|
821
|
+
timegroup.setAttribute("duration", "5s");
|
|
822
|
+
container.appendChild(timegroup);
|
|
823
|
+
|
|
824
|
+
let callbackWorkedAfterReconnect = false;
|
|
825
|
+
const callback = () => {
|
|
826
|
+
callbackWorkedAfterReconnect = true;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
timegroup.addFrameTask(callback);
|
|
830
|
+
|
|
831
|
+
// Disconnect and reconnect
|
|
832
|
+
container.removeChild(timegroup);
|
|
833
|
+
callbackWorkedAfterReconnect = false; // Reset after disconnect
|
|
834
|
+
container.appendChild(timegroup);
|
|
835
|
+
|
|
836
|
+
// Callback should still work after reconnect
|
|
837
|
+
await timegroup.seek(2000);
|
|
838
|
+
assert.equal(
|
|
839
|
+
callbackWorkedAfterReconnect,
|
|
840
|
+
true,
|
|
841
|
+
"Callback should still work after reconnect",
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
container.remove();
|
|
845
|
+
}, 1000);
|
|
846
|
+
|
|
847
|
+
test("sync and async callbacks execute together", async () => {
|
|
848
|
+
const timegroup = renderTimegroup(
|
|
849
|
+
html`<ef-timegroup mode="fixed" duration="5s"></ef-timegroup>`,
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
let syncExecuted = false;
|
|
853
|
+
let asyncExecuted = false;
|
|
854
|
+
|
|
855
|
+
timegroup.addFrameTask(() => {
|
|
856
|
+
syncExecuted = true;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
timegroup.addFrameTask(async () => {
|
|
860
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
861
|
+
asyncExecuted = true;
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
await timegroup.seek(1000);
|
|
865
|
+
|
|
866
|
+
assert.equal(syncExecuted, true);
|
|
867
|
+
assert.equal(asyncExecuted, true);
|
|
868
|
+
}, 1000);
|
|
869
|
+
});
|
|
672
870
|
});
|
|
@@ -34,6 +34,15 @@ declare global {
|
|
|
34
34
|
|
|
35
35
|
const log = debug("ef:elements:EFTimegroup");
|
|
36
36
|
|
|
37
|
+
// Custom frame task callback type
|
|
38
|
+
export type FrameTaskCallback = (info: {
|
|
39
|
+
ownCurrentTimeMs: number;
|
|
40
|
+
currentTimeMs: number;
|
|
41
|
+
durationMs: number;
|
|
42
|
+
percentComplete: number;
|
|
43
|
+
element: EFTimegroup;
|
|
44
|
+
}) => void | Promise<void>;
|
|
45
|
+
|
|
37
46
|
// Cache for sequence mode duration calculations to avoid O(n) recalculation
|
|
38
47
|
let sequenceDurationCache: WeakMap<EFTimegroup, number> = new WeakMap();
|
|
39
48
|
|
|
@@ -125,6 +134,7 @@ export class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
|
|
|
125
134
|
#seekInProgress = false;
|
|
126
135
|
#pendingSeekTime: number | undefined;
|
|
127
136
|
#processingPendingSeek = false;
|
|
137
|
+
#customFrameTasks: Set<FrameTaskCallback> = new Set();
|
|
128
138
|
|
|
129
139
|
/**
|
|
130
140
|
* Get the effective FPS for this timegroup.
|
|
@@ -276,6 +286,33 @@ export class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
|
|
|
276
286
|
return !this.parentTimegroup;
|
|
277
287
|
}
|
|
278
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Register a custom frame task callback that will be executed during frame rendering.
|
|
291
|
+
* The callback receives timing information and can be async or sync.
|
|
292
|
+
* Multiple callbacks can be registered and will execute in parallel.
|
|
293
|
+
*
|
|
294
|
+
* @param callback - Function to execute on each frame
|
|
295
|
+
* @returns A cleanup function that removes the callback when called
|
|
296
|
+
*/
|
|
297
|
+
addFrameTask(callback: FrameTaskCallback): () => void {
|
|
298
|
+
if (typeof callback !== "function") {
|
|
299
|
+
throw new Error("Frame task callback must be a function");
|
|
300
|
+
}
|
|
301
|
+
this.#customFrameTasks.add(callback);
|
|
302
|
+
return () => {
|
|
303
|
+
this.#customFrameTasks.delete(callback);
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Remove a previously registered custom frame task callback.
|
|
309
|
+
*
|
|
310
|
+
* @param callback - The callback function to remove
|
|
311
|
+
*/
|
|
312
|
+
removeFrameTask(callback: FrameTaskCallback): void {
|
|
313
|
+
this.#customFrameTasks.delete(callback);
|
|
314
|
+
}
|
|
315
|
+
|
|
279
316
|
saveTimeToLocalStorage(time: number) {
|
|
280
317
|
try {
|
|
281
318
|
if (this.id && this.isConnected && !Number.isNaN(time)) {
|
|
@@ -759,6 +796,26 @@ export class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
|
|
|
759
796
|
undefined,
|
|
760
797
|
async () => {
|
|
761
798
|
await this.waitForFrameTasks();
|
|
799
|
+
|
|
800
|
+
// Execute custom frame tasks
|
|
801
|
+
if (this.#customFrameTasks.size > 0) {
|
|
802
|
+
const percentComplete =
|
|
803
|
+
this.durationMs > 0 ? ownCurrentTimeMs / this.durationMs : 0;
|
|
804
|
+
const frameInfo = {
|
|
805
|
+
ownCurrentTimeMs,
|
|
806
|
+
currentTimeMs,
|
|
807
|
+
durationMs: this.durationMs,
|
|
808
|
+
percentComplete,
|
|
809
|
+
element: this,
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
await Promise.all(
|
|
813
|
+
Array.from(this.#customFrameTasks).map((callback) =>
|
|
814
|
+
Promise.resolve(callback(frameInfo)),
|
|
815
|
+
),
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
762
819
|
updateAnimations(this);
|
|
763
820
|
},
|
|
764
821
|
);
|