@editframe/elements 0.31.0-beta.0 → 0.31.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.
@@ -2,7 +2,7 @@ import { MediaEngine } from "../transcoding/types/index.js";
2
2
  import { EFMedia } from "./EFMedia.js";
3
3
  import * as _lit_task8 from "@lit/task";
4
4
  import { Task } from "@lit/task";
5
- import * as lit_html1 from "lit-html";
5
+ import * as lit_html2 from "lit-html";
6
6
  import * as lit_html_directives_ref_js1 from "lit-html/directives/ref.js";
7
7
 
8
8
  //#region src/elements/EFAudio.d.ts
@@ -20,7 +20,7 @@ declare class EFAudio extends EFAudio_base {
20
20
  volume: number;
21
21
  audioElementRef: lit_html_directives_ref_js1.Ref<HTMLAudioElement>;
22
22
  protected updated(changedProperties: Map<PropertyKey, unknown>): void;
23
- render(): lit_html1.TemplateResult<1>;
23
+ render(): lit_html2.TemplateResult<1>;
24
24
  frameTask: Task<readonly [_lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus], void>;
25
25
  /**
26
26
  * Legacy getter for fragment index task (maps to audioSegmentIdTask)
@@ -4,9 +4,9 @@ import { FetchMixinInterface } from "./FetchMixin.js";
4
4
  import { EFVideo } from "./EFVideo.js";
5
5
  import { EFAudio } from "./EFAudio.js";
6
6
  import { Task, TaskStatus } from "@lit/task";
7
- import * as lit3 from "lit";
7
+ import * as lit4 from "lit";
8
8
  import { LitElement, PropertyValueMap } from "lit";
9
- import * as lit_html3 from "lit-html";
9
+ import * as lit_html4 from "lit-html";
10
10
 
11
11
  //#region src/elements/EFCaptions.d.ts
12
12
  interface WordSegment {
@@ -25,8 +25,8 @@ interface Caption {
25
25
  }
26
26
  declare const EFCaptionsActiveWord_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
27
27
  declare class EFCaptionsActiveWord extends EFCaptionsActiveWord_base {
28
- static styles: lit3.CSSResult[];
29
- render(): lit_html3.TemplateResult<1> | undefined;
28
+ static styles: lit4.CSSResult[];
29
+ render(): lit_html4.TemplateResult<1> | undefined;
30
30
  wordStartMs: number;
31
31
  wordEndMs: number;
32
32
  wordText: string;
@@ -38,8 +38,8 @@ declare class EFCaptionsActiveWord extends EFCaptionsActiveWord_base {
38
38
  }
39
39
  declare const EFCaptionsSegment_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
40
40
  declare class EFCaptionsSegment extends EFCaptionsSegment_base {
41
- static styles: lit3.CSSResult[];
42
- render(): lit_html3.TemplateResult<1> | undefined;
41
+ static styles: lit4.CSSResult[];
42
+ render(): lit_html4.TemplateResult<1> | undefined;
43
43
  segmentStartMs: number;
44
44
  segmentEndMs: number;
45
45
  segmentText: string;
@@ -49,8 +49,8 @@ declare class EFCaptionsSegment extends EFCaptionsSegment_base {
49
49
  get durationMs(): number;
50
50
  }
51
51
  declare class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
52
- static styles: lit3.CSSResult[];
53
- render(): lit_html3.TemplateResult<1> | undefined;
52
+ static styles: lit4.CSSResult[];
53
+ render(): lit_html4.TemplateResult<1> | undefined;
54
54
  hidden: boolean;
55
55
  segmentText: string;
56
56
  segmentStartMs: number;
@@ -60,8 +60,8 @@ declare class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
60
60
  get durationMs(): number;
61
61
  }
62
62
  declare class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
63
- static styles: lit3.CSSResult[];
64
- render(): lit_html3.TemplateResult<1> | undefined;
63
+ static styles: lit4.CSSResult[];
64
+ render(): lit_html4.TemplateResult<1> | undefined;
65
65
  hidden: boolean;
66
66
  segmentText: string;
67
67
  segmentStartMs: number;
@@ -73,7 +73,7 @@ declare class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
73
73
  declare const EFCaptions_base: (new (...args: any[]) => EFSourceMixinInterface) & (new (...args: any[]) => TemporalMixinInterface) & (new (...args: any[]) => FetchMixinInterface) & typeof LitElement;
74
74
  declare class EFCaptions extends EFCaptions_base {
75
75
  #private;
76
- static styles: lit3.CSSResult[];
76
+ static styles: lit4.CSSResult[];
77
77
  targetSelector: string;
78
78
  set target(value: string);
79
79
  wordStyle: string;
@@ -96,7 +96,7 @@ declare class EFCaptions extends EFCaptions_base {
96
96
  segmentContainers: HTMLCollectionOf<EFCaptionsSegment>;
97
97
  beforeActiveWordContainers: HTMLCollectionOf<EFCaptionsBeforeActiveWord>;
98
98
  afterActiveWordContainers: HTMLCollectionOf<EFCaptionsAfterActiveWord>;
99
- render(): lit_html3.TemplateResult<1>;
99
+ render(): lit_html4.TemplateResult<1>;
100
100
  transcriptionsPath(): string | null;
101
101
  captionsPath(): string | null;
102
102
  protected md5SumLoader: Task<readonly [string, typeof fetch], any>;
@@ -3,21 +3,21 @@ import { TemporalMixinInterface } from "./EFTemporal.js";
3
3
  import { FetchMixinInterface } from "./FetchMixin.js";
4
4
  import * as _lit_task0 from "@lit/task";
5
5
  import { Task } from "@lit/task";
6
- import * as lit0 from "lit";
6
+ import * as lit1 from "lit";
7
7
  import { LitElement } from "lit";
8
- import * as lit_html0 from "lit-html";
8
+ import * as lit_html1 from "lit-html";
9
9
  import * as lit_html_directives_ref_js0 from "lit-html/directives/ref.js";
10
10
 
11
11
  //#region src/elements/EFImage.d.ts
12
12
  declare const EFImage_base: (new (...args: any[]) => TemporalMixinInterface) & (new (...args: any[]) => EFSourceMixinInterface) & (new (...args: any[]) => FetchMixinInterface) & typeof LitElement;
13
13
  declare class EFImage extends EFImage_base {
14
14
  #private;
15
- static styles: lit0.CSSResult[];
15
+ static styles: lit1.CSSResult[];
16
16
  imageRef: lit_html_directives_ref_js0.Ref<HTMLImageElement>;
17
17
  canvasRef: lit_html_directives_ref_js0.Ref<HTMLCanvasElement>;
18
18
  set assetId(value: string | null);
19
19
  get assetId(): string | null;
20
- render(): lit_html0.TemplateResult<1>;
20
+ render(): lit_html1.TemplateResult<1>;
21
21
  private isDirectUrl;
22
22
  assetPath(): string;
23
23
  get hasOwnDuration(): boolean;
@@ -9,7 +9,7 @@ import { AudioBufferState } from "./EFMedia/audioTasks/makeAudioBufferTask.js";
9
9
  import { ControllableInterface } from "../gui/Controllable.js";
10
10
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
11
11
  import * as _lit_task0 from "@lit/task";
12
- import * as lit1 from "lit";
12
+ import * as lit2 from "lit";
13
13
  import { LitElement, PropertyValueMap } from "lit";
14
14
  import * as mediabunny0 from "mediabunny";
15
15
 
@@ -34,7 +34,7 @@ declare class EFMedia extends EFMedia_base {
34
34
  */
35
35
  get requiredTracks(): "audio" | "video" | "both";
36
36
  static get observedAttributes(): string[];
37
- static styles: lit1.CSSResult[];
37
+ static styles: lit2.CSSResult[];
38
38
  /**
39
39
  * Duration in milliseconds for audio buffering ahead of current time
40
40
  * @domAttribute "audio-buffer-duration"
@@ -1,14 +1,14 @@
1
1
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
2
  import { EFTextSegment } from "./EFTextSegment.js";
3
- import * as lit8 from "lit";
3
+ import * as lit0 from "lit";
4
4
  import { LitElement, PropertyValueMap } from "lit";
5
- import * as lit_html8 from "lit-html";
5
+ import * as lit_html0 from "lit-html";
6
6
 
7
7
  //#region src/elements/EFText.d.ts
8
8
  type SplitMode = "line" | "word" | "char";
9
9
  declare const EFText_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
10
10
  declare class EFText extends EFText_base {
11
- static styles: lit8.CSSResult[];
11
+ static styles: lit0.CSSResult[];
12
12
  split: SplitMode;
13
13
  private validateSplit;
14
14
  staggerMs?: number;
@@ -19,7 +19,7 @@ declare class EFText extends EFText_base {
19
19
  private _textContent;
20
20
  private _templateElement;
21
21
  private _segmentsReadyResolvers;
22
- render(): lit_html8.TemplateResult<1>;
22
+ render(): lit_html0.TemplateResult<1>;
23
23
  set textContent(value: string | null);
24
24
  get textContent(): string;
25
25
  /**
@@ -48,6 +48,10 @@ declare class EFThumbnailStrip extends LitElement {
48
48
  private _renderRequested;
49
49
  /** Track if any thumbnails have been loaded (for ready event) */
50
50
  private _hasLoadedThumbnails;
51
+ /** Track the last epoch we loaded thumbnails for */
52
+ private _lastLoadedEpoch;
53
+ /** Track layout parameters to avoid unnecessary slot recreation */
54
+ private _lastLayoutParams;
51
55
  connectedCallback(): void;
52
56
  disconnectedCallback(): void;
53
57
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
@@ -78,6 +82,11 @@ declare class EFThumbnailStrip extends LitElement {
78
82
  private _onScroll;
79
83
  private _onContextScroll;
80
84
  private get _viewportWidth();
85
+ /**
86
+ * Watch for async content loading from child media elements.
87
+ * When media finishes loading, increment the epoch to invalidate cached thumbnails.
88
+ */
89
+ private _watchChildContentLoading;
81
90
  private _setupTargetObserver;
82
91
  private _scheduleRender;
83
92
  /**
@@ -86,6 +95,7 @@ declare class EFThumbnailStrip extends LitElement {
86
95
  private _checkAndDispatchReady;
87
96
  /**
88
97
  * Calculate thumbnail layout based on current dimensions and time range.
98
+ * Only recreates slots if layout parameters have actually changed.
89
99
  */
90
100
  private _calculateLayout;
91
101
  /**
@@ -112,6 +122,7 @@ declare class EFThumbnailStrip extends LitElement {
112
122
  private _drawThumbnailImage;
113
123
  /**
114
124
  * Load thumbnails that are visible in the current viewport.
125
+ * Skips loading if the epoch hasn't changed since last load.
115
126
  */
116
127
  private _loadVisibleThumbnails;
117
128
  /**
@@ -19,13 +19,17 @@ function isEFTimegroup(element) {
19
19
  }
20
20
  /**
21
21
  * Get identifiers for cache key generation.
22
- * Returns rootId (for cache isolation) and elementId (for element-specific caching).
22
+ * Returns rootId (for cache isolation), elementId (for element-specific caching), and epoch (for content versioning).
23
23
  */
24
24
  function getCacheIdentifiers(element) {
25
25
  const rootTemporal = findRootTemporal(element);
26
+ const rootTimegroup = rootTemporal && isEFTimegroup(rootTemporal) ? rootTemporal : null;
27
+ const rootId = rootTimegroup?.id || "default";
28
+ const epoch = rootTimegroup?.contentEpoch ?? 0;
26
29
  return {
27
- rootId: (rootTemporal && isEFTimegroup(rootTemporal) ? rootTemporal : null)?.id || "default",
28
- elementId: isEFVideo(element) ? element.src || element.id || "video" : element.id || "timegroup"
30
+ rootId,
31
+ elementId: isEFVideo(element) ? element.src || element.id || "video" : element.id || "timegroup",
32
+ epoch
29
33
  };
30
34
  }
31
35
  /** Padding in pixels for virtual rendering (render extra thumbnails beyond viewport) */
@@ -58,6 +62,8 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
58
62
  this._captureInProgress = false;
59
63
  this._renderRequested = false;
60
64
  this._hasLoadedThumbnails = false;
65
+ this._lastLoadedEpoch = null;
66
+ this._lastLayoutParams = null;
61
67
  this._onScroll = () => {
62
68
  if (!this._scrollContainer) return;
63
69
  this._currentScrollLeft = this._scrollContainer.scrollLeft;
@@ -96,7 +102,11 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
96
102
  const oldValue = this._targetElement;
97
103
  this._targetElement = value;
98
104
  this._mutationObserver?.disconnect();
99
- if (value !== oldValue) this._hasLoadedThumbnails = false;
105
+ if (value !== oldValue) {
106
+ this._hasLoadedThumbnails = false;
107
+ this._lastLoadedEpoch = null;
108
+ this._lastLayoutParams = null;
109
+ }
100
110
  if (value && value !== oldValue) this._setupTargetObserver(value);
101
111
  this.requestUpdate("targetElement", oldValue);
102
112
  }
@@ -222,6 +232,31 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
222
232
  if (this._scrollContainer) return this._scrollContainer.clientWidth - this._trackLeftOffset;
223
233
  return this._width;
224
234
  }
235
+ /**
236
+ * Watch for async content loading from child media elements.
237
+ * When media finishes loading, increment the epoch to invalidate cached thumbnails.
238
+ */
239
+ _watchChildContentLoading(target) {
240
+ const mediaElements = target.querySelectorAll("ef-video, ef-image, ef-audio");
241
+ for (const el of mediaElements) {
242
+ const mediaEngine = el.mediaEngineTask;
243
+ if (mediaEngine?.taskComplete) mediaEngine.taskComplete.then(() => {
244
+ if (this._targetElement === target) {
245
+ target.incrementContentEpoch();
246
+ this._lastLayoutParams = null;
247
+ this._scheduleRender();
248
+ }
249
+ }).catch(() => {});
250
+ const fetchTask = el.fetchImage;
251
+ if (fetchTask?.taskComplete) fetchTask.taskComplete.then(() => {
252
+ if (this._targetElement === target) {
253
+ target.incrementContentEpoch();
254
+ this._lastLayoutParams = null;
255
+ this._scheduleRender();
256
+ }
257
+ }).catch(() => {});
258
+ }
259
+ }
225
260
  _setupTargetObserver(target) {
226
261
  if (isEFVideo(target)) {
227
262
  this._mutationObserver = new MutationObserver(() => this._scheduleRender());
@@ -243,11 +278,37 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
243
278
  });
244
279
  });
