@editframe/elements 0.12.0-beta.2 → 0.12.0-beta.21

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 (92) hide show
  1. package/dist/elements/EFAudio.d.ts +1 -1
  2. package/dist/elements/EFCaptions.d.ts +5 -5
  3. package/dist/elements/EFImage.d.ts +1 -1
  4. package/dist/elements/EFMedia.browsertest.d.ts +1 -1
  5. package/dist/elements/EFMedia.d.ts +1 -2
  6. package/dist/elements/{src/elements/EFMedia.js → EFMedia.js} +72 -15
  7. package/dist/elements/EFTemporal.browsertest.d.ts +1 -1
  8. package/dist/elements/EFTemporal.d.ts +4 -1
  9. package/dist/elements/{src/elements/EFTemporal.js → EFTemporal.js} +3 -0
  10. package/dist/elements/EFTimegroup.browsertest.d.ts +2 -2
  11. package/dist/elements/EFTimegroup.d.ts +3 -3
  12. package/dist/elements/EFVideo.d.ts +1 -1
  13. package/dist/elements/{src/elements/EFVideo.js → EFVideo.js} +10 -2
  14. package/dist/elements/EFWaveform.d.ts +7 -4
  15. package/dist/elements/{src/elements/EFWaveform.js → EFWaveform.js} +48 -56
  16. package/dist/elements/TimegroupController.d.ts +2 -2
  17. package/dist/elements/util.d.ts +1 -1
  18. package/dist/gui/ContextMixin.browsertest.d.ts +1 -1
  19. package/dist/gui/ContextMixin.d.ts +1 -1
  20. package/dist/{elements/src/gui → gui}/ContextMixin.js +8 -4
  21. package/dist/gui/EFFilmstrip.d.ts +7 -7
  22. package/dist/gui/EFPreview.d.ts +1 -1
  23. package/dist/{elements/src/gui → gui}/EFPreview.js +3 -1
  24. package/dist/gui/EFScrubber.d.ts +1 -1
  25. package/dist/gui/EFTimeDisplay.d.ts +1 -1
  26. package/dist/gui/EFToggleLoop.d.ts +1 -1
  27. package/dist/gui/EFTogglePlay.d.ts +1 -3
  28. package/dist/{elements/src/gui → gui}/EFTogglePlay.js +1 -9
  29. package/dist/gui/EFWorkbench.d.ts +1 -1
  30. package/dist/{elements/src/gui → gui}/TWMixin.css.js +1 -1
  31. package/dist/gui/efContext.d.ts +1 -1
  32. package/dist/index.d.ts +14 -14
  33. package/package.json +5 -4
  34. package/src/elements/EFAudio.ts +3 -3
  35. package/src/elements/EFCaptions.browsertest.ts +3 -3
  36. package/src/elements/EFCaptions.ts +9 -9
  37. package/src/elements/EFImage.browsertest.ts +2 -2
  38. package/src/elements/EFImage.ts +4 -4
  39. package/src/elements/EFMedia.browsertest.ts +7 -5
  40. package/src/elements/EFMedia.ts +91 -19
  41. package/src/elements/EFSourceMixin.ts +3 -3
  42. package/src/elements/EFTemporal.browsertest.ts +1 -1
  43. package/src/elements/EFTemporal.ts +9 -3
  44. package/src/elements/EFTimegroup.browsertest.ts +5 -5
  45. package/src/elements/EFTimegroup.ts +6 -6
  46. package/src/elements/EFVideo.ts +15 -4
  47. package/src/elements/EFWaveform.ts +82 -98
  48. package/src/elements/FetchMixin.ts +2 -2
  49. package/src/elements/TimegroupController.ts +2 -2
  50. package/src/elements/durationConverter.ts +1 -1
  51. package/src/elements/util.ts +1 -1
  52. package/src/gui/ContextMixin.browsertest.ts +3 -3
  53. package/src/gui/ContextMixin.ts +18 -12
  54. package/src/gui/EFFilmstrip.ts +15 -15
  55. package/src/gui/EFPreview.ts +5 -3
  56. package/src/gui/EFScrubber.ts +3 -3
  57. package/src/gui/EFTimeDisplay.ts +2 -2
  58. package/src/gui/EFToggleLoop.ts +4 -4
  59. package/src/gui/EFTogglePlay.ts +4 -10
  60. package/src/gui/EFWorkbench.ts +2 -2
  61. package/src/gui/efContext.ts +1 -1
  62. package/dist/assets/src/EncodedAsset.js +0 -560
  63. package/dist/assets/src/MP4File.js +0 -172
  64. package/dist/assets/src/memoize.js +0 -14
  65. package/dist/elements/src/elements/util.js +0 -11
  66. package/dist/{elements/src/EF_FRAMEGEN.js → EF_FRAMEGEN.js} +0 -0
  67. package/dist/{elements/src/EF_INTERACTIVE.js → EF_INTERACTIVE.js} +0 -0
  68. package/dist/{elements/src/EF_RENDERING.js → EF_RENDERING.js} +0 -0
  69. package/dist/elements/{src/elements/CrossUpdateController.js → CrossUpdateController.js} +0 -0
  70. package/dist/elements/{src/elements/EFAudio.js → EFAudio.js} +2 -2
  71. package/dist/elements/{src/elements/EFCaptions.js → EFCaptions.js} +0 -0
  72. package/dist/elements/{src/elements/EFImage.js → EFImage.js} +0 -0
  73. package/dist/elements/{src/elements/EFSourceMixin.js → EFSourceMixin.js} +1 -1
  74. package/dist/elements/{src/elements/EFTimegroup.js → EFTimegroup.js} +0 -0
  75. package/dist/elements/{src/elements/FetchMixin.js → FetchMixin.js} +0 -0
  76. package/dist/elements/{src/elements/TimegroupController.js → TimegroupController.js} +0 -0
  77. package/dist/elements/{src/elements/durationConverter.js → durationConverter.js} +0 -0
  78. package/dist/elements/{src/elements/parseTimeToMs.js → parseTimeToMs.js} +0 -0
  79. package/dist/{elements/src/gui → gui}/EFFilmstrip.js +0 -0
  80. package/dist/{elements/src/gui → gui}/EFScrubber.js +0 -0
  81. package/dist/{elements/src/gui → gui}/EFTimeDisplay.js +0 -0
  82. package/dist/{elements/src/gui → gui}/EFToggleLoop.js +1 -1
  83. /package/dist/{elements/src/gui → gui}/EFWorkbench.js +0 -0
  84. /package/dist/{elements/src/gui → gui}/TWMixin.js +0 -0
  85. /package/dist/{elements/src/gui → gui}/apiHostContext.js +0 -0
  86. /package/dist/{elements/src/gui → gui}/efContext.js +0 -0
  87. /package/dist/{elements/src/gui → gui}/fetchContext.js +0 -0
  88. /package/dist/{elements/src/gui → gui}/focusContext.js +0 -0
  89. /package/dist/{elements/src/gui → gui}/focusedElementContext.js +0 -0
  90. /package/dist/{elements/src/gui → gui}/playingContext.js +0 -0
  91. /package/dist/{elements/src/index.js → index.js} +0 -0
  92. /package/dist/{elements/src/msToTimeCode.js → msToTimeCode.js} +0 -0
