@editframe/elements 0.26.3-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 (132) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-css.js +3 -3
  3. package/tsdown.config.ts +1 -1
  4. package/src/elements/ContextProxiesController.ts +0 -124
  5. package/src/elements/CrossUpdateController.ts +0 -22
  6. package/src/elements/EFAudio.browsertest.ts +0 -706
  7. package/src/elements/EFAudio.ts +0 -56
  8. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  9. package/src/elements/EFCaptions.ts +0 -823
  10. package/src/elements/EFImage.browsertest.ts +0 -120
  11. package/src/elements/EFImage.ts +0 -113
  12. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  13. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  14. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  15. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  16. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  17. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  18. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  19. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  20. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  21. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  22. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  23. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  24. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  25. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  26. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  27. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  28. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  29. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  30. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  31. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  32. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  33. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  34. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  35. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  36. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  37. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  38. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  39. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  40. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  41. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  42. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  43. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  44. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  45. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  46. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  47. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  49. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  53. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  54. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  55. package/src/elements/EFMedia.browsertest.ts +0 -872
  56. package/src/elements/EFMedia.ts +0 -341
  57. package/src/elements/EFSourceMixin.ts +0 -60
  58. package/src/elements/EFSurface.browsertest.ts +0 -151
  59. package/src/elements/EFSurface.ts +0 -142
  60. package/src/elements/EFTemporal.browsertest.ts +0 -215
  61. package/src/elements/EFTemporal.ts +0 -800
  62. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  63. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  64. package/src/elements/EFThumbnailStrip.ts +0 -906
  65. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  66. package/src/elements/EFTimegroup.ts +0 -882
  67. package/src/elements/EFVideo.browsertest.ts +0 -1482
  68. package/src/elements/EFVideo.ts +0 -564
  69. package/src/elements/EFWaveform.ts +0 -547
  70. package/src/elements/FetchContext.browsertest.ts +0 -401
  71. package/src/elements/FetchMixin.ts +0 -38
  72. package/src/elements/SampleBuffer.ts +0 -94
  73. package/src/elements/TargetController.browsertest.ts +0 -230
  74. package/src/elements/TargetController.ts +0 -224
  75. package/src/elements/TimegroupController.ts +0 -26
  76. package/src/elements/durationConverter.ts +0 -35
  77. package/src/elements/parseTimeToMs.ts +0 -9
  78. package/src/elements/printTaskStatus.ts +0 -16
  79. package/src/elements/renderTemporalAudio.ts +0 -108
  80. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  81. package/src/elements/updateAnimations.ts +0 -217
  82. package/src/elements/util.ts +0 -24
  83. package/src/gui/ContextMixin.browsertest.ts +0 -860
  84. package/src/gui/ContextMixin.ts +0 -562
  85. package/src/gui/Controllable.browsertest.ts +0 -258
  86. package/src/gui/Controllable.ts +0 -41
  87. package/src/gui/EFConfiguration.ts +0 -40
  88. package/src/gui/EFControls.browsertest.ts +0 -389
  89. package/src/gui/EFControls.ts +0 -195
  90. package/src/gui/EFDial.browsertest.ts +0 -84
  91. package/src/gui/EFDial.ts +0 -172
  92. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  93. package/src/gui/EFFilmstrip.ts +0 -1349
  94. package/src/gui/EFFitScale.ts +0 -152
  95. package/src/gui/EFFocusOverlay.ts +0 -79
  96. package/src/gui/EFPause.browsertest.ts +0 -202
  97. package/src/gui/EFPause.ts +0 -73
  98. package/src/gui/EFPlay.browsertest.ts +0 -202
  99. package/src/gui/EFPlay.ts +0 -73
  100. package/src/gui/EFPreview.ts +0 -74
  101. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  102. package/src/gui/EFResizableBox.ts +0 -898
  103. package/src/gui/EFScrubber.ts +0 -151
  104. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  105. package/src/gui/EFTimeDisplay.ts +0 -55
  106. package/src/gui/EFToggleLoop.ts +0 -35
  107. package/src/gui/EFTogglePlay.ts +0 -70
  108. package/src/gui/EFWorkbench.ts +0 -115
  109. package/src/gui/PlaybackController.ts +0 -527
  110. package/src/gui/TWMixin.css +0 -6
  111. package/src/gui/TWMixin.ts +0 -61
  112. package/src/gui/TargetOrContextMixin.ts +0 -185
  113. package/src/gui/currentTimeContext.ts +0 -5
  114. package/src/gui/durationContext.ts +0 -3
  115. package/src/gui/efContext.ts +0 -6
  116. package/src/gui/fetchContext.ts +0 -5
  117. package/src/gui/focusContext.ts +0 -7
  118. package/src/gui/focusedElementContext.ts +0 -5
  119. package/src/gui/playingContext.ts +0 -5
  120. package/src/otel/BridgeSpanExporter.ts +0 -150
  121. package/src/otel/setupBrowserTracing.ts +0 -73
  122. package/src/otel/tracingHelpers.ts +0 -251
  123. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  124. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  125. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  126. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  127. package/src/transcoding/types/index.ts +0 -312
  128. package/src/transcoding/utils/MediaUtils.ts +0 -63
  129. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  130. package/src/transcoding/utils/constants.ts +0 -36
  131. package/src/utils/LRUCache.test.ts +0 -274
  132. package/src/utils/LRUCache.ts +0 -696
