@editframe/elements 0.11.0-beta.9 → 0.12.0-beta.1

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 (47) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +8 -15
  2. package/dist/elements/EFCaptions.d.ts +50 -6
  3. package/dist/elements/EFMedia.d.ts +1 -1
  4. package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
  5. package/dist/elements/EFTimegroup.d.ts +23 -2
  6. package/dist/elements/EFWaveform.d.ts +15 -11
  7. package/dist/elements/src/EF_FRAMEGEN.js +24 -26
  8. package/dist/elements/src/elements/EFCaptions.js +295 -42
  9. package/dist/elements/src/elements/EFImage.js +0 -6
  10. package/dist/elements/src/elements/EFMedia.js +0 -5
  11. package/dist/elements/src/elements/EFTemporal.js +13 -10
  12. package/dist/elements/src/elements/EFTimegroup.js +37 -12
  13. package/dist/elements/src/elements/EFVideo.js +1 -4
  14. package/dist/elements/src/elements/EFWaveform.js +250 -143
  15. package/dist/elements/src/gui/ContextMixin.js +36 -7
  16. package/dist/elements/src/gui/EFScrubber.js +142 -0
  17. package/dist/elements/src/gui/EFTimeDisplay.js +81 -0
  18. package/dist/elements/src/gui/EFTogglePlay.js +14 -14
  19. package/dist/elements/src/gui/EFWorkbench.js +1 -24
  20. package/dist/elements/src/gui/TWMixin.css.js +1 -1
  21. package/dist/elements/src/index.js +8 -1
  22. package/dist/gui/ContextMixin.d.ts +2 -1
  23. package/dist/gui/EFScrubber.d.ts +23 -0
  24. package/dist/gui/EFTimeDisplay.d.ts +17 -0
  25. package/dist/gui/EFTogglePlay.d.ts +1 -1
  26. package/dist/gui/EFWorkbench.d.ts +0 -1
  27. package/dist/index.d.ts +3 -1
  28. package/dist/style.css +6 -801
  29. package/package.json +2 -2
  30. package/src/elements/EFCaptions.browsertest.ts +6 -6
  31. package/src/elements/EFCaptions.ts +325 -56
  32. package/src/elements/EFImage.browsertest.ts +4 -17
  33. package/src/elements/EFImage.ts +0 -6
  34. package/src/elements/EFMedia.browsertest.ts +8 -19
  35. package/src/elements/EFMedia.ts +1 -6
  36. package/src/elements/EFTemporal.browsertest.ts +14 -0
  37. package/src/elements/EFTemporal.ts +14 -0
  38. package/src/elements/EFTimegroup.browsertest.ts +37 -0
  39. package/src/elements/EFTimegroup.ts +42 -17
  40. package/src/elements/EFVideo.ts +1 -4
  41. package/src/elements/EFWaveform.ts +339 -314
  42. package/src/gui/ContextMixin.browsertest.ts +28 -2
  43. package/src/gui/ContextMixin.ts +41 -9
  44. package/src/gui/EFScrubber.ts +145 -0
  45. package/src/gui/EFTimeDisplay.ts +81 -0
  46. package/src/gui/EFTogglePlay.ts +21 -21
  47. package/src/gui/EFWorkbench.ts +3 -36
@@ -57,11 +57,6 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
57
57
  #assetId: string | null = null;
58
58
  @property({ type: String, attribute: "asset-id", reflect: true })
59
59
  set assetId(value: string | null) {
60
- if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
61
- throw new Error(
62
- "EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)",
63
- );
64
- }
65
60
  this.#assetId = value;
66
61
  }
67
62
 
@@ -104,7 +99,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
104
99
  },
105
100
  });
106
101
 
107
- protected initSegmentsLoader = new Task(this, {
102
+ public initSegmentsLoader = new Task(this, {
108
103
  autoRun: EF_INTERACTIVE,
109
104
  args: () =>
110
105
  [this.trackFragmentIndexLoader.value, this.src, this.fetch] as const,
@@ -63,3 +63,17 @@ describe("trimstart and trimend", () => {
63
63
  expect(element.trimEndMs).toBe(5_000);
64
64
  });
65
65
  });
66
+
67
+ describe("duration", () => {
68
+ test("duration is parsed correctly", () => {
69
+ const element = document.createElement("test-temporal");
70
+ element.setAttribute("duration", "10s");
71
+ expect(element.durationMs).toBe(10_000);
72
+ });
73
+
74
+ test("duration can be set directly on the element", () => {
75
+ const element = document.createElement("test-temporal");
76
+ element.duration = "10s";
77
+ expect(element.durationMs).toBe(10_000);
78
+ });
79
+ });
@@ -169,6 +169,14 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
169
169
  })