@@ -4,17 +4,17 @@ import debug from "debug";
4
4
  import { LitElement, type PropertyValueMap, css, html } from "lit";
5
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
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
8
+ import { isContextMixin } from "../gui/ContextMixin.js";
9
+ import { deepGetMediaElements } from "./EFMedia.js";
10
10
  import {
11
11
  EFTemporal,
12
12
  isEFTemporal,
13
13
  shallowGetTemporalElements,
14
14
  timegroupContext,
15
- } from "./EFTemporal.ts";
16
- import { TimegroupController } from "./TimegroupController.ts";
17
- import { durationConverter } from "./durationConverter.ts";
15
+ } from "./EFTemporal.js";
16
+ import { TimegroupController } from "./TimegroupController.js";
17
+ import { durationConverter } from "./durationConverter.js";
18
18
 
19
19
  const log = debug("ef:elements:EFTimegroup");
20
20
 
@@ -3,8 +3,8 @@ import { css, html } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
4
  import { createRef, ref } from "lit/directives/ref.js";
5
5
 
6
- import { TWMixin } from "../gui/TWMixin.ts";
7
- import { EFMedia } from "./EFMedia.ts";
6
+ import { TWMixin } from "../gui/TWMixin.js";
7
+ import { EFMedia } from "./EFMedia.js";
8
8
 
9
9
  @customElement("ef-video")
