@editframe/elements 0.26.2-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 (135) hide show
  1. package/dist/elements/EFTimegroup.js +7 -2
  2. package/dist/elements/EFTimegroup.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/build-css.js +3 -3
  5. package/tsdown.config.ts +1 -1
  6. package/types.json +1 -1
  7. package/src/elements/ContextProxiesController.ts +0 -124
  8. package/src/elements/CrossUpdateController.ts +0 -22
  9. package/src/elements/EFAudio.browsertest.ts +0 -706
  10. package/src/elements/EFAudio.ts +0 -56
  11. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  12. package/src/elements/EFCaptions.ts +0 -823
  13. package/src/elements/EFImage.browsertest.ts +0 -120
  14. package/src/elements/EFImage.ts +0 -113
  15. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  16. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  17. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  18. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  19. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  20. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  21. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  22. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  23. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  24. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  25. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  26. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  27. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  28. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  29. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  30. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  31. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  32. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  33. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  34. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  35. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  36. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  37. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  38. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  39. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  40. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  41. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  42. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  43. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  44. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  45. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  46. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  47. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  48. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  49. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  53. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  54. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  55. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  56. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  57. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  58. package/src/elements/EFMedia.browsertest.ts +0 -872
  59. package/src/elements/EFMedia.ts +0 -341
  60. package/src/elements/EFSourceMixin.ts +0 -60
  61. package/src/elements/EFSurface.browsertest.ts +0 -151
  62. package/src/elements/EFSurface.ts +0 -142
  63. package/src/elements/EFTemporal.browsertest.ts +0 -215
  64. package/src/elements/EFTemporal.ts +0 -800
  65. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  66. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  67. package/src/elements/EFThumbnailStrip.ts +0 -906
  68. package/src/elements/EFTimegroup.browsertest.ts +0 -870
  69. package/src/elements/EFTimegroup.ts +0 -878
  70. package/src/elements/EFVideo.browsertest.ts +0 -1482
  71. package/src/elements/EFVideo.ts +0 -564
  72. package/src/elements/EFWaveform.ts +0 -547
  73. package/src/elements/FetchContext.browsertest.ts +0 -401
  74. package/src/elements/FetchMixin.ts +0 -38
  75. package/src/elements/SampleBuffer.ts +0 -94
  76. package/src/elements/TargetController.browsertest.ts +0 -230
  77. package/src/elements/TargetController.ts +0 -224
  78. package/src/elements/TimegroupController.ts +0 -26
  79. package/src/elements/durationConverter.ts +0 -35
  80. package/src/elements/parseTimeToMs.ts +0 -9
  81. package/src/elements/printTaskStatus.ts +0 -16
  82. package/src/elements/renderTemporalAudio.ts +0 -108
  83. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  84. package/src/elements/updateAnimations.ts +0 -217
  85. package/src/elements/util.ts +0 -24
  86. package/src/gui/ContextMixin.browsertest.ts +0 -860
  87. package/src/gui/ContextMixin.ts +0 -562
  88. package/src/gui/Controllable.browsertest.ts +0 -258
  89. package/src/gui/Controllable.ts +0 -41
  90. package/src/gui/EFConfiguration.ts +0 -40
  91. package/src/gui/EFControls.browsertest.ts +0 -389
  92. package/src/gui/EFControls.ts +0 -195
  93. package/src/gui/EFDial.browsertest.ts +0 -84
  94. package/src/gui/EFDial.ts +0 -172
  95. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  96. package/src/gui/EFFilmstrip.ts +0 -1349
  97. package/src/gui/EFFitScale.ts +0 -152
  98. package/src/gui/EFFocusOverlay.ts +0 -79
  99. package/src/gui/EFPause.browsertest.ts +0 -202
  100. package/src/gui/EFPause.ts +0 -73
  101. package/src/gui/EFPlay.browsertest.ts +0 -202
  102. package/src/gui/EFPlay.ts +0 -73
  103. package/src/gui/EFPreview.ts +0 -74
  104. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  105. package/src/gui/EFResizableBox.ts +0 -898
  106. package/src/gui/EFScrubber.ts +0 -151
  107. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  108. package/src/gui/EFTimeDisplay.ts +0 -55
  109. package/src/gui/EFToggleLoop.ts +0 -35
  110. package/src/gui/EFTogglePlay.ts +0 -70
  111. package/src/gui/EFWorkbench.ts +0 -115
  112. package/src/gui/PlaybackController.ts +0 -527
  113. package/src/gui/TWMixin.css +0 -6
  114. package/src/gui/TWMixin.ts +0 -61
  115. package/src/gui/TargetOrContextMixin.ts +0 -185
  116. package/src/gui/currentTimeContext.ts +0 -5
  117. package/src/gui/durationContext.ts +0 -3
  118. package/src/gui/efContext.ts +0 -6
  119. package/src/gui/fetchContext.ts +0 -5
  120. package/src/gui/focusContext.ts +0 -7
  121. package/src/gui/focusedElementContext.ts +0 -5
  122. package/src/gui/playingContext.ts +0 -5
  123. package/src/otel/BridgeSpanExporter.ts +0 -150
  124. package/src/otel/setupBrowserTracing.ts +0 -73
  125. package/src/otel/tracingHelpers.ts +0 -251
  126. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  127. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  128. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  129. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  130. package/src/transcoding/types/index.ts +0 -312
  131. package/src/transcoding/utils/MediaUtils.ts +0 -63
  132. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  133. package/src/transcoding/utils/constants.ts +0 -36
  134. package/src/utils/LRUCache.test.ts +0 -274
  135. 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
- }