@editframe/elements 0.19.4-beta.0 → 0.20.1-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 +15 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +2 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +3 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +6 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +2 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +2 -2
- 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/tasks/makeMediaEngineTask.js +1 -1
- package/dist/elements/EFMedia.d.ts +2 -2
- 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 +6 -1
- package/dist/elements/EFTimegroup.js +53 -11
- 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 +124 -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 +34 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +6 -5
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +5 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +8 -5
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +5 -5
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +11 -12
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +7 -4
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -0
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +2 -2
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +2 -2
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +2 -2
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -1
- 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 -16
- package/src/elements/updateAnimations.browsertest.ts +333 -11
- 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/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +1 -1
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +1 -1
- package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +1 -1
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +1 -1
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +1 -1
- package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +1 -1
- package/types.json +1 -1
- package/dist/transcoding/cache/CacheManager.d.ts +0 -73
- package/src/transcoding/cache/CacheManager.ts +0 -208
|
@@ -21,7 +21,7 @@ export const getLatestMediaEngine = async (
|
|
|
21
21
|
export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
|
|
22
22
|
const videoRendition = mediaEngine.videoRendition;
|
|
23
23
|
if (!videoRendition) {
|
|
24
|
-
throw new Error("
|
|
24
|
+
throw new Error("No video track available in source");
|
|
25
25
|
}
|
|
26
26
|
return videoRendition;
|
|
27
27
|
};
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { css, LitElement, type PropertyValueMap } from "lit";
|
|
2
2
|
import { property, state } from "lit/decorators.js";
|
|
3
|
-
|
|
3
|
+
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
4
4
|
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
5
5
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
6
6
|
import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
|
|
@@ -64,6 +64,8 @@ export class EFMedia extends EFTargetable(
|
|
|
64
64
|
"audio-buffer-duration",
|
|
65
65
|
"max-audio-buffer-fetches",
|
|
66
66
|
"enable-audio-buffering",
|
|
67
|
+
"sourcein",
|
|
68
|
+
"sourceout",
|
|
67
69
|
];
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -212,6 +214,41 @@ export class EFMedia extends EFTargetable(
|
|
|
212
214
|
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
213
215
|
this.executeSeek(this.currentSourceTimeMs);
|
|
214
216
|
}
|
|
217
|
+
|
|
218
|
+
// Check if trim/source properties changed that affect duration
|
|
219
|
+
const durationAffectingProps = [
|
|
220
|
+
"_trimStartMs",
|
|
221
|
+
"_trimEndMs",
|
|
222
|
+
"_sourceInMs",
|
|
223
|
+
"_sourceOutMs",
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const hasDurationChange = durationAffectingProps.some((prop) =>
|
|
227
|
+
changedProperties.has(prop),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (hasDurationChange) {
|
|
231
|
+
// Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)
|
|
232
|
+
if (this.parentTimegroup) {
|
|
233
|
+
this.parentTimegroup.requestUpdate("durationMs");
|
|
234
|
+
this.parentTimegroup.requestUpdate("currentTime");
|
|
235
|
+
|
|
236
|
+
// Also find and directly notify any context provider (ContextMixin)
|
|
237
|
+
let parent = this.parentNode;
|
|
238
|
+
while (parent) {
|
|
239
|
+
if (isContextMixin(parent)) {
|
|
240
|
+
parent.dispatchEvent(
|
|
241
|
+
new CustomEvent("child-duration-changed", {
|
|
242
|
+
detail: { source: this },
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
parent = parent.parentNode;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
215
252
|
// if (
|
|
216
253
|
// changedProperties.has("currentTime") ||
|
|
217
254
|
// changedProperties.has("ownCurrentTimeMs")
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { html, render } from "lit";
|
|
2
|
+
import { beforeEach, describe } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { test as baseTest } from "../../test/useMSW.js";
|
|
5
|
+
|
|
6
|
+
import "./EFVideo.js";
|
|
7
|
+
import "./EFTimegroup.js";
|
|
8
|
+
import "../gui/EFPreview.js";
|
|
9
|
+
import "../gui/EFWorkbench.js";
|
|
10
|
+
import "./EFSurface.js";
|
|
11
|
+
|
|
12
|
+
import type { EFSurface } from "./EFSurface.js";
|
|
13
|
+
import type { EFTimegroup } from "./EFTimegroup.js";
|
|
14
|
+
import type { EFVideo } from "./EFVideo.js";
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
localStorage.clear();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const surfaceTest = baseTest.extend<{
|
|
21
|
+
timegroup: EFTimegroup;
|
|
22
|
+
video: EFVideo;
|
|
23
|
+
surface: EFSurface;
|
|
24
|
+
}>({
|
|
25
|
+
timegroup: async ({}, use) => {
|
|
26
|
+
const container = document.createElement("div");
|
|
27
|
+
render(
|
|
28
|
+
html`
|
|
29
|
+
<ef-configuration api-host="http://localhost:63315">
|
|
30
|
+
<ef-preview>
|
|
31
|
+
<ef-timegroup id="tg" mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
32
|
+
<ef-video id="vid" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
33
|
+
<ef-surface id="surf" target="vid" style="position: absolute; inset: 0;"></ef-surface>
|
|
34
|
+
</ef-timegroup>
|
|
35
|
+
</ef-preview>
|
|
36
|
+
</ef-configuration>
|
|
37
|
+
`,
|
|
38
|
+
container,
|
|
39
|
+
);
|
|
40
|
+
document.body.appendChild(container);
|
|
41
|
+
const configuration = container.querySelector("ef-configuration") as any;
|
|
42
|
+
configuration.signingURL = "";
|
|
43
|
+
const tg = container.querySelector("#tg") as EFTimegroup;
|
|
44
|
+
await tg.updateComplete;
|
|
45
|
+
await use(tg);
|
|
46
|
+
container.remove();
|
|
47
|
+
},
|
|
48
|
+
video: async ({ timegroup }, use) => {
|
|
49
|
+
const video = timegroup.querySelector("#vid") as EFVideo;
|
|
50
|
+
await video.updateComplete;
|
|
51
|
+
await use(video);
|
|
52
|
+
},
|
|
53
|
+
surface: async ({ timegroup }, use) => {
|
|
54
|
+
const surface = timegroup.querySelector("#surf") as unknown as EFSurface;
|
|
55
|
+
await surface.updateComplete;
|
|
56
|
+
await use(surface);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("EFSurface", () => {
|
|
61
|
+
surfaceTest("defines and renders a canvas", async ({ expect }) => {
|
|
62
|
+
const el = document.createElement("ef-surface");
|
|
63
|
+
document.body.appendChild(el);
|
|
64
|
+
await (el as any).updateComplete;
|
|
65
|
+
const canvas = el.shadowRoot?.querySelector("canvas");
|
|
66
|
+
expect(canvas).toBeTruthy();
|
|
67
|
+
expect((canvas as HTMLCanvasElement).tagName).toBe("CANVAS");
|
|
68
|
+
el.remove();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
surfaceTest(
|
|
72
|
+
"mirrors video canvas after a seek via EFTimegroup",
|
|
73
|
+
async ({ timegroup, video, surface, expect }) => {
|
|
74
|
+
// Ensure media engine initialized
|
|
75
|
+
await video.mediaEngineTask.run();
|
|
76
|
+
|
|
77
|
+
// Seek to a known time through timegroup (triggers frame tasks)
|
|
78
|
+
timegroup.currentTimeMs = 3000;
|
|
79
|
+
await timegroup.seekTask.taskComplete;
|
|
80
|
+
|
|
81
|
+
// After scheduling, surface should have mirrored pixel dimensions
|
|
82
|
+
const videoCanvas = (video as any).canvasElement as
|
|
83
|
+
| HTMLCanvasElement
|
|
84
|
+
| undefined;
|
|
85
|
+
const surfaceCanvas =
|
|
86
|
+
(surface.shadowRoot?.querySelector("canvas") as HTMLCanvasElement) ??
|
|
87
|
+
undefined;
|
|
88
|
+
|
|
89
|
+
expect(videoCanvas).toBeTruthy();
|
|
90
|
+
expect(surfaceCanvas).toBeTruthy();
|
|
91
|
+
expect(videoCanvas!.width).toBeGreaterThan(0);
|
|
92
|
+
expect(videoCanvas!.height).toBeGreaterThan(0);
|
|
93
|
+
|
|
94
|
+
// Surface copies pixel dimensions
|
|
95
|
+
expect(surfaceCanvas!.width).toBe(videoCanvas!.width);
|
|
96
|
+
expect(surfaceCanvas!.height).toBe(videoCanvas!.height);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
surfaceTest(
|
|
101
|
+
"supports multiple surfaces mirroring the same source",
|
|
102
|
+
async ({ expect }) => {
|
|
103
|
+
const container = document.createElement("div");
|
|
104
|
+
render(
|
|
105
|
+
html`
|
|
106
|
+
<ef-configuration api-host="http://localhost:63315">
|
|
107
|
+
<ef-preview>
|
|
108
|
+
<ef-timegroup mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
109
|
+
<ef-video id="v" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
110
|
+
<ef-surface id="s1" target="v" style="position: absolute; inset: 0;"></ef-surface>
|
|
111
|
+
<ef-surface id="s2" target="v" style="position: absolute; inset: 0;"></ef-surface>
|
|
112
|
+
</ef-timegroup>
|
|
113
|
+
</ef-preview>
|
|
114
|
+
</ef-configuration>
|
|
115
|
+
`,
|
|
116
|
+
container,
|
|
117
|
+
);
|
|
118
|
+
document.body.appendChild(container);
|
|
119
|
+
const configuration = container.querySelector("ef-configuration") as any;
|
|
120
|
+
configuration.signingURL = "";
|
|
121
|
+
const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
|
|
122
|
+
const video = container.querySelector("ef-video") as EFVideo;
|
|
123
|
+
const s1 = container.querySelector("#s1") as unknown as EFSurface;
|
|
124
|
+
const s2 = container.querySelector("#s2") as unknown as EFSurface;
|
|
125
|
+
await timegroup.updateComplete;
|
|
126
|
+
await video.mediaEngineTask.run();
|
|
127
|
+
|
|
128
|
+
timegroup.currentTimeMs = 1000;
|
|
129
|
+
await timegroup.seekTask.taskComplete;
|
|
130
|
+
|
|
131
|
+
const vCanvas = (video as any).canvasElement as HTMLCanvasElement;
|
|
132
|
+
const c1 = s1.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
|
|
133
|
+
const c2 = s2.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
|
|
134
|
+
|
|
135
|
+
expect(vCanvas.width).toBeGreaterThan(0);
|
|
136
|
+
expect(c1.width).toBe(vCanvas.width);
|
|
137
|
+
expect(c2.width).toBe(vCanvas.width);
|
|
138
|
+
expect(c1.height).toBe(vCanvas.height);
|
|
139
|
+
expect(c2.height).toBe(vCanvas.height);
|
|
140
|
+
|
|
141
|
+
container.remove();
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
surfaceTest(
|
|
146
|
+
"handles missing video gracefully (no throw)",
|
|
147
|
+
async ({ expect }) => {
|
|
148
|
+
const el = document.createElement("ef-surface") as any;
|
|
149
|
+
document.body.appendChild(el);
|
|
150
|
+
await el.updateComplete;
|
|
151
|
+
await expect(el.frameTask.run()).resolves.toBeUndefined();
|
|
152
|
+
el.remove();
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Task } from "@lit/task";
|
|
2
|
+
import { css, html, LitElement } from "lit";
|
|
3
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
4
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
+
import type { ContextMixinInterface } from "../gui/ContextMixin.ts";
|
|
6
|
+
import { TargetController } from "./TargetController.ts";
|
|
7
|
+
|
|
8
|
+
@customElement("ef-surface")
|
|
9
|
+
export class EFSurface extends LitElement {
|
|
10
|
+
static styles = [
|
|
11
|
+
css`
|
|
12
|
+
:host {
|
|
13
|
+
display: block;
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
canvas {
|
|
17
|
+
all: inherit;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
display: block;
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
canvasRef = createRef<HTMLCanvasElement>();
|
|
26
|
+
|
|
27
|
+
// @ts-expect-error controller is intentionally not referenced directly
|
|
28
|
+
#targetController: TargetController = new TargetController(this);
|
|
29
|
+
|
|
30
|
+
@state()
|
|
31
|
+
targetElement: ContextMixinInterface | null = null;
|
|
32
|
+
|
|
33
|
+
@property({ type: String })
|
|
34
|
+
target = "";
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Provide minimal temporal-like properties so EFTimegroup can schedule us
|
|
41
|
+
get rootTimegroup(): any {
|
|
42
|
+
// Prefer the target element's root timegroup if available
|
|
43
|
+
const target: any = this.targetElement;
|
|
44
|
+
if (target && "rootTimegroup" in target) {
|
|
45
|
+
return target.rootTimegroup;
|
|
46
|
+
}
|
|
47
|
+
// Fallback: nearest containing timegroup if any
|
|
48
|
+
let root: any = this.closest("ef-timegroup");
|
|
49
|
+
while (root?.parentTimegroup) {
|
|
50
|
+
root = root.parentTimegroup;
|
|
51
|
+
}
|
|
52
|
+
return root;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get currentTimeMs(): number {
|
|
56
|
+
return this.rootTimegroup?.currentTimeMs ?? 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get durationMs(): number {
|
|
60
|
+
return this.rootTimegroup?.durationMs ?? 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get startTimeMs(): number {
|
|
64
|
+
return this.rootTimegroup?.startTimeMs ?? 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get endTimeMs(): number {
|
|
68
|
+
return this.startTimeMs + this.durationMs;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Minimal integration with EFTimegroup's frame scheduling:
|
|
73
|
+
* - Waits for the target video element's frameTask to complete (ensuring it painted)
|
|
74
|
+
* - Copies the target's canvas into this element's canvas
|
|
75
|
+
*/
|
|
76
|
+
frameTask = new Task(this, {
|
|
77
|
+
autoRun: false,
|
|
78
|
+
args: () => [this.targetElement] as const,
|
|
79
|
+
task: async ([target]) => {
|
|
80
|
+
if (!target) return;
|
|
81
|
+
|
|
82
|
+
// Ensure the target has painted its frame for this tick
|
|
83
|
+
try {
|
|
84
|
+
const maybeTask = (target as any).frameTask;
|
|
85
|
+
if (maybeTask && typeof maybeTask.run === "function") {
|
|
86
|
+
// Run (idempotent) and then wait for completion
|
|
87
|
+
maybeTask.run();
|
|
88
|
+
await maybeTask.taskComplete;
|
|
89
|
+
}
|
|
90
|
+
} catch (_err) {
|
|
91
|
+
// Best-effort; continue to attempt copy
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.copyFromTarget(target);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
protected updated(): void {
|
|
99
|
+
if (this.targetElement) {
|
|
100
|
+
this.copyFromTarget(this.targetElement);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Target resolution is handled by TargetController. No implicit discovery.
|
|
105
|
+
|
|
106
|
+
private getSourceCanvas(from: Element): HTMLCanvasElement | null {
|
|
107
|
+
const anyEl = from as any;
|
|
108
|
+
if ("canvasElement" in anyEl) {
|
|
109
|
+
return anyEl.canvasElement ?? null;
|
|
110
|
+
}
|
|
111
|
+
const sr = (from as HTMLElement).shadowRoot;
|
|
112
|
+
if (sr) {
|
|
113
|
+
const c = sr.querySelector("canvas");
|
|
114
|
+
return (c as HTMLCanvasElement) ?? null;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private copyFromTarget(target: Element) {
|
|
120
|
+
const dst = this.canvasRef.value;
|
|
121
|
+
const src = this.getSourceCanvas(target);
|
|
122
|
+
if (!dst || !src) return;
|
|
123
|
+
if (!src.width || !src.height) return;
|
|
124
|
+
|
|
125
|
+
// Match source pixel size for a faithful mirror; layout scaling is handled by CSS
|
|
126
|
+
if (dst.width !== src.width || dst.height !== src.height) {
|
|
127
|
+
dst.width = src.width;
|
|
128
|
+
dst.height = src.height;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const ctx = dst.getContext("2d");
|
|
132
|
+
if (!ctx) return;
|
|
133
|
+
ctx.drawImage(src, 0, 0, dst.width, dst.height);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare global {
|
|
138
|
+
interface HTMLElementTagNameMap {
|
|
139
|
+
"ef-surface": EFSurface;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -469,7 +469,7 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
get hasOwnDuration() {
|
|
472
|
-
return
|
|
472
|
+
return this.intrinsicDurationMs !== undefined || this.hasExplicitDuration;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
get intrinsicDurationMs() {
|
|
@@ -477,15 +477,21 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
get durationMs() {
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
// Get the base duration - either intrinsic or explicit
|
|
481
|
+
const baseDurationMs =
|
|
482
|
+
this.intrinsicDurationMs ??
|
|
483
|
+
this._durationMs ??
|
|
484
|
+
this.parentTimegroup?.durationMs ??
|
|
485
|
+
0;
|
|
486
|
+
|
|
487
|
+
if (baseDurationMs === 0) {
|
|
488
|
+
return 0;
|
|
482
489
|
}
|
|
483
490
|
|
|
491
|
+
// Apply trimming logic to any duration source
|
|
484
492
|
if (this.trimStartMs || this.trimEndMs) {
|
|
485
493
|
const trimmedDurationMs =
|
|
486
|
-
this.
|
|
487
|
-
(this.trimStartMs ?? 0) -
|
|
488
|
-
(this.trimEndMs ?? 0);
|
|
494
|
+
baseDurationMs - (this.trimStartMs ?? 0) - (this.trimEndMs ?? 0);
|
|
489
495
|
if (trimmedDurationMs < 0) {
|
|
490
496
|
return 0;
|
|
491
497
|
}
|
|
@@ -494,14 +500,14 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
494
500
|
|
|
495
501
|
if (this.sourceInMs || this.sourceOutMs) {
|
|
496
502
|
const sourceInMs = this.sourceInMs ?? 0;
|
|
497
|
-
const sourceOutMs = this.sourceOutMs ??
|
|
503
|
+
const sourceOutMs = this.sourceOutMs ?? baseDurationMs;
|
|
498
504
|
if (sourceInMs >= sourceOutMs) {
|
|
499
505
|
return 0;
|
|
500
506
|
}
|
|
501
507
|
return sourceOutMs - sourceInMs;
|
|
502
508
|
}
|
|
503
509
|
|
|
504
|
-
return
|
|
510
|
+
return baseDurationMs;
|
|
505
511
|
}
|
|
506
512
|
|
|
507
513
|
get sourceStartMs() {
|