10
10
  export class EFVideo extends TWMixin(EFMedia) {
@@ -78,9 +78,20 @@ export class EFVideo extends TWMixin(EFMedia) {
78
78
  return;
79
79
  }
80
80
 
81
- this.canvasElement.width = frame?.codedWidth;
82
- this.canvasElement.height = frame?.codedHeight;
81
+ if (frame?.codedWidth && frame?.codedHeight) {
82
+ if (
83
+ this.canvasElement.width !== frame.codedWidth ||
84
+ this.canvasElement.height !== frame.codedHeight
85
+ ) {
86
+ this.canvasElement.width = frame.codedWidth;
87
+ this.canvasElement.height = frame.codedHeight;
88
+ }
89
+ }
83
90
 
91
+ if (frame.format === null) {
92
+ console.warn("Frame format is null", frame);
93
+ return seekToMs;
94
+ }
84
95
  ctx.drawImage(
85
96
  frame,
86
97
  0,
@@ -1,14 +1,14 @@
1
- import { EFAudio } from "./EFAudio.ts";
1
+ import { EFAudio } from "./EFAudio.js";
2
2
 
3
3
  import { Task } from "@lit/task";
4
4
  import { LitElement, type PropertyValueMap, css, html } from "lit";
5
5
  import { customElement, property } from "lit/decorators.js";
6
6
  import { type Ref, createRef, ref } from "lit/directives/ref.js";
7
- import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
8
- import { TWMixin } from "../gui/TWMixin.ts";
9
- import { CrossUpdateController } from "./CrossUpdateController.ts";
10
- import { EFTemporal } from "./EFTemporal.ts";
11
- import { EFVideo } from "./EFVideo.ts";
7
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
8
+ import { TWMixin } from "../gui/TWMixin.js";
9
+ import { CrossUpdateController } from "./CrossUpdateController.js";
10
+ import { EFTemporal } from "./EFTemporal.js";
11
+ import { EFVideo } from "./EFVideo.js";
12
12
 
13
13
  @customElement("ef-waveform")
14
14
  export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
@@ -24,6 +24,9 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
24
24
  canvasRef: Ref<HTMLCanvasElement> = createRef();
25
25
  private ctx: CanvasRenderingContext2D | null = null;
26
26
 
27
+ private resizeObserver?: ResizeObserver;
28
+ private mutationObserver?: MutationObserver;
29
+
27
30
  createRenderRoot() {
28
31
  return this;
29
32
  }
@@ -61,10 +64,43 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
61
64
  if (this.targetElement) {
62
65
  new CrossUpdateController(this.targetElement, this);
63
66
  }
67
+
68
+ // Initialize ResizeObserver
69
+ this.resizeObserver = new ResizeObserver(() => {
70
+ this.resizeCanvas();
71
+ });
72
+
73
+ // Observe the host element
74
+ this.resizeObserver.observe(this);
75
+
76
+ // Initialize MutationObserver
77
+ this.mutationObserver = new MutationObserver((mutationsList) => {
78
+ for (const mutation of mutationsList) {
79
+ if (mutation.type === "attributes") {
80
+ this.frameTask.run();
81
+ }
82
+ }
83
+ });
84
+
85
+ // Observe attribute changes on the element
86
+ this.mutationObserver.observe(this, { attributes: true });
87
+ }
88
+
89
+ disconnectedCallback() {
90
+ super.disconnectedCallback();
91
+ // Disconnect the observers when the element is removed from the DOM
92
+ this.resizeObserver?.disconnect();
93
+ this.mutationObserver?.disconnect();
94
+ }
95
+
96
+ private resizeCanvas() {
97
+ this.ctx = this.initCanvas();
98
+ if (this.ctx) {
99
+ this.frameTask.run(); // Redraw the canvas
100
+ }
64
101
  }
65
102
 
66
103
  protected initCanvas() {
67
- console.count("initCanvas");
68
104
  const canvas = this.canvasRef.value;
69
105
  if (!canvas) return null;
70
106
 
@@ -87,9 +123,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
87
123
  }
88
124
 
89
125
  protected drawBars(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
90
- ctx.strokeStyle = this.color;
91
- ctx.fillStyle = this.color;
92
-
93
126
  const canvas = ctx.canvas;
94
127
  const waveWidth = canvas.width / devicePixelRatio;
95
128
  const waveHeight = canvas.height / devicePixelRatio;
@@ -111,9 +144,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
111
144
  ctx: CanvasRenderingContext2D,
112
145
  frequencyData: Uint8Array,
113
146
  ) {
114
- ctx.strokeStyle = this.color;
115
- ctx.fillStyle = this.color;
116
-
117
147
  const canvas = ctx.canvas;
118
148
  const waveWidth = canvas.width / devicePixelRatio;
119
149
  const waveHeight = canvas.height / devicePixelRatio;
@@ -144,9 +174,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
144
174
  }
145
175
 
146
176
  protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
147
- ctx.strokeStyle = this.color;
148
- ctx.fillStyle = this.color;
149
-
150
177
  const canvas = ctx.canvas;
151
178
  const waveWidth = canvas.width / devicePixelRatio;
152
179
  const waveHeight = canvas.height / devicePixelRatio;
@@ -173,9 +200,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
173
200
  ctx: CanvasRenderingContext2D,
174
201
  frequencyData: Uint8Array,
175
202
  ) {
176
- ctx.strokeStyle = this.color;
177
- ctx.fillStyle = this.color;
178
-
179
203
  const canvas = ctx.canvas;
180
204
  const waveWidth = canvas.width / devicePixelRatio;
181
205
  const waveHeight = canvas.height / devicePixelRatio;
@@ -202,9 +226,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
202
226
  ctx: CanvasRenderingContext2D,
203
227
  frequencyData: Uint8Array,
204
228
  ) {
205
- ctx.strokeStyle = this.color;
206
- ctx.fillStyle = this.color;
207
-
208
229
  const canvas = ctx.canvas;
209
230
  const waveWidth = canvas.width / devicePixelRatio;
210
231
  const waveHeight = canvas.height / devicePixelRatio;
@@ -234,9 +255,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
234
255
  ctx: CanvasRenderingContext2D,
235
256
  frequencyData: Uint8Array,
236
257
  ) {
237
- ctx.strokeStyle = this.color;
238
- ctx.fillStyle = this.color;
239
-
240
258
  const canvas = ctx.canvas;
241
259
  const waveWidth = canvas.width / devicePixelRatio;
242
260
  const waveHeight = canvas.height / devicePixelRatio;
@@ -263,9 +281,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
263
281
  ctx: CanvasRenderingContext2D,
264
282
  frequencyData: Uint8Array,
265
283
  ) {
266
- ctx.strokeStyle = this.color;
267
- ctx.fillStyle = this.color;
268
-
269
284
  const canvas = ctx.canvas;
270
285
  const waveWidth = canvas.width / devicePixelRatio;
271
286
  const waveHeight = canvas.height / devicePixelRatio;
@@ -284,9 +299,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
284
299
  }