@@ -1,341 +0,0 @@
1
- import { provide } from "@lit/context";
2
- import { css, LitElement, type PropertyValueMap } from "lit";
3
- import { property, state } from "lit/decorators.js";
4
- import { isContextMixin } from "../gui/ContextMixin.js";
5
- import type { ControllableInterface } from "../gui/Controllable.js";
6
- import { efContext } from "../gui/efContext.js";
7
- import { withSpan } from "../otel/tracingHelpers.js";
8
- import type { AudioSpan } from "../transcoding/types/index.ts";
9
- import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
10
- import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
11
- import { makeAudioFrequencyAnalysisTask } from "./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts";
12
- import { makeAudioInitSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts";
13
- import { makeAudioInputTask } from "./EFMedia/audioTasks/makeAudioInputTask.ts";
14
- import { makeAudioSeekTask } from "./EFMedia/audioTasks/makeAudioSeekTask.ts";
15
- import { makeAudioSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts";
16
- import { makeAudioSegmentIdTask } from "./EFMedia/audioTasks/makeAudioSegmentIdTask.ts";
17
- import { makeAudioTimeDomainAnalysisTask } from "./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts";
18
- import { fetchAudioSpanningTime } from "./EFMedia/shared/AudioSpanUtils.ts";
19
- import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.ts";
20
- import { EFSourceMixin } from "./EFSourceMixin.js";
21
- import { EFTemporal } from "./EFTemporal.js";
22
- import { FetchMixin } from "./FetchMixin.js";
23
- import { renderTemporalAudio } from "./renderTemporalAudio.js";
24
- import { EFTargetable } from "./TargetController.ts";
25
-
26
- // EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
27
- declare global {
28
- var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
29
- }
30
-
31
- const freqWeightsCache = new Map<number, Float32Array>();
32
-
33
- export class IgnorableError extends Error {}
34
-
35
- export const deepGetMediaElements = (
36
- element: Element,
37
- medias: EFMedia[] = [],
38
- ) => {
39
- for (const child of Array.from(element.children)) {
40
- if (child instanceof EFMedia) {
41
- medias.push(child);
42
- } else {
43
- deepGetMediaElements(child, medias);
44
- }
45
- }
46
- return medias;
47
- };
48
-
49
- export class EFMedia extends EFTargetable(
50
- EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
51
- assetType: "isobmff_files",
52
- }),
53
- ) {
54
- @provide({ context: efContext })
55
- get efContext(): ControllableInterface | null {
56
- return this.rootTimegroup ?? this;
57
- }
58
-
59
- // Sample buffer size configuration
60
- static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
61
- static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
62
-
63
- static get observedAttributes() {
64
- // biome-ignore lint/complexity/noThisInStatic: We need to access super
65
- const parentAttributes = super.observedAttributes || [];
66
- return [
67
- ...parentAttributes,
68
- "mute",
69
- "fft-size",
70
- "fft-decay",
71
- "fft-gain",
72
- "interpolate-frequencies",
73
- "asset-id",
74
- "audio-buffer-duration",
75
- "max-audio-buffer-fetches",
76
- "enable-audio-buffering",
77
- "sourcein",
78
- "sourceout",
79
- ];
80
- }
81
-
82
- static styles = [
83
- css`
84
- :host {
85
- display: block;
86
- position: relative;
87
- overflow: hidden;
88
- }
89
- `,
90
- ];
91
-
92
- /**
93
- * Duration in milliseconds for audio buffering ahead of current time
94
- * @domAttribute "audio-buffer-duration"
95
- */
96
- @property({ type: Number, attribute: "audio-buffer-duration" })
97
- audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding
98
-
99
- /**
100
- * Maximum number of concurrent audio segment fetches for buffering
101
- * @domAttribute "max-audio-buffer-fetches"
102
- */
103
- @property({ type: Number, attribute: "max-audio-buffer-fetches" })
104
- maxAudioBufferFetches = 2;
105
-
106
- /**
107
- * Enable/disable audio buffering system
108
- * @domAttribute "enable-audio-buffering"
109
- */
110
- @property({ type: Boolean, attribute: "enable-audio-buffering" })
111
- enableAudioBuffering = true;
112
-
113
- /**
114
- * Mute/unmute the media element
115
- * @domAttribute "mute"
116
- */
117
- @property({
118
- type: Boolean,
119
- attribute: "mute",
120
- reflect: true,
121
- })
122
- mute = false;
123
-
124
- /**
125
- * FFT size for frequency analysis
126
- * @domAttribute "fft-size"
127
- */
128
- @property({ type: Number, attribute: "fft-size", reflect: true })
129
- fftSize = 128;
130
-
131
- /**
132
- * FFT decay rate for frequency analysis
133
- * @domAttribute "fft-decay"
134
- */
135
- @property({ type: Number, attribute: "fft-decay", reflect: true })
136
- fftDecay = 8;
137
-
138
- /**
139
- * FFT gain for frequency analysis
140
- * @domAttribute "fft-gain"
141
- */
142
- @property({ type: Number, attribute: "fft-gain", reflect: true })
143
- fftGain = 3.0;
144
-
145
- /**
146
- * Enable/disable frequency interpolation
147
- * @domAttribute "interpolate-frequencies"
148
- */
149
- @property({
150
- type: Boolean,
151
- attribute: "interpolate-frequencies",
152
- reflect: true,
153
- })
154
- interpolateFrequencies = false;
155
-
156
- // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
157
- get FREQ_WEIGHTS() {
158
- if (freqWeightsCache.has(this.fftSize)) {
159
- // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above
160
- return freqWeightsCache.get(this.fftSize)!;
161
- }
162
-
163
- const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
164
- const frequency = (i * 48000) / this.fftSize;
165
- if (frequency < 60) return 0.3;
166
- if (frequency < 250) return 0.4;
167
- if (frequency < 500) return 0.6;
168
- if (frequency < 2000) return 0.8;
169
- if (frequency < 4000) return 1.2;
170
- if (frequency < 8000) return 1.6;
171
- return 2.0;
172
- });
173
-
174
- freqWeightsCache.set(this.fftSize, weights);
175
- return weights;
176
- }
177
-
178
- // Helper getter for backwards compatibility
179
- get shouldInterpolateFrequencies() {
180
- return this.interpolateFrequencies;
181
- }
182
-
183
- get urlGenerator() {
184
- return new UrlGenerator(() => this.apiHost ?? "");
185
- }
186
-
187
- mediaEngineTask = makeMediaEngineTask(this);
188
-
189
- audioSegmentIdTask = makeAudioSegmentIdTask(this);
190
- audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);
191
- audioSegmentFetchTask = makeAudioSegmentFetchTask(this);
192
- audioInputTask = makeAudioInputTask(this);
193
- audioSeekTask = makeAudioSeekTask(this);
194
-
195
- audioBufferTask = makeAudioBufferTask(this);
196
-
197
- // Audio analysis tasks for frequency and time domain analysis
198
- byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);
199
- frequencyDataTask = makeAudioFrequencyAnalysisTask(this);
200
-
201
- /**
202
- * The unique identifier for the media asset.
203
- * This property can be set programmatically or via the "asset-id" attribute.
204
- * @domAttribute "asset-id"
205
- */
206
- @property({ type: String, attribute: "asset-id", reflect: true })
207
- assetId: string | null = null;
208
-
209
- get intrinsicDurationMs() {
210
- return this.mediaEngineTask.value?.durationMs ?? 0;
211
- }
212
-
213
- protected updated(
214
- changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
215
- ): void {
216
- super.updated(changedProperties);
217
-
218
- // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property
219
- const newCurrentSourceTimeMs = this.currentSourceTimeMs;
220
- if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {
221
- this.executeSeek(newCurrentSourceTimeMs);
222
- }
223
-
224
- if (changedProperties.has("ownCurrentTimeMs")) {
225
- this.executeSeek(this.currentSourceTimeMs);
226
- }
227
-
228
- // Check if trim/source properties changed that affect duration
229
- const durationAffectingProps = [
230
- "_trimStartMs",
231
- "_trimEndMs",
232
- "_sourceInMs",
233
- "_sourceOutMs",
234
- ];
235
-
236
- const hasDurationChange = durationAffectingProps.some((prop) =>
237
- changedProperties.has(prop),
238
- );
239
-
240
- if (hasDurationChange) {
241
- // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)
242
- if (this.parentTimegroup) {
243
- this.parentTimegroup.requestUpdate("durationMs");
244
- this.parentTimegroup.requestUpdate("currentTime");
245
-
246
- // Also find and directly notify any context provider (ContextMixin)
247
- let parent = this.parentNode;
248
- while (parent) {
249
- if (isContextMixin(parent)) {
250
- parent.dispatchEvent(
251
- new CustomEvent("child-duration-changed", {
252
- detail: { source: this },
253
- }),
254
- );
255
- break;
256
- }
257
- parent = parent.parentNode;
258
- }
259
- }
260
- }
261
- }
262
-
263
- get hasOwnDuration() {
264
- return true;
265
- }
266
-
267
- @state()
268
- private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading
269
-
270
- get desiredSeekTimeMs() {
271
- return this._desiredSeekTimeMs;
272
- }
273
-
274
- set desiredSeekTimeMs(value: number) {
275
- if (this._desiredSeekTimeMs !== value) {
276
- this._desiredSeekTimeMs = value;
277
- }
278
- }
279
-
280
- protected async executeSeek(seekToMs: number) {
281
- // The seekToMs parameter should be the timeline-relative media time
282
- // calculated from currentSourceTimeMs which includes timeline positioning
283
- this.desiredSeekTimeMs = seekToMs;
284
- }
285
-
286
- /**
287
- * Main integration method for EFTimegroup audio playback
288
- * Now powered by clean, testable utility functions
289
- * Returns undefined if no audio rendition is available
290
- */
291
- async fetchAudioSpanningTime(
292
- fromMs: number,
293
- toMs: number,
294
- signal: AbortSignal = new AbortController().signal,
295
- ): Promise<AudioSpan | undefined> {
296
- return withSpan(
297
- "media.fetchAudioSpanningTime",
298
- {
299
- elementId: this.id || "unknown",
300
- tagName: this.tagName.toLowerCase(),
301
- fromMs,
302
- toMs,
303
- durationMs: toMs - fromMs,
304
- src: this.src || "none",
305
- },
306
- undefined,
307
- async () => {
308
- return fetchAudioSpanningTime(this, fromMs, toMs, signal);
309
- },
310
- );
311
- }
312
-
313
- /**
314
- * Wait for media engine to load and determine duration
315
- * Ensures media is ready for playback
316
- */
317
- async waitForMediaDurations(): Promise<void> {
318
- if (this.mediaEngineTask.value) {
319
- return;
320
- }
321
- await this.mediaEngineTask.run();
322
- }
323
-
324
- /**
325
- * Returns media elements for playback audio rendering
326
- * For standalone media, returns [this]; for timegroups, returns all descendants
327
- * Used by PlaybackController for audio-driven playback
328
- */
329
- getMediaElements(): EFMedia[] {
330
- return [this];
331
- }
332
-
333
- /**
334
- * Render audio buffer for playback
335
- * Called by PlaybackController during live playback
336
- * Delegates to shared renderTemporalAudio utility for consistent behavior
337
- */
338
- async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {
339
- return renderTemporalAudio(this, fromMs, toMs);
340
- }
341
- }
@@ -1,60 +0,0 @@
1
- import { Task } from "@lit/task";
2
- import type { LitElement } from "lit";
3
- import { property } from "lit/decorators/property.js";
4
-
5
- export declare class EFSourceMixinInterface {
6
- apiHost?: string;
7
- productionSrc(): string;
8
- src: string;
9
- }
10
-
11
- interface EFSourceMixinOptions {
12
- assetType: string;
13
- }
14
- type Constructor<T = {}> = new (...args: any[]) => T;
15
- export function EFSourceMixin<T extends Constructor<LitElement>>(
16
- superClass: T,
17
- options: EFSourceMixinOptions,
18
- ) {
19
- class EFSourceElement extends superClass {
20
- get apiHost() {
21
- const apiHost =
22
- this.closest("ef-configuration")?.apiHost ??
23
- this.closest("ef-workbench")?.apiHost ??
24
- this.closest("ef-preview")?.apiHost;
25
-
26
- return apiHost || "https://editframe.dev";
27
- }
28
-
29
- @property({ type: String })
30
- src = "";
31
-
32
- productionSrc() {
33
- if (!this.md5SumLoader.value) {
34
- throw new Error(
35
- `MD5 sum not available for ${this}. Cannot generate production URL`,
36
- );
37
- }
38
-
39
- if (!this.apiHost) {
40
- throw new Error(
41
- `apiHost not available for ${this}. Cannot generate production URL`,
42
- );
43
- }
44
-
45
- return `${this.apiHost}/api/v1/${options.assetType}/${this.md5SumLoader.value}`;
46
- }
47
-
48
- md5SumLoader = new Task(this, {
49
- autoRun: false,
50
- args: () => [this.src] as const,
51
- task: async ([src], { signal }) => {
52
- const md5Path = `/@ef-asset/${src}`;
53
- const response = await fetch(md5Path, { method: "HEAD", signal });
54
- return response.headers.get("etag") ?? undefined;
55
- },
56
- });
57
- }
58
-
59
- return EFSourceElement as Constructor<EFSourceMixinInterface> & T;
60
- }
@@ -1,151 +0,0 @@
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" signing-url="">
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 tg = container.querySelector("#tg") as EFTimegroup;
42
- await tg.updateComplete;
43
- await use(tg);
44
- container.remove();
45
- },
46
- video: async ({ timegroup }, use) => {
47
- const video = timegroup.querySelector("#vid") as EFVideo;
48
- await video.updateComplete;
49
- await use(video);
50
- },
51
- surface: async ({ timegroup }, use) => {
52
- const surface = timegroup.querySelector("#surf") as unknown as EFSurface;
53
- await surface.updateComplete;
54
- await use(surface);
55
- },
56
- });
57
-
58
- describe("EFSurface", () => {
59
- surfaceTest("defines and renders a canvas", async ({ expect }) => {
60
- const el = document.createElement("ef-surface");
61
- document.body.appendChild(el);
62
- await (el as any).updateComplete;
63
- const canvas = el.shadowRoot?.querySelector("canvas");
64
- expect(canvas).toBeTruthy();
65
- expect((canvas as HTMLCanvasElement).tagName).toBe("CANVAS");
66
- el.remove();
67
- });
68
-
69
- surfaceTest(
70
- "mirrors video canvas after a seek via EFTimegroup",
71
- async ({ timegroup, video, surface, expect }) => {
72
- // Ensure media engine initialized
73
- await video.mediaEngineTask.run();
74
-
75
- // Seek to a known time through timegroup (triggers frame tasks)
76
- timegroup.currentTimeMs = 3000;
77
- await timegroup.seekTask.taskComplete;
78
-
79
- // After scheduling, surface should have mirrored pixel dimensions
80
- const videoCanvas = (video as any).canvasElement as
81
- | HTMLCanvasElement
82
- | undefined;
83
- const surfaceCanvas =
84
- (surface.shadowRoot?.querySelector("canvas") as HTMLCanvasElement) ??
85
- undefined;
86
-
87
- expect(videoCanvas).toBeTruthy();
88
- expect(surfaceCanvas).toBeTruthy();
89
- expect(videoCanvas!.width).toBeGreaterThan(0);
90
- expect(videoCanvas!.height).toBeGreaterThan(0);
91
-
92
- // Surface copies pixel dimensions
93
- expect(surfaceCanvas!.width).toBe(videoCanvas!.width);
94
- expect(surfaceCanvas!.height).toBe(videoCanvas!.height);
95
- },
96
- );
97
-
98
- surfaceTest(
99
- "supports multiple surfaces mirroring the same source",
100
- async ({ expect }) => {
101
- const container = document.createElement("div");
102
- render(
103
- html`
104
- <ef-configuration api-host="http://localhost:63315" signing-url="">
105
- <ef-preview>
106
- <ef-timegroup mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
107
- <ef-video id="v" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
108
- <ef-surface id="s1" target="v" style="position: absolute; inset: 0;"></ef-surface>
109
- <ef-surface id="s2" target="v" style="position: absolute; inset: 0;"></ef-surface>
110
- </ef-timegroup>
111
- </ef-preview>
112
- </ef-configuration>
113
- `,
114
- container,
115
- );
116
- document.body.appendChild(container);
117
- const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
118
- const video = container.querySelector("ef-video") as EFVideo;
119
- const s1 = container.querySelector("#s1") as unknown as EFSurface;
120
- const s2 = container.querySelector("#s2") as unknown as EFSurface;
121
- await timegroup.updateComplete;
122
- await video.mediaEngineTask.run();
123
-
124
- timegroup.currentTimeMs = 1000;
125
- await timegroup.seekTask.taskComplete;
126
-
127
- const vCanvas = (video as any).canvasElement as HTMLCanvasElement;
128
- const c1 = s1.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
129
- const c2 = s2.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
130
-
131
- expect(vCanvas.width).toBeGreaterThan(0);
132
- expect(c1.width).toBe(vCanvas.width);
133
- expect(c2.width).toBe(vCanvas.width);
134
- expect(c1.height).toBe(vCanvas.height);
135
- expect(c2.height).toBe(vCanvas.height);
136
-
137
- container.remove();
138
- },
139
- );
140
-
141
- surfaceTest(
142
- "handles missing video gracefully (no throw)",
143
- async ({ expect }) => {
144
- const el = document.createElement("ef-surface") as any;
145
- document.body.appendChild(el);
146
- await el.updateComplete;
147
- await expect(el.frameTask.run()).resolves.toBeUndefined();
148
- el.remove();
149
- },
150
- );
151
- });
@@ -1,142 +0,0 @@
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
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for side effects
29
- #targetController: TargetController = new TargetController(this);
30
-
31
- @state()
32
- targetElement: ContextMixinInterface | null = null;
33
-
34
- @property({ type: String })
35
- target = "";
36
-
37
- render() {
38
- return html`<canvas ${ref(this.canvasRef)}></canvas>`;
39
- }
40
-
41
- // Provide minimal temporal-like properties so EFTimegroup can schedule us
42
- get rootTimegroup(): any {
43
- // Prefer the target element's root timegroup if available
44
- const target: any = this.targetElement;
45
- if (target && "rootTimegroup" in target) {
46
- return target.rootTimegroup;
47
- }
48
- // Fallback: nearest containing timegroup if any
49
- let root: any = this.closest("ef-timegroup");
50
- while (root?.parentTimegroup) {
51
- root = root.parentTimegroup;
52
- }
53
- return root;
54
- }
55
-
56
- get currentTimeMs(): number {
57
- return this.rootTimegroup?.currentTimeMs ?? 0;
58
- }
59
-
60
- get durationMs(): number {
61
- return this.rootTimegroup?.durationMs ?? 0;
62
- }
63
-
64
- get startTimeMs(): number {
65
- return this.rootTimegroup?.startTimeMs ?? 0;
66
- }
67
-
68
- get endTimeMs(): number {
69
- return this.startTimeMs + this.durationMs;
70
- }
71
-
72
- /**
73
- * Minimal integration with EFTimegroup's frame scheduling:
74
- * - Waits for the target video element's frameTask to complete (ensuring it painted)
75
- * - Copies the target's canvas into this element's canvas
76
- */
77
- frameTask = new Task(this, {
78
- autoRun: false,
79
- args: () => [this.targetElement] as const,
80
- task: async ([target]) => {
81
- if (!target) return;
82
-
83
- // Ensure the target has painted its frame for this tick
84
- try {
85
- const maybeTask = (target as any).frameTask;
86
- if (maybeTask && typeof maybeTask.run === "function") {
87
- // Run (idempotent) and then wait for completion
88
- maybeTask.run();
89
- await maybeTask.taskComplete;
90
- }
91
- } catch (_err) {
92
- // Best-effort; continue to attempt copy
93
- }
94
-
95
- this.copyFromTarget(target);
96
- },
97
- });
98
-
99
- protected updated(): void {
100
- if (this.targetElement) {
101
- this.copyFromTarget(this.targetElement);
102
- }
103
- }
104
-
105
- // Target resolution is handled by TargetController. No implicit discovery.
106
-
107
- private getSourceCanvas(from: Element): HTMLCanvasElement | null {
108
- const anyEl = from as any;
109
- if ("canvasElement" in anyEl) {
110
- return anyEl.canvasElement ?? null;
111
- }
112
- const sr = (from as HTMLElement).shadowRoot;
113
- if (sr) {
114
- const c = sr.querySelector("canvas");
115
- return (c as HTMLCanvasElement) ?? null;
116
- }
117
- return null;
118
- }
119
-
120
- private copyFromTarget(target: Element) {
121
- const dst = this.canvasRef.value;
122
- const src = this.getSourceCanvas(target);
123
- if (!dst || !src) return;
124
- if (!src.width || !src.height) return;
125
-
126
- // Match source pixel size for a faithful mirror; layout scaling is handled by CSS
127
- if (dst.width !== src.width || dst.height !== src.height) {
128
- dst.width = src.width;
129
- dst.height = src.height;
130
- }
131
-
132
- const ctx = dst.getContext("2d");
133
- if (!ctx) return;
134
- ctx.drawImage(src, 0, 0, dst.width, dst.height);
135
- }
136
- }
137
-
138
- declare global {
139
- interface HTMLElementTagNameMap {
140
- "ef-surface": EFSurface;
141
- }
142
- }