170
170
  private _durationMs?: number;
171
171
 
172
+ set duration(value: string | undefined) {
173
+ if (value !== undefined) {
174
+ this.setAttribute("duration", value);
175
+ } else {
176
+ this.removeAttribute("duration");
177
+ }
178
+ }
179
+
172
180
  private _trimStartMs = 0;
173
181
  @property({
174
182
  type: Number,
@@ -179,6 +187,9 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
179
187
  return this._trimStartMs;
180
188
  }
181
189
  public set trimStartMs(value: number) {
190
+ if (this._trimStartMs === value) {
191
+ return;
192
+ }
182
193
  this._trimStartMs = value;
183
194
  this.setAttribute(
184
195
  "trimstart",
@@ -203,6 +214,9 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
203
214
  return this._trimEndMs;
204
215
  }
205
216
  public set trimEndMs(value: number) {
217
+ if (this._trimEndMs === value) {
218
+ return;
219
+ }
206
220
  this._trimEndMs = value;
207
221
  this.setAttribute("trimend", durationConverter.toAttribute(value / 1000));
208
222
  }
@@ -8,7 +8,10 @@ import { assert, beforeEach, describe, test } from "vitest";
8
8
  import { EFTimegroup } from "./EFTimegroup.ts";
9
9
  import "./EFTimegroup.ts";
10
10
  import { customElement } from "lit/decorators/custom-element.js";
11
+ import { ContextMixin } from "../gui/ContextMixin.ts";
11
12
  import { EFTemporal } from "./EFTemporal.ts";
13
+ // Need workbench to make workbench wrapping occurs
14
+ import "../gui/EFWorkbench.ts";
12
15
 
13
16
  beforeEach(() => {
14
17
  for (let i = 0; i < localStorage.length; i++) {
@@ -21,6 +24,9 @@ beforeEach(() => {
21
24
  }
22
25
  });
23
26
 
27
+ @customElement("test-context")
28
+ class TestContext extends ContextMixin(LitElement) {}
29
+
24
30
  @customElement("test-temporal")
25
31
  class TestTemporal extends EFTemporal(LitElement) {
26
32
  get hasOwnDuration(): boolean {
@@ -31,6 +37,7 @@ class TestTemporal extends EFTemporal(LitElement) {
31
37
  declare global {
32
38
  interface HTMLElementTagNameMap {
33
39
  "test-temporal": TestTemporal;
40
+ "test-context": TestContext;
34
41
  }
35
42
  }
36
43
 
@@ -331,3 +338,33 @@ describe("setting currentTime", () => {
331
338
  assert.equal(b.ownCurrentTimeMs, 2_500);
332
339
  });
333
340
  });
341
+
342
+ describe("shouldWrapWithWorkbench", () => {
343
+ test.skip("should not wrap if EF_INTERACTIVE is false", () => {
344
+ // TODO: need a way to define EF_INTERACTIVE in a test
345
+ });
346
+
347
+ test("should wrap if root-most timegroup", () => {
348
+ const root = document.createElement("ef-timegroup");
349
+ const child = document.createElement("ef-timegroup");
350
+ root.appendChild(child);
351
+ assert.isTrue(child.shouldWrapWithWorkbench());
352
+ });
353
+
354
+ test("should not wrap if contained within a preview context", () => {
355
+ const timegorup = document.createElement("ef-timegroup");
356
+ const context = document.createElement("test-context");
357
+ context.append(timegorup);
358
+ assert.isFalse(timegorup.shouldWrapWithWorkbench());
359
+ });
360
+ });
361
+
362
+ describe("DOM nodes", () => {
363
+ test("can have mode and duration set as attributes", () => {
364
+ const timegroup = document.createElement("ef-timegroup");
365
+ timegroup.setAttribute("mode", "fixed");
366
+ timegroup.setAttribute("duration", "10s");
367
+ assert.equal(timegroup.mode, "fixed");
368
+ assert.equal(timegroup.durationMs, 10_000);
369
+ });
370
+ });
@@ -1,9 +1,12 @@
1
- import { LitElement, html, css, type PropertyValueMap } from "lit";
2
1
  import { provide } from "@lit/context";
3
2
  import { Task } from "@lit/task";
4
- import { customElement, property } from "lit/decorators.js";
5
3
  import debug from "debug";
4
+ import { LitElement, type PropertyValueMap, css, html } from "lit";
5
+ import { customElement, property } from "lit/decorators.js";
6
6
 
7
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
8
+ import { isContextMixin } from "../gui/ContextMixin.ts";
9
+ import { deepGetMediaElements } from "./EFMedia.ts";
7
10
  import {
8
11
  EFTemporal,
9
12
  isEFTemporal,
@@ -11,8 +14,6 @@ import {
11
14
  timegroupContext,
12
15
  } from "./EFTemporal.ts";
13
16
  import { TimegroupController } from "./TimegroupController.ts";
14
- import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
15
- import { deepGetMediaElements } from "./EFMedia.ts";
16
17
  import { durationConverter } from "./durationConverter.ts";
17
18
 
18
19
  const log = debug("ef:elements:EFTimegroup");
@@ -151,10 +152,16 @@ export class EFTimegroup extends EFTemporal(LitElement) {
151
152
  }
152
153
  }
153
154
 
155
+ /**
156
+ * Wait for all media elements to load their initial segments.
157
+ * Ideally we would only need the extracted index json data, but
158
+ * that caused issues with constructing audio data. We had negative durations
159
+ * in calculations and it was not clear why.
160
+ */
154
161
  async waitForMediaDurations() {
155
162
  return await Promise.all(
156
163
  deepGetMediaElements(this).map(
157
- (media) => media.trackFragmentIndexLoader.taskComplete,
164
+ (media) => media.initSegmentsLoader.taskComplete,
158
165
  ),
159
166
  );
160
167
  }
@@ -243,12 +250,34 @@ export class EFTimegroup extends EFTemporal(LitElement) {
243
250
  }
244
251
  }
245
252
 
253
+ get contextProvider() {
254
+ let parent = this.parentNode;
255
+ while (parent) {
256
+ if (isContextMixin(parent)) {
257
+ return parent;
258
+ }
259
+ parent = parent.parentNode;
260
+ }
261
+ return null;
262
+ }
263
+
264
+ /**
265
+ * Returns true if the timegroup should be wrapped with a workbench.
266
+ *
267
+ * A timegroup should be wrapped with a workbench if it is the root-most timegroup
268
+ * and EF_INTERACTIVE is true.
269
+ *
270
+ * If the timegroup is already wrappedin a context provider like ef-preview,
271
+ * it should NOT be wrapped in a workbench.
272
+ *
273
+ */
246
274
  shouldWrapWithWorkbench() {
247
275
  return (
248
276
  EF_INTERACTIVE &&
249
277
  this.closest("ef-timegroup") === this &&
278
+ this.closest("ef-preview") === null &&
250
279
  this.closest("ef-workbench") === null &&
251
- this.closest("ef-preview") === null
280
+ this.closest("test-context") === null
252
281
  );
253
282
  }
254
283
 
@@ -286,12 +315,8 @@ export class EFTimegroup extends EFTemporal(LitElement) {
286
315
  ) {
287
316
  await this.waitForMediaDurations();
288
317
 
289
- const durationMs = toMs - fromMs;
290
-
291
318
  await Promise.all(
292
319
  deepGetMediaElements(this).map(async (mediaElement) => {
293
- await mediaElement.trackFragmentIndexLoader.taskComplete;
294
-
295
320
  const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
296
321
  const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
297
322
  const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
@@ -304,19 +329,19 @@ export class EFTimegroup extends EFTemporal(LitElement) {
304
329
  throw new Error("Failed to fetch audio");
305
330
  }
306
331
 
307
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
308
- const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
309
- const ctxDurationMs = ctxEndMs - ctxStartMs;
310
-
311
- const offset =
312
- Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
313
-
314
332
  const bufferSource = audioContext.createBufferSource();
315
333
  bufferSource.buffer = await audioContext.decodeAudioData(
316
334
  await audio.blob.arrayBuffer(),
317
335
  );
318
336
  bufferSource.connect(audioContext.destination);
319
337
 
338
+ const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
339
+ const ctxEndMs = mediaElement.endTimeMs - fromMs;
340
+ const ctxDurationMs = ctxEndMs - ctxStartMs;
341
+
342
+ const offset =
343
+ Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
344
+
320
345
  bufferSource.start(
321
346
  ctxStartMs / 1000,
322
347
  offset / 1000,
@@ -20,10 +20,7 @@ export class EFVideo extends TWMixin(EFMedia) {
20
20
  ];
21
21
  canvasRef = createRef<HTMLCanvasElement>();
22
22
  render() {
23
- return html` <canvas
24
- class="h-full w-full object-fill"
25
- ${ref(this.canvasRef)}
26
- ></canvas>`;
23
+ return html` <canvas ${ref(this.canvasRef)}></canvas>`;
27
24
  }
28
25
 
29
26
  get canvasElement() {