285
300
 
286
301
  protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
287
- ctx.strokeStyle = this.color;
288
- ctx.fillStyle = this.color;
289
-
290
302
  const canvas = ctx.canvas;
291
303
  const waveWidth = canvas.width / devicePixelRatio;
292
304
  const waveHeight = canvas.height / devicePixelRatio;
@@ -300,7 +312,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
300
312
  ctx.moveTo(0, baseline);
301
313
  ctx.lineTo(waveWidth, baseline);
302
314
  ctx.strokeStyle = this.color;
303
- ctx.lineWidth = 2;
315
+ ctx.lineWidth = 1;
304
316
  ctx.stroke();
305
317
 
306
318
  // Draw bars
@@ -313,21 +325,10 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
313
325
  });
314
326
  }
315
327
 
316
- // Add this property to store the last frame timestamp
317
- private lastFrameTime = 0;
318
-
319
328
  frameTask = new Task(this, {
320
329
  autoRun: EF_INTERACTIVE,
321
330
  args: () => [this.targetElement.audioBufferTask.status] as const,
322
331
  task: async () => {
323
- // Calculate and log time since last frame
324
- const currentTime = performance.now();
325
- const timeSinceLastFrame = this.lastFrameTime
326
- ? currentTime - this.lastFrameTime
327
- : 0;
328
- console.log(`Time since last frame: ${timeSinceLastFrame.toFixed(2)}ms`);
329
- this.lastFrameTime = currentTime;
330
-
331
332
  await this.targetElement.audioBufferTask.taskComplete;
332
333
  // Lazy initialize canvas, if we don't stash it, we re-init
333
334
  // every frame, which blanks the canvas, causing flicker.
@@ -338,61 +339,44 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
338
339
  if (!this.targetElement.audioBufferTask.value) return;
339
340
 
340
341
  if (this.targetElement.trimAdjustedOwnCurrentTimeMs > 0) {
341
- const FRAMES_TO_ANALYZE = 4;
342
- const FRAME_DURATION_MS = 48000 / 100;
343
-
344
- const multiFrameData: Uint8Array[] = [];
345
-
346
- for (let i = 0; i < FRAMES_TO_ANALYZE; i++) {
347
- const frameOffset = i - Math.floor(FRAMES_TO_ANALYZE / 2);
348
- const audioContext = new OfflineAudioContext(2, 48000 / 25, 48000);
349
- const audioBufferSource = audioContext.createBufferSource();
350
- audioBufferSource.buffer =
351
- this.targetElement.audioBufferTask.value.buffer;
352
- const analyser = audioContext.createAnalyser();
353
-
354
- // Adjust FFT size for better frequency resolution
355
- analyser.fftSize = 128 / 2;
356
- // Adjust smoothing to make peaks more pronounced
357
- analyser.smoothingTimeConstant = 0.1;
358
-
359
- audioBufferSource.connect(analyser);
360
-
361
- const startTime = Math.max(
362
- 0,
363
- (this.targetElement.trimAdjustedOwnCurrentTimeMs -
364
- this.targetElement.audioBufferTask.value.startOffsetMs) /
365
- 1000 +
366
- (frameOffset * FRAME_DURATION_MS) / 1000,
367
- );
368
-
369
- audioBufferSource.start(0, startTime, FRAME_DURATION_MS / 1000);
370
- await audioContext.startRendering();
371
-
372
- const frameData = new Uint8Array(analyser.frequencyBinCount);
373
- analyser.getByteFrequencyData(frameData);
374
-
375
- multiFrameData.push(frameData);
376
- }
377
-
378
- const dataLength = multiFrameData[0]?.length ?? 0;
379
-
380
- // Average and enhance dynamics
381
- const smoothedData = new Uint8Array(dataLength);
382
- for (let i = 0; i < smoothedData.length; i++) {
383
- let sum = 0;
384
- for (const frameData of multiFrameData) {
385
- sum += frameData[i] ?? 0;
386
- }
387
- let avg = sum / FRAMES_TO_ANALYZE;
388
-
389
- // Enhance dynamics by applying a subtle exponential curve
390
- avg = (avg / 255) ** 1.2 * 255;
391
-
392
- smoothedData[i] = Math.round(avg);
342
+ const FRAME_DURATION_MS = 48000 / 1000;
343
+ const FRAME_SMEAR_MS = FRAME_DURATION_MS * 1;
344
+ const FRAME_SMEAR_S = FRAME_SMEAR_MS / 1000;
345
+
346
+ const audioContext = new OfflineAudioContext(2, 48000 / 25, 48000);
347
+ const audioBufferSource = audioContext.createBufferSource();
348
+ audioBufferSource.buffer =
349
+ this.targetElement.audioBufferTask.value.buffer;
350
+ const analyser = audioContext.createAnalyser();
351
+
352
+ // Adjust FFT size for better frequency resolution
353
+ analyser.fftSize = 128 * 8;
354
+
355
+ audioBufferSource.connect(analyser);
356
+
357
+ const startTime = Math.max(
358
+ 0,
359
+ (this.targetElement.trimAdjustedOwnCurrentTimeMs -
360
+ this.targetElement.audioBufferTask.value.startOffsetMs) /
361
+ 1000,
362
+ );
363
+
364
+ audioBufferSource.start(0, startTime, FRAME_SMEAR_S);
365
+ await audioContext.startRendering();
366
+
367
+ const frameData = new Uint8Array(analyser.frequencyBinCount);
368
+ analyser.getByteFrequencyData(frameData);
369
+
370
+ const smoothedData = frameData.slice(0, frameData.length / 2);
371
+
372
+ if (this.color === "currentColor") {
373
+ const computedStyle = getComputedStyle(this);
374
+ const currentColor = computedStyle.color;
375
+ ctx.strokeStyle = currentColor;
376
+ ctx.fillStyle = currentColor;
377
+ } else {
393
378
  }
394
379
 
395
- // Use smoothedData for rendering
396
380
  switch (this.mode) {
397
381
  case "bars":
398
382
  this.drawBars(ctx, smoothedData);
@@ -438,8 +422,8 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
438
422
  protected updated(changedProperties: PropertyValueMap<this>): void {
439
423
  super.updated(changedProperties);
440
424
 
441
- // Trigger a redraw if color or mode changes
442
- if (changedProperties.has("color") || changedProperties.has("mode")) {
425
+ // Trigger a redraw if any property changes
426
+ if (changedProperties.size > 0) {
443
427
  // Request a new frame
444
428
  this.frameTask.run();
445
429
  }
@@ -1,8 +1,8 @@
1
- import type { LitElement } from "lit";
2
1
  import { consume } from "@lit/context";
2
+ import type { LitElement } from "lit";
3
3
  import { state } from "lit/decorators/state.js";
4
4
 
5
- import { fetchContext } from "../gui/fetchContext.ts";
5
+ import { fetchContext } from "../gui/fetchContext.js";
6
6
 
7
7
  export declare class FetchMixinInterface {
8
8
  fetch: typeof fetch;
@@ -1,5 +1,5 @@
1
- import type { ReactiveController, LitElement } from "lit";
2
- import type { EFTimegroup } from "./EFTimegroup.ts";
1
+ import type { LitElement, ReactiveController } from "lit";
2
+ import type { EFTimegroup } from "./EFTimegroup.js";
3
3
 
4
4
  export class TimegroupController implements ReactiveController {
5
5
  constructor(
@@ -1,4 +1,4 @@
1
- import { parseTimeToMs } from "./parseTimeToMs.ts";
1
+ import { parseTimeToMs } from "./parseTimeToMs.js";
2
2
 
3
3
  export const durationConverter = {
4
4
  fromAttribute: (value: string): number => parseTimeToMs(value),
@@ -1,4 +1,4 @@
1
- import { EFTimegroup } from "./EFTimegroup.ts";
1
+ import { EFTimegroup } from "./EFTimegroup.js";
2
2
 
3
3
  export const getRootTimeGroup = (element: Element): EFTimegroup | null => {
4
4
  let bestCandidate: EFTimegroup | null = null;
@@ -3,11 +3,11 @@ import { customElement } from "lit/decorators/custom-element.js";
3
3
  import { describe, expect, test, vi } from "vitest";
4
4
 
5
5
  import { consume } from "@lit/context";
6
- import { ContextMixin } from "./ContextMixin.ts";
7
- import { apiHostContext } from "./apiHostContext.ts";
6
+ import { ContextMixin } from "./ContextMixin.js";
7
+ import { apiHostContext } from "./apiHostContext.js";
8
8
 
9
9
  // Required to test timeupdate event, we need a duration, and timegroups are a quick way to do that
10
- import "../elements/EFTimegroup.ts";
10
+ import "../elements/EFTimegroup.js";
11
11
 
12
12
  @customElement("test-context")
13
13
  class TestContext extends ContextMixin(LitElement) {}
@@ -3,13 +3,13 @@ import type { LitElement } from "lit";
3
3
  import { property, state } from "lit/decorators.js";
4
4
 
5
5
  import { createRef } from "lit/directives/ref.js";
6
- import type { EFTimegroup } from "../elements/EFTimegroup.ts";
7
- import { apiHostContext } from "./apiHostContext.ts";
8
- import { efContext } from "./efContext.ts";
9
- import { fetchContext } from "./fetchContext.ts";
10
- import { type FocusContext, focusContext } from "./focusContext.ts";
11
- import { focusedElementContext } from "./focusedElementContext.ts";
12
- import { loopContext, playingContext } from "./playingContext.ts";
6
+ import type { EFTimegroup } from "../elements/EFTimegroup.js";
7
+ import { apiHostContext } from "./apiHostContext.js";
8
+ import { efContext } from "./efContext.js";
9
+ import { fetchContext } from "./fetchContext.js";
10
+ import { type FocusContext, focusContext } from "./focusContext.js";
11
+ import { focusedElementContext } from "./focusedElementContext.js";
12
+ import { loopContext, playingContext } from "./playingContext.js";
13
13
 
14
14
  export declare class ContextMixinInterface extends LitElement {
15
15
  signingURL?: string;
@@ -136,18 +136,19 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
136
136
  const canvasHeight = canvasElement.clientHeight;
137
137
  const stageRatio = stageWidth / stageHeight;
138
138
  const canvasRatio = canvasWidth / canvasHeight;
139
+
139
140
  if (stageRatio > canvasRatio) {
140
141
  const scale = stageHeight / canvasHeight;
141
142
  if (this.stageScale !== scale) {
142
143
  canvasElement.style.transform = `scale(${scale})`;
143
- canvasElement.style.transformOrigin = "top";
144
+ canvasElement.style.transformOrigin = "center";
144
145
  }
145
146
  this.stageScale = scale;
146
147
  } else {
147
148
  const scale = stageWidth / canvasWidth;
148
149
  if (this.stageScale !== scale) {
149
150
  canvasElement.style.transform = `scale(${scale})`;
150
- canvasElement.style.transformOrigin = "top";
151
+ canvasElement.style.transformOrigin = "center";
151
152
  }
152
153
  this.stageScale = scale;
153
154
  }
@@ -251,6 +252,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
251
252
  await timegroup.waitForMediaDurations();
252
253
 
253
254
  let currentMs = timegroup.currentTimeMs;
255
+ const fromMs = currentMs;
256
+ const toMs = timegroup.endTimeMs;
257
+
258
+ if (fromMs >= toMs) {
259
+ this.pause();
260
+ return;
261
+ }
262
+
254
263
  let bufferCount = 0;
255
264
  this.#playbackAudioContext = new AudioContext({
256
265
  latencyHint: "playback",
@@ -280,9 +289,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
280
289
  }
281
290
  };
282
291
 
283
- const fromMs = currentMs;
284
- const toMs = timegroup.endTimeMs;
285
-
286
292
  const queueBufferSource = async () => {
287
293
  if (currentMs >= toMs) {
288
294
  return false;
@@ -17,21 +17,21 @@ import {
17
17
  import { createRef, ref } from "lit/directives/ref.js";
18
18
  import { styleMap } from "lit/directives/style-map.js";
19
19
 
20
- import { EFAudio } from "../elements/EFAudio.ts";
21
- import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.ts";
22
- import { EFImage } from "../elements/EFImage.ts";
23
- import type { TemporalMixinInterface } from "../elements/EFTemporal.ts";
24
- import { EFTimegroup } from "../elements/EFTimegroup.ts";
25
- import { EFVideo } from "../elements/EFVideo.ts";
26
- import { EFWaveform } from "../elements/EFWaveform.ts";
27
- import { TimegroupController } from "../elements/TimegroupController.ts";
28
- import { msToTimeCode } from "../msToTimeCode.ts";
29
- import type { EFPreview } from "./EFPreview.ts";
30
- import type { EFWorkbench } from "./EFWorkbench.ts";
31
- import { TWMixin } from "./TWMixin.ts";
32
- import { type FocusContext, focusContext } from "./focusContext.ts";
33
- import { focusedElementContext } from "./focusedElementContext.ts";
34
- import { loopContext, playingContext } from "./playingContext.ts";
20
+ import { EFAudio } from "../elements/EFAudio.js";
21
+ import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
22
+ import { EFImage } from "../elements/EFImage.js";
23
+ import type { TemporalMixinInterface } from "../elements/EFTemporal.js";
24
+ import { EFTimegroup } from "../elements/EFTimegroup.js";
25
+ import { EFVideo } from "../elements/EFVideo.js";
26
+ import { EFWaveform } from "../elements/EFWaveform.js";
27
+ import { TimegroupController } from "../elements/TimegroupController.js";
28
+ import { msToTimeCode } from "../msToTimeCode.js";
29
+ import type { EFPreview } from "./EFPreview.js";
30
+ import type { EFWorkbench } from "./EFWorkbench.js";
31
+ import { TWMixin } from "./TWMixin.js";
32
+ import { type FocusContext, focusContext } from "./focusContext.js";
33
+ import { focusedElementContext } from "./focusedElementContext.js";
34
+ import { loopContext, playingContext } from "./playingContext.js";
35
35
 
36
36
  class ElementFilmstripController implements ReactiveController {
37
37
  constructor(
@@ -1,9 +1,9 @@
1
- import { LitElement, html, css } from "lit";
1
+ import { LitElement, css, html } from "lit";
2
2
  import { customElement } from "lit/decorators.js";
3
3
  import { ref } from "lit/directives/ref.js";
4
4
 
5
- import { TWMixin } from "./TWMixin.ts";
6
- import { ContextMixin } from "./ContextMixin.ts";
5
+ import { ContextMixin } from "./ContextMixin.js";
6
+ import { TWMixin } from "./TWMixin.js";
7
7
 
8
8
  @customElement("ef-preview")
9
9
  export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
@@ -26,7 +26,9 @@ export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
26
26
  <slot
27
27
  ${ref(this.canvasRef)}
28
28
  class="inline-block"
29
+ name="canvas"
29
30
  ></slot>
31
+ <slot name="controls"></slot>
30
32
  </div>
31
33
  `;
32
34
  }
@@ -3,9 +3,9 @@ import { LitElement, css, html } from "lit";
3
3
  import { customElement, state } from "lit/decorators.js";
4
4
 
5
5
  import { ref } from "lit/directives/ref.js";
6
- import type { ContextMixinInterface } from "./ContextMixin.ts";
7
- import { efContext } from "./efContext.ts";
8
- import { playingContext } from "./playingContext.ts";
6
+ import type { ContextMixinInterface } from "./ContextMixin.js";
7
+ import { efContext } from "./efContext.js";
8
+ import { playingContext } from "./playingContext.js";
9
9
 
10
10
  @customElement("ef-scrubber")
11
11
  export class EFScrubber extends LitElement {
@@ -1,8 +1,8 @@
1
1
  import { consume } from "@lit/context";
2
2
  import { LitElement, css, html } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
- import type { ContextMixinInterface } from "./ContextMixin.ts";
5
- import { efContext } from "./efContext.ts";
4
+ import type { ContextMixinInterface } from "./ContextMixin.js";
5
+ import { efContext } from "./efContext.js";
6
6
 
7
7
  @customElement("ef-time-display")
8
8
  export class EFTimeDisplay extends LitElement {
@@ -1,9 +1,9 @@
1
- import { css, html, LitElement } from "lit";
2
- import { customElement } from "lit/decorators.js";
3
1
  import { consume } from "@lit/context";
2
+ import { LitElement, css, html } from "lit";
3
+ import { customElement } from "lit/decorators.js";
4
4
 
5
- import { efContext } from "./efContext.ts";
6
- import type { ContextMixinInterface } from "./ContextMixin.ts";
5
+ import type { ContextMixinInterface } from "./ContextMixin.js";
6
+ import { efContext } from "./efContext.js";
7
7
 
8
8
  @customElement("ef-toggle-loop")
9
9
  export class EFToggleLoop extends LitElement {
@@ -1,10 +1,10 @@
1
1
  import { consume } from "@lit/context";
2
2
  import { LitElement, css, html } from "lit";
3
- import { customElement, property } from "lit/decorators.js";
3
+ import { customElement } from "lit/decorators.js";
4
4
 
5
- import type { ContextMixinInterface } from "./ContextMixin.ts";
6
- import { efContext } from "./efContext.ts";
7
- import { playingContext } from "./playingContext.ts";
5
+ import type { ContextMixinInterface } from "./ContextMixin.js";
6
+ import { efContext } from "./efContext.js";
7
+ import { playingContext } from "./playingContext.js";
8
8
 
9
9
  @customElement("ef-toggle-play")
10
10
  export class EFTogglePlay extends LitElement {
@@ -23,12 +23,6 @@ export class EFTogglePlay extends LitElement {
23
23
  @consume({ context: playingContext, subscribe: true })
24
24
  playing = false;
25
25
 
26
- @property({ type: String })
27
- play = '<button class="text-2xl cursor-pointer">▶️</button>';
28
-
29
- @property({ type: String })
30
- pause = '<button class="text-2xl cursor-pointer">⏸️</button>';
31
-
32
26
  render() {
33
27
  return html`
34
28
  <div
@@ -2,8 +2,8 @@ import { LitElement, type PropertyValueMap, css, html } from "lit";
2
2
  import { customElement, eventOptions } from "lit/decorators.js";
3
3
  import { createRef, ref } from "lit/directives/ref.js";
4
4
 
5
- import { ContextMixin } from "./ContextMixin.ts";
6
- import { TWMixin } from "./TWMixin.ts";
5
+ import { ContextMixin } from "./ContextMixin.js";
6
+ import { TWMixin } from "./TWMixin.js";
7
7
 
8
8
  @customElement("ef-workbench")
9
9
  export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
@@ -1,5 +1,5 @@
1
1
  import { createContext } from "@lit/context";
2
- import type { ContextMixinInterface } from "./ContextMixin.ts";
2
+ import type { ContextMixinInterface } from "./ContextMixin.js";
3
3
 
4
4
  export const efContext = createContext<ContextMixinInterface | null>(
5
5
  Symbol("efContext"),