245
280
  } else if (isEFTimegroup(target)) {
246
- this._mutationObserver = new MutationObserver(() => this._scheduleRender());
281
+ this._mutationObserver = new MutationObserver((mutations) => {
282
+ if (mutations.some((mutation) => {
283
+ if (mutation.type === "childList") return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
284
+ if (mutation.type === "attributes") {
285
+ const attrName = mutation.attributeName;
286
+ if (attrName === "currenttime" || attrName === "current-time" || attrName === "playing" || attrName === "loop") return false;
287
+ return attrName === "src" || attrName === "asset-id" || attrName === "style" || attrName === "transform";
288
+ }
289
+ return false;
290
+ })) {
291
+ const epochBefore = target.contentEpoch;
292
+ target.incrementContentEpoch();
293
+ if (target.contentEpoch !== epochBefore) {
294
+ this._lastLayoutParams = null;
295
+ if (mutations.some((m) => m.addedNodes.length > 0)) this._watchChildContentLoading(target);
296
+ this._scheduleRender();
297
+ }
298
+ }
299
+ });
247
300
  this._mutationObserver.observe(target, {
248
301
  childList: true,
249
- subtree: true
302
+ subtree: true,
303
+ attributes: true,
304
+ attributeFilter: [
305
+ "src",
306
+ "asset-id",
307
+ "style",
308
+ "transform"
309
+ ]
250
310
  });
311
+ this._watchChildContentLoading(target);
251
312
  if (target.durationMs === 0) {
252
313
  const checkDuration = () => {
253
314
  if (this._targetElement !== target) return;
@@ -285,19 +346,32 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
285
346
  }
286
347
  /**
287
348
  * Calculate thumbnail layout based on current dimensions and time range.
349
+ * Only recreates slots if layout parameters have actually changed.
288
350
  */
289
351
  _calculateLayout() {
290
352
  if (this._width <= 0 || this._height <= 0 || !this._targetElement) {
291
353
  this._thumbnailSlots = [];
354
+ this._lastLayoutParams = null;
292
355
  return;
293
356
  }
294
357
  const timeRange = this._getTimeRange();
295
358
  if (timeRange.endMs <= timeRange.startMs) {
296
359
  this._thumbnailSlots = [];
360
+ this._lastLayoutParams = null;
297
361
  return;
298
362
  }
299
363
  const thumbWidth = this._getEffectiveThumbnailWidth();
300
364
  const gap = this.gap;
365
+ const currentParams = {
366
+ width: this._width,
367
+ height: this._height,
368
+ startTimeMs: timeRange.startMs,
369
+ endTimeMs: timeRange.endMs,
370
+ thumbWidth,
371
+ gap
372
+ };
373
+ if (this._lastLayoutParams && this._lastLayoutParams.width === currentParams.width && this._lastLayoutParams.height === currentParams.height && this._lastLayoutParams.startTimeMs === currentParams.startTimeMs && this._lastLayoutParams.endTimeMs === currentParams.endTimeMs && this._lastLayoutParams.thumbWidth === currentParams.thumbWidth && this._lastLayoutParams.gap === currentParams.gap) return;
374
+ this._lastLayoutParams = currentParams;
301
375
  const count = Math.max(1, Math.floor((this._width + gap) / (thumbWidth + gap)));
302
376
  const pitch = count > 1 ? (this._width - thumbWidth) / (count - 1) : 0;
303
377
  const slots = [];
@@ -355,13 +429,13 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
355
429
  */
356
430
  _checkCache() {
357
431
  if (!this._targetElement) return;
358
- const { rootId, elementId } = getCacheIdentifiers(this._targetElement);
432
+ const { rootId, elementId, epoch } = getCacheIdentifiers(this._targetElement);
359
433
  for (const slot of this._thumbnailSlots) {
360
- const key = getCacheKey(rootId, elementId, slot.timeMs);
434
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
361
435
  if (sessionThumbnailCache.has(key)) {
362
436
  slot.imageData = sessionThumbnailCache.get(key);
363
437
  slot.status = "cached";
364
- }
438
+ } else slot.status = "pending";
365
439
  }
366
440
  }
367
441
  /**
@@ -447,9 +521,16 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
447
521
  }
448
522
  /**
449
523
  * Load thumbnails that are visible in the current viewport.
524
+ * Skips loading if the epoch hasn't changed since last load.
450
525
  */
451
526
  async _loadVisibleThumbnails() {
452
527
  if (this._captureInProgress || !this._targetElement) return;
528
+ if (isEFTimegroup(this._targetElement)) {
529
+ const currentEpoch = this._targetElement.contentEpoch;
530
+ if (this._lastLoadedEpoch !== null && this._lastLoadedEpoch === currentEpoch) {
531
+ if (!this._thumbnailSlots.some((s) => s.status === "pending")) return;
532
+ }
533
+ }
453
534
  const viewportWidth = this._viewportWidth;
454
535
  const scrollOffset = this._currentScrollLeft;
455
536
  const stripWidth = this._width;
@@ -475,6 +556,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
475
556
  } finally {
476
557
  this._captureInProgress = false;
477
558
  this._drawCanvas();
559
+ if (isEFTimegroup(this._targetElement)) this._lastLoadedEpoch = this._targetElement.contentEpoch;
478
560
  if (this._thumbnailSlots.some((s) => s.status === "cached") && !this._hasLoadedThumbnails) {
479
561
  this._hasLoadedThumbnails = true;
480
562
  this.dispatchEvent(new CustomEvent("thumbnails-ready", { bubbles: true }));
@@ -486,7 +568,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
486
568
  */
487
569
  async _captureTimegroupThumbnails(slots) {
488
570
  const target = this._targetElement;
489
- const { rootId, elementId } = getCacheIdentifiers(target);
571
+ const { rootId, elementId, epoch } = getCacheIdentifiers(target);
490
572
  const timegroupWidth = target.offsetWidth || 1920;
491
573
  const timegroupHeight = target.offsetHeight || 1080;
492
574
  const scale = Math.min(1, this._height / timegroupHeight, MAX_CAPTURE_WIDTH / timegroupWidth);
@@ -504,7 +586,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
504
586
  if (canvas) {
505
587
  const imageData = this._canvasToImageData(canvas);
506
588
  if (imageData) {
507
- const key = getCacheKey(rootId, elementId, slot.timeMs);
589
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
508
590
  sessionThumbnailCache.set(key, imageData, slot.timeMs, elementId);
509
591
  slot.imageData = imageData;
510
592
  slot.status = "cached";
@@ -523,7 +605,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
523
605
  */
524
606
  async _captureVideoThumbnails(slots) {
525
607
  const target = this._targetElement;
526
- const { rootId, elementId } = getCacheIdentifiers(target);
608
+ const { rootId, elementId, epoch } = getCacheIdentifiers(target);
527
609
  if (target.mediaEngineTask) await target.mediaEngineTask.taskComplete;
528
610
  const mediaEngine = target.mediaEngineTask?.value;
529
611
  if (!mediaEngine) return;
@@ -540,7 +622,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
540
622
  if (result?.thumbnail) {
541
623
  const imageData = this._canvasToImageData(result.thumbnail);
542
624
  if (imageData) {
543
- const key = getCacheKey(rootId, elementId, slot.timeMs);
625
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
544
626
  sessionThumbnailCache.set(key, imageData, slot.timeMs, elementId);
545
627
  slot.imageData = imageData;
546
628
  slot.status = "cached";