@editframe/elements 0.19.2-beta.0 → 0.20.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/elements/ContextProxiesController.d.ts +40 -0
  2. package/dist/elements/ContextProxiesController.js +69 -0
  3. package/dist/elements/EFCaptions.d.ts +45 -6
  4. package/dist/elements/EFCaptions.js +220 -26
  5. package/dist/elements/EFImage.js +4 -1
  6. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +2 -1
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.js +9 -0
  8. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.js +11 -0
  10. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +13 -1
  11. package/dist/elements/EFMedia/BaseMediaEngine.js +9 -0
  12. package/dist/elements/EFMedia/JitMediaEngine.d.ts +7 -1
  13. package/dist/elements/EFMedia/JitMediaEngine.js +24 -0
  14. package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +39 -0
  15. package/dist/elements/EFMedia/shared/GlobalInputCache.js +57 -0
  16. package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +27 -0
  17. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +106 -0
  18. package/dist/elements/EFMedia.js +25 -1
  19. package/dist/elements/EFSurface.browsertest.d.ts +0 -0
  20. package/dist/elements/EFSurface.d.ts +30 -0
  21. package/dist/elements/EFSurface.js +96 -0
  22. package/dist/elements/EFTemporal.js +7 -6
  23. package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
  24. package/dist/elements/EFThumbnailStrip.d.ts +86 -0
  25. package/dist/elements/EFThumbnailStrip.js +490 -0
  26. package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
  27. package/dist/elements/EFTimegroup.d.ts +7 -7
  28. package/dist/elements/EFTimegroup.js +59 -16
  29. package/dist/elements/updateAnimations.browsertest.d.ts +13 -0
  30. package/dist/elements/updateAnimations.d.ts +5 -0
  31. package/dist/elements/updateAnimations.js +37 -13
  32. package/dist/getRenderInfo.js +1 -1
  33. package/dist/gui/ContextMixin.js +27 -14
  34. package/dist/gui/EFControls.browsertest.d.ts +0 -0
  35. package/dist/gui/EFControls.d.ts +38 -0
  36. package/dist/gui/EFControls.js +51 -0
  37. package/dist/gui/EFFilmstrip.d.ts +40 -1
  38. package/dist/gui/EFFilmstrip.js +240 -3
  39. package/dist/gui/EFPreview.js +2 -1
  40. package/dist/gui/EFScrubber.d.ts +6 -5
  41. package/dist/gui/EFScrubber.js +31 -21
  42. package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
  43. package/dist/gui/EFTimeDisplay.d.ts +2 -6
  44. package/dist/gui/EFTimeDisplay.js +13 -23
  45. package/dist/gui/TWMixin.js +1 -1
  46. package/dist/gui/currentTimeContext.d.ts +3 -0
  47. package/dist/gui/currentTimeContext.js +3 -0
  48. package/dist/gui/durationContext.d.ts +3 -0
  49. package/dist/gui/durationContext.js +3 -0
  50. package/dist/index.d.ts +3 -0
  51. package/dist/index.js +4 -1
  52. package/dist/style.css +1 -1
  53. package/dist/transcoding/types/index.d.ts +11 -0
  54. package/dist/utils/LRUCache.d.ts +46 -0
  55. package/dist/utils/LRUCache.js +382 -1
  56. package/dist/utils/LRUCache.test.d.ts +1 -0
  57. package/package.json +2 -2
  58. package/src/elements/ContextProxiesController.ts +123 -0
  59. package/src/elements/EFCaptions.browsertest.ts +1820 -0
  60. package/src/elements/EFCaptions.ts +373 -36
  61. package/src/elements/EFImage.ts +4 -1
  62. package/src/elements/EFMedia/AssetIdMediaEngine.ts +30 -1
  63. package/src/elements/EFMedia/AssetMediaEngine.ts +33 -0
  64. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +3 -8
  65. package/src/elements/EFMedia/BaseMediaEngine.ts +35 -0
  66. package/src/elements/EFMedia/JitMediaEngine.ts +48 -0
  67. package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
  68. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
  69. package/src/elements/EFMedia.ts +38 -1
  70. package/src/elements/EFSurface.browsertest.ts +155 -0
  71. package/src/elements/EFSurface.ts +141 -0
  72. package/src/elements/EFTemporal.ts +14 -8
  73. package/src/elements/EFThumbnailStrip.browsertest.ts +591 -0
  74. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +713 -0
  75. package/src/elements/EFThumbnailStrip.ts +905 -0
  76. package/src/elements/EFTimegroup.browsertest.ts +56 -7
  77. package/src/elements/EFTimegroup.ts +88 -18
  78. package/src/elements/updateAnimations.browsertest.ts +361 -12
  79. package/src/elements/updateAnimations.ts +68 -19
  80. package/src/gui/ContextMixin.browsertest.ts +0 -25
  81. package/src/gui/ContextMixin.ts +44 -20
  82. package/src/gui/EFControls.browsertest.ts +175 -0
  83. package/src/gui/EFControls.ts +84 -0
  84. package/src/gui/EFFilmstrip.ts +323 -4
  85. package/src/gui/EFPreview.ts +2 -1
  86. package/src/gui/EFScrubber.ts +29 -25
  87. package/src/gui/EFTimeDisplay.browsertest.ts +237 -0
  88. package/src/gui/EFTimeDisplay.ts +12 -40
  89. package/src/gui/currentTimeContext.ts +5 -0
  90. package/src/gui/durationContext.ts +3 -0
  91. package/src/transcoding/types/index.ts +13 -0
  92. package/src/utils/LRUCache.test.ts +272 -0
  93. package/src/utils/LRUCache.ts +543 -0
  94. package/types.json +1 -1
  95. package/dist/transcoding/cache/CacheManager.d.ts +0 -73
  96. package/src/transcoding/cache/CacheManager.ts +0 -208
@@ -4,6 +4,8 @@ import { property, state } from "lit/decorators.js";
4
4
  import { EF_RENDERING } from "../EF_RENDERING.ts";
5
5
  import type { EFTimegroup } from "../elements/EFTimegroup.js";
6
6
  import { globalURLTokenDeduplicator } from "../transcoding/cache/URLTokenDeduplicator.js";
7
+ import { currentTimeContext } from "./currentTimeContext.js";
8
+ import { durationContext } from "./durationContext.js";
7
9
  import {
8
10
  type EFConfiguration,
9
11
  efConfigurationContext,
@@ -74,15 +76,12 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
74
76
  targetTimegroup: EFTimegroup | null = null;
75
77
 
76
78
  // Add reactive properties that depend on the targetTimegroup
77
- @state()
78
- get durationMs(): number {
79
- return this.targetTimegroup?.durationMs ?? 0;
80
- }
79
+ @provide({ context: durationContext })
80
+ @property({ type: Number })
81
+ durationMs = 0;
81
82
 
82
- @state()
83
- get endTimeMs(): number {
84
- return this.targetTimegroup?.endTimeMs ?? 0;
85
- }
83
+ @property({ type: Number })
84
+ endTimeMs = 0;
86
85
 
87
86
  @provide({ context: fetchContext })
88
87
  fetch = async (url: string, init: RequestInit = {}) => {
@@ -248,7 +247,8 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
248
247
  @property({ type: Boolean })
249
248
  rendering = false;
250
249
 
251
- @state()
250
+ @provide({ context: currentTimeContext })
251
+ @property({ type: Number })
252
252
  currentTimeMs = Number.NaN;
253
253
 
254
254
  #FPS = 30;
@@ -273,11 +273,24 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
273
273
  }
274
274
  } else if (mutation.type === "attributes") {
275
275
  // Watch for attribute changes that might affect duration
276
+ const durationAffectingAttributes = [
277
+ "duration",
278
+ "mode",
279
+ "trimstart",
280
+ "trimend",
281
+ "sourcein",
282
+ "sourceout",
283
+ ];
284
+
276
285
  if (
277
- mutation.attributeName === "duration" ||
278
- mutation.attributeName === "mode" ||
286
+ durationAffectingAttributes.includes(
287
+ mutation.attributeName || "",
288
+ ) ||
279
289
  (mutation.target instanceof Element &&
280
290
  (mutation.target.tagName === "EF-TIMEGROUP" ||
291
+ mutation.target.tagName === "EF-VIDEO" ||
292
+ mutation.target.tagName === "EF-AUDIO" ||
293
+ mutation.target.tagName === "EF-CAPTIONS" ||
281
294
  mutation.target.closest("ef-timegroup")))
282
295
  ) {
283
296
  shouldUpdate = true;
@@ -289,6 +302,8 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
289
302
  // Trigger an update to ensure reactive properties recalculate
290
303
  // Use a microtask to ensure DOM updates are complete
291
304
  queueMicrotask(() => {
305
+ // Recalculate duration and endTime when timegroup changes
306
+ this.updateDurationProperties();
292
307
  this.requestUpdate();
293
308
  // Also ensure the targetTimegroup updates its computed properties
294
309
  if (this.targetTimegroup) {
@@ -298,11 +313,29 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
298
313
  }
299
314
  });
300
315
 
316
+ /**
317
+ * Update duration properties when timegroup changes
318
+ */
319
+ updateDurationProperties(): void {
320
+ const newDuration = this.targetTimegroup?.durationMs ?? 0;
321
+ const newEndTime = this.targetTimegroup?.endTimeMs ?? 0;
322
+
323
+ if (this.durationMs !== newDuration) {
324
+ this.durationMs = newDuration;
325
+ }
326
+
327
+ if (this.endTimeMs !== newEndTime) {
328
+ this.endTimeMs = newEndTime;
329
+ }
330
+ }
331
+
301
332
  connectedCallback(): void {
302
333
  super.connectedCallback();
303
334
 
304
335
  // Initialize targetTimegroup
305
336
  this.targetTimegroup = this.querySelector("ef-timegroup");
337
+ // Initialize duration properties
338
+ this.updateDurationProperties();
306
339
 
307
340
  this.#timegroupObserver.observe(this, {
308
341
  childList: true,
@@ -342,15 +375,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
342
375
  return;
343
376
  }
344
377
  this.targetTimegroup.currentTimeMs = this.currentTimeMs;
345
- this.dispatchEvent(
346
- new CustomEvent("timeupdate", {
347
- detail: {
348
- currentTimeMs: this.currentTimeMs,
349
- progress:
350
- this.currentTimeMs / this.targetTimegroup.durationMs,
351
- },
352
- }),
353
- );
354
378
  }
355
379
  }
356
380
  }
@@ -0,0 +1,175 @@
1
+ import { html, LitElement } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { beforeEach, describe, expect, test } from "vitest";
4
+
5
+ import "../elements/EFTimegroup.js";
6
+ import { EFTargetable } from "../elements/TargetController.js";
7
+ import { ContextMixin } from "./ContextMixin.js";
8
+ import "./EFControls.js";
9
+ import { EFControls } from "./EFControls.js";
10
+ import "./EFPreview.js";
11
+
12
+ @customElement("test-context")
13
+ class TestContext extends EFTargetable(ContextMixin(LitElement)) {
14
+ render() {
15
+ return html`<slot></slot>`;
16
+ }
17
+ }
18
+
19
+ beforeEach(() => {
20
+ // Clean up localStorage
21
+ for (let i = 0; i < localStorage.length; i++) {
22
+ const key = localStorage.key(i);
23
+ if (typeof key !== "string") continue;
24
+ localStorage.removeItem(key);
25
+ }
26
+
27
+ // Clean up DOM
28
+ while (document.body.children.length) {
29
+ document.body.children[0]?.remove();
30
+ }
31
+ });
32
+
33
+ describe("EFControls", () => {
34
+ test("should be defined", () => {
35
+ expect(EFControls).toBeDefined();
36
+ });
37
+
38
+ test("can find and connect to target preview by ID", async () => {
39
+ // Create a preview with an ID
40
+ const preview = document.createElement("ef-preview") as any;
41
+ preview.id = "test-preview";
42
+
43
+ // Add a timegroup to the preview to give it duration
44
+ const timegroup = document.createElement("ef-timegroup");
45
+ timegroup.mode = "fixed";
46
+ timegroup.duration = "10s";
47
+ preview.appendChild(timegroup);
48
+
49
+ document.body.appendChild(preview);
50
+
51
+ // Create controls targeting the preview
52
+ const controls = document.createElement("ef-controls");
53
+ controls.target = "test-preview";
54
+ document.body.appendChild(controls);
55
+
56
+ // Wait for both elements to complete their updates
57
+ await preview.updateComplete;
58
+ await controls.updateComplete;
59
+
60
+ // The controls should have found and connected to the preview
61
+ expect(controls.targetElement).toBe(preview);
62
+ });
63
+
64
+ test("handles missing target gracefully", async () => {
65
+ const controls = document.createElement("ef-controls");
66
+ controls.target = "nonexistent-preview";
67
+ document.body.appendChild(controls);
68
+
69
+ // Wait for the controller to attempt connection
70
+ await controls.updateComplete;
71
+
72
+ // Should have no target but not crash
73
+ expect(controls.targetElement).toBe(null);
74
+ // Note: EFControls with context proxying doesn't have playing property
75
+ // It only proxies context requests to the target element
76
+ });
77
+
78
+ test("updates when target is set after connection", async () => {
79
+ const controls = document.createElement("ef-controls") as EFControls;
80
+ document.body.appendChild(controls);
81
+
82
+ // Initially no target
83
+ expect(controls.targetElement).toBe(null);
84
+
85
+ // Create preview
86
+ const preview = document.createElement("test-context") as TestContext;
87
+ preview.id = "test-preview";
88
+
89
+ const timegroup = document.createElement("ef-timegroup");
90
+ timegroup.mode = "fixed";
91
+ timegroup.duration = "10s";
92
+ preview.appendChild(timegroup);
93
+
94
+ document.body.appendChild(preview);
95
+
96
+ // Set target after both are connected
97
+ controls.target = "test-preview";
98
+
99
+ // Wait for both elements to complete their updates
100
+ await preview.updateComplete;
101
+ await controls.updateComplete;
102
+
103
+ // The controls should have found and connected to the target
104
+ expect(controls.targetElement).toBe(preview);
105
+ });
106
+
107
+ test("disconnects from target when removed", async () => {
108
+ const preview = document.createElement("test-context") as TestContext;
109
+ preview.id = "test-preview";
110
+
111
+ const timegroup = document.createElement("ef-timegroup");
112
+ timegroup.mode = "fixed";
113
+ timegroup.duration = "10s";
114
+ preview.appendChild(timegroup);
115
+
116
+ document.body.appendChild(preview);
117
+
118
+ const controls = document.createElement("ef-controls") as EFControls;
119
+ controls.target = "test-preview";
120
+ document.body.appendChild(controls);
121
+
122
+ // Wait for both elements to complete their updates
123
+ await preview.updateComplete;
124
+ await controls.updateComplete;
125
+
126
+ // Should be connected
127
+ expect(controls.targetElement).toBe(preview);
128
+
129
+ // Disconnect the controls
130
+ document.body.removeChild(controls);
131
+
132
+ // After disconnection, targetElement persists but should have no effect
133
+ // (TargetController only clears targetElement when target is removed, not when consumer disconnects)
134
+ expect(controls.targetElement).toBe(preview);
135
+ });
136
+
137
+ test("works with child control elements - EFTogglePlay", async () => {
138
+ // Import the control element
139
+ await import("./EFTogglePlay.js");
140
+
141
+ const preview = document.createElement("test-context") as TestContext;
142
+ preview.id = "test-preview";
143
+ preview.playing = false;
144
+
145
+ const timegroup = document.createElement("ef-timegroup");
146
+ timegroup.mode = "fixed";
147
+ timegroup.duration = "10s";
148
+ preview.appendChild(timegroup);
149
+
150
+ document.body.appendChild(preview);
151
+
152
+ const controls = document.createElement("ef-controls") as EFControls;
153
+ controls.target = "test-preview";
154
+
155
+ const togglePlay = document.createElement("ef-toggle-play");
156
+ controls.appendChild(togglePlay);
157
+
158
+ document.body.appendChild(controls);
159
+
160
+ // Wait for all elements to complete their updates
161
+ await preview.updateComplete;
162
+ await controls.updateComplete;
163
+ await togglePlay.updateComplete;
164
+
165
+ // The toggle play should be connected to the controls' context (which syncs with preview)
166
+ expect((togglePlay as any).playing).toBe(false);
167
+
168
+ // Test that clicking the toggle affects the preview
169
+ preview.playing = true;
170
+ await preview.updateComplete;
171
+ await togglePlay.updateComplete;
172
+
173
+ expect((togglePlay as any).playing).toBe(true);
174
+ });
175
+ });
@@ -0,0 +1,84 @@
1
+ import { css, html, LitElement } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+
4
+ import { ContextProxyController } from "../elements/ContextProxiesController.js";
5
+ import { TargetController } from "../elements/TargetController.js";
6
+ import type { ContextMixinInterface } from "./ContextMixin.js";
7
+ import { targetTimegroupContext } from "./ContextMixin.js";
8
+ import { currentTimeContext } from "./currentTimeContext.js";
9
+ import { durationContext } from "./durationContext.js";
10
+ import { efConfigurationContext } from "./EFConfiguration.js";
11
+ import { efContext } from "./efContext.js";
12
+ import { fetchContext } from "./fetchContext.js";
13
+ import { focusContext } from "./focusContext.js";
14
+ import { focusedElementContext } from "./focusedElementContext.js";
15
+ import { loopContext, playingContext } from "./playingContext.js";
16
+
17
+ /**
18
+ * EFControls provides a way to control an ef-preview element that is not a direct ancestor.
19
+ * It bridges the contexts from a target preview element to its children controls.
20
+ *
21
+ * Usage:
22
+ * ```html
23
+ * <ef-preview id="my-preview">...</ef-preview>
24
+ *
25
+ * <ef-controls target="my-preview">
26
+ * <ef-toggle-play>
27
+ * <button slot="play">Play</button>
28
+ * <button slot="pause">Pause</button>
29
+ * </ef-toggle-play>
30
+ * <ef-scrubber></ef-scrubber>
31
+ * <ef-time-display></ef-time-display>
32
+ * </ef-controls>
33
+ * ```
34
+ */
35
+ @customElement("ef-controls")
36
+ export class EFControls extends LitElement {
37
+ static styles = css`
38
+ :host {
39
+ display: block;
40
+ }
41
+ `;
42
+
43
+ /**
44
+ * The ID of the ef-preview element to control
45
+ */
46
+ @property({ type: String })
47
+ target = "";
48
+
49
+ /**
50
+ * The target element (set by TargetController)
51
+ */
52
+ @state()
53
+ targetElement: ContextMixinInterface | null = null;
54
+
55
+ // @ts-expect-error controller is intentionally not referenced directly
56
+ #targetController = new TargetController(this);
57
+
58
+ // @ts-expect-error controller is intentionally not referenced directly
59
+ #contextProxyController = new ContextProxyController(this, {
60
+ target: () => this.targetElement,
61
+ contexts: [
62
+ playingContext,
63
+ loopContext,
64
+ currentTimeContext,
65
+ durationContext,
66
+ targetTimegroupContext,
67
+ focusedElementContext,
68
+ efContext,
69
+ fetchContext,
70
+ focusContext,
71
+ efConfigurationContext,
72
+ ],
73
+ });
74
+
75
+ render() {
76
+ return html`<slot></slot>`;
77
+ }
78
+ }
79
+
80
+ declare global {
81
+ interface HTMLElementTagNameMap {
82
+ "ef-controls": EFControls;
83
+ }
84
+ }