@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
@@ -12,6 +12,11 @@ interface TemporalState {
12
12
  * Evaluates what the element's state should be based on the timeline
13
13
  */
14
14
  export declare const evaluateTemporalState: (element: AnimatableElement) => TemporalState;
15
+ /**
16
+ * Evaluates element visibility specifically for animation coordination
17
+ * Uses inclusive end boundaries to prevent animation jumps at exact boundaries
18
+ */
19
+ export declare const evaluateTemporalStateForAnimation: (element: AnimatableElement) => TemporalState;
15
20
  /**
16
21
  * Main function: synchronizes DOM element with timeline
17
22
  */
@@ -1,5 +1,5 @@
1
- import { deepGetTemporalElements, isEFTemporal } from "./EFTemporal.js";
2
- const ANIMATION_PRECISION_OFFSET = Number.EPSILON;
1
+ import { deepGetTemporalElements } from "./EFTemporal.js";
2
+ const ANIMATION_PRECISION_OFFSET = .1;
3
3
  const DEFAULT_ANIMATION_ITERATIONS = 1;
4
4
  const PROGRESS_PROPERTY = "--ef-progress";
5
5
  const DURATION_PROPERTY = "--ef-duration";
@@ -12,7 +12,23 @@ const TIMEGROUP_TAGNAME = "ef-timegroup";
12
12
  const evaluateTemporalState = (element) => {
13
13
  const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
14
14
  const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
15
- const isVisible = element.startTimeMs <= timelineTimeMs && element.endTimeMs > timelineTimeMs;
15
+ const isRootTimegroup = element.tagName.toLowerCase() === TIMEGROUP_TAGNAME && !element.parentTimegroup;
16
+ const useInclusiveEnd = isRootTimegroup;
17
+ const isVisible = element.startTimeMs <= timelineTimeMs && (useInclusiveEnd ? element.endTimeMs >= timelineTimeMs : element.endTimeMs > timelineTimeMs);
18
+ return {
19
+ progress,
20
+ isVisible,
21
+ timelineTimeMs
22
+ };
23
+ };
24
+ /**
25
+ * Evaluates element visibility specifically for animation coordination
26
+ * Uses inclusive end boundaries to prevent animation jumps at exact boundaries
27
+ */
28
+ const evaluateTemporalStateForAnimation = (element) => {
29
+ const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
30
+ const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
31
+ const isVisible = element.startTimeMs <= timelineTimeMs && element.endTimeMs >= timelineTimeMs;
16
32
  return {
17
33
  progress,
18
34
  isVisible,
@@ -34,18 +50,16 @@ const updateVisualState = (element, state) => {
34
50
  element.style.setProperty(TRANSITION_OUT_START_PROPERTY, `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`);
35
51
  };
36
52
  /**
37
- * Coordinates animations to match timeline
53
+ * Coordinates animations for a single element and its subtree, using the element as the time source
38
54
  */
39
- const coordinateAnimations = (element) => {
55
+ const coordinateAnimationsForSingleElement = (element) => {
40
56
  const animations = element.getAnimations({ subtree: true });
41
57
  for (const animation of animations) {
42
58
  if (animation.playState === "running") animation.pause();
43
59
  const effect = animation.effect;
44
60
  if (!(effect && effect instanceof KeyframeEffect)) continue;
45
61
  const target = effect.target;
46
- if (!target || target.closest(TIMEGROUP_TAGNAME) !== element) continue;
47
- const timeTarget = isEFTemporal(target) ? target : target.closest(TIMEGROUP_TAGNAME);
48
- if (!timeTarget) continue;
62
+ if (!target) continue;
49
63
  const timing = effect.getTiming();
50
64
  const duration = Number(timing.duration) || 0;
51
65
  const delay = Number(timing.delay) || 0;
@@ -54,7 +68,7 @@ const coordinateAnimations = (element) => {
54
68
  animation.currentTime = 0;
55
69
  continue;
56
70
  }
57
- const currentTime = timeTarget.ownCurrentTimeMs ?? 0;
71
+ const currentTime = element.ownCurrentTimeMs ?? 0;
58
72
  if (currentTime < delay) {
59
73
  animation.currentTime = 0;
60
74
  continue;
@@ -62,8 +76,13 @@ const coordinateAnimations = (element) => {
62
76
  const adjustedTime = currentTime - delay;
63
77
  const currentIteration = Math.floor(adjustedTime / duration);
64
78
  const currentIterationTime = adjustedTime % duration;
65
- if (currentIteration >= iterations) animation.currentTime = duration - ANIMATION_PRECISION_OFFSET;
66
- else animation.currentTime = Math.min(currentIterationTime, duration - ANIMATION_PRECISION_OFFSET) + delay;
79
+ const totalAnimationLength = delay + duration * iterations;
80
+ const maxSafeCurrentTime = totalAnimationLength - ANIMATION_PRECISION_OFFSET;
81
+ if (currentIteration >= iterations) animation.currentTime = maxSafeCurrentTime;
82
+ else {
83
+ const proposedCurrentTime = Math.min(currentIterationTime, duration - ANIMATION_PRECISION_OFFSET) + delay;
84
+ animation.currentTime = Math.min(proposedCurrentTime, maxSafeCurrentTime);
85
+ }
67
86
  }
68
87
  };
69
88
  /**
@@ -76,6 +95,11 @@ const updateAnimations = (element) => {
76
95
  updateVisualState(temporalElement, temporalState$1);
77
96
  });
78
97
  updateVisualState(element, temporalState);
79
- if (temporalState.isVisible) coordinateAnimations(element);
98
+ const animationState = evaluateTemporalStateForAnimation(element);
99
+ if (animationState.isVisible) coordinateAnimationsForSingleElement(element);
100
+ deepGetTemporalElements(element).forEach((temporalElement) => {
101
+ const childAnimationState = evaluateTemporalStateForAnimation(temporalElement);
102
+ if (childAnimationState.isVisible) coordinateAnimationsForSingleElement(temporalElement);
103
+ });
80
104
  };
81
- export { evaluateTemporalState, updateAnimations };
105
+ export { evaluateTemporalStateForAnimation, updateAnimations };
@@ -43,7 +43,7 @@ const getRenderInfo = async () => {
43
43
  case "EF-CAPTIONS": {
44
44
  const src = element.targetElement?.src;
45
45
  console.error("Processing element", element.tagName, src);
46
- assets.efCaptions.add(src);
46
+ assets.efCaptions.add(src ?? "undefined");
47
47
  break;
48
48
  }
49
49
  }
@@ -1,5 +1,7 @@
1
1
  import { EF_RENDERING } from "../EF_RENDERING.js";
2
2
  import { globalURLTokenDeduplicator } from "../transcoding/cache/URLTokenDeduplicator.js";
3
+ import { currentTimeContext } from "./currentTimeContext.js";
4
+ import { durationContext } from "./durationContext.js";
3
5
  import { efConfigurationContext } from "./EFConfiguration.js";
4
6
  import { efContext } from "./efContext.js";
5
7
  import { fetchContext } from "./fetchContext.js";
@@ -22,6 +24,8 @@ function ContextMixin(superClass) {
22
24
  this.focusContext = this;
23
25
  this.efContext = this;
24
26
  this.targetTimegroup = null;
27
+ this.durationMs = 0;
28
+ this.endTimeMs = 0;
25
29
  this.fetch = async (url, init = {}) => {
26
30
  init.headers ||= {};
27
31
  Object.assign(init.headers, { "Content-Type": "application/json" });
@@ -67,12 +71,6 @@ function ContextMixin(superClass) {
67
71
  set apiHost(value) {
68
72
  this.#apiHost = value;
69
73
  }
70
- get durationMs() {
71
- return this.targetTimegroup?.durationMs ?? 0;
72
- }
73
- get endTimeMs() {
74
- return this.targetTimegroup?.endTimeMs ?? 0;
75
- }
76
74
  /**
77
75
  * Generate a cache key for URL token based on signing strategy
78
76
  *
@@ -157,16 +155,35 @@ function ContextMixin(superClass) {
157
155
  shouldUpdate = true;
158
156
  } else if (mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
159
157
  } else if (mutation.type === "attributes") {
160
- if (mutation.attributeName === "duration" || mutation.attributeName === "mode" || mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
158
+ const durationAffectingAttributes = [
159
+ "duration",
160
+ "mode",
161
+ "trimstart",
162
+ "trimend",
163
+ "sourcein",
164
+ "sourceout"
165
+ ];
166
+ if (durationAffectingAttributes.includes(mutation.attributeName || "") || mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.tagName === "EF-VIDEO" || mutation.target.tagName === "EF-AUDIO" || mutation.target.tagName === "EF-CAPTIONS" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
161
167
  }
162
168
  if (shouldUpdate) queueMicrotask(() => {
169
+ this.updateDurationProperties();
163
170
  this.requestUpdate();
164
171
  if (this.targetTimegroup) this.targetTimegroup.requestUpdate();
165
172
  });
166
173
  });
174
+ /**
175
+ * Update duration properties when timegroup changes
176
+ */
177
+ updateDurationProperties() {
178
+ const newDuration = this.targetTimegroup?.durationMs ?? 0;
179
+ const newEndTime = this.targetTimegroup?.endTimeMs ?? 0;
180
+ if (this.durationMs !== newDuration) this.durationMs = newDuration;
181
+ if (this.endTimeMs !== newEndTime) this.endTimeMs = newEndTime;
182
+ }
167
183
  connectedCallback() {
168
184
  super.connectedCallback();
169
185
  this.targetTimegroup = this.querySelector("ef-timegroup");
186
+ this.updateDurationProperties();
170
187
  this.#timegroupObserver.observe(this, {
171
188
  childList: true,
172
189
  subtree: true,
@@ -187,10 +204,6 @@ function ContextMixin(superClass) {
187
204
  if (this.isConnected) {
188
205
  if (this.targetTimegroup.currentTimeMs === this.currentTimeMs) return;
189
206
  this.targetTimegroup.currentTimeMs = this.currentTimeMs;
190
- this.dispatchEvent(new CustomEvent("timeupdate", { detail: {
191
- currentTimeMs: this.currentTimeMs,
192
- progress: this.currentTimeMs / this.targetTimegroup.durationMs
193
- } }));
194
207
  }
195
208
  }
196
209
  }
@@ -290,8 +303,8 @@ function ContextMixin(superClass) {
290
303
  })], ContextElement.prototype, "apiHost", null);
291
304
  _decorate([provide({ context: efContext })], ContextElement.prototype, "efContext", void 0);
292
305
  _decorate([provide({ context: targetTimegroupContext }), state()], ContextElement.prototype, "targetTimegroup", void 0);
293
- _decorate([state()], ContextElement.prototype, "durationMs", null);
294
- _decorate([state()], ContextElement.prototype, "endTimeMs", null);
306
+ _decorate([provide({ context: durationContext }), property({ type: Number })], ContextElement.prototype, "durationMs", void 0);
307
+ _decorate([property({ type: Number })], ContextElement.prototype, "endTimeMs", void 0);
295
308
  _decorate([provide({ context: fetchContext })], ContextElement.prototype, "fetch", void 0);
296
309
  _decorate([property({
297
310
  type: String,
@@ -306,7 +319,7 @@ function ContextMixin(superClass) {
306
319
  reflect: true
307
320
  })], ContextElement.prototype, "loop", void 0);
308
321
  _decorate([property({ type: Boolean })], ContextElement.prototype, "rendering", void 0);
309
- _decorate([state()], ContextElement.prototype, "currentTimeMs", void 0);
322
+ _decorate([provide({ context: currentTimeContext }), property({ type: Number })], ContextElement.prototype, "currentTimeMs", void 0);
310
323
  return ContextElement;
311
324
  }
312
325
  export { ContextMixin, isContextMixin, targetTimegroupContext };
File without changes
@@ -0,0 +1,38 @@
1
+ import { LitElement } from 'lit';
2
+ import { ContextMixinInterface } from './ContextMixin.js';
3
+ /**
4
+ * EFControls provides a way to control an ef-preview element that is not a direct ancestor.
5
+ * It bridges the contexts from a target preview element to its children controls.
6
+ *
7
+ * Usage:
8
+ * ```html
9
+ * <ef-preview id="my-preview">...</ef-preview>
10
+ *
11
+ * <ef-controls target="my-preview">
12
+ * <ef-toggle-play>
13
+ * <button slot="play">Play</button>
14
+ * <button slot="pause">Pause</button>
15
+ * </ef-toggle-play>
16
+ * <ef-scrubber></ef-scrubber>
17
+ * <ef-time-display></ef-time-display>
18
+ * </ef-controls>
19
+ * ```
20
+ */
21
+ export declare class EFControls extends LitElement {
22
+ #private;
23
+ static styles: import('lit').CSSResult;
24
+ /**
25
+ * The ID of the ef-preview element to control
26
+ */
27
+ target: string;
28
+ /**
29
+ * The target element (set by TargetController)
30
+ */
31
+ targetElement: ContextMixinInterface | null;
32
+ render(): import('lit-html').TemplateResult<1>;
33
+ }
34
+ declare global {
35
+ interface HTMLElementTagNameMap {
36
+ "ef-controls": EFControls;
37
+ }
38
+ }
@@ -0,0 +1,51 @@
1
+ import { currentTimeContext } from "./currentTimeContext.js";
2
+ import { durationContext } from "./durationContext.js";
3
+ import { efConfigurationContext } from "./EFConfiguration.js";
4
+ import { efContext } from "./efContext.js";
5
+ import { fetchContext } from "./fetchContext.js";
6
+ import { focusContext } from "./focusContext.js";
7
+ import { focusedElementContext } from "./focusedElementContext.js";
8
+ import { loopContext, playingContext } from "./playingContext.js";
9
+ import { targetTimegroupContext } from "./ContextMixin.js";
10
+ import { TargetController } from "../elements/TargetController.js";
11
+ import { ContextProxyController } from "../elements/ContextProxiesController.js";
12
+ import { LitElement, css, html } from "lit";
13
+ import { customElement, property, state } from "lit/decorators.js";
14
+ import _decorate from "@oxc-project/runtime/helpers/decorate";
15
+ let EFControls = class EFControls$1 extends LitElement {
16
+ constructor(..._args) {
17
+ super(..._args);
18
+ this.target = "";
19
+ this.targetElement = null;
20
+ }
21
+ static {
22
+ this.styles = css`
23
+ :host {
24
+ display: block;
25
+ }
26
+ `;
27
+ }
28
+ #targetController = new TargetController(this);
29
+ #contextProxyController = new ContextProxyController(this, {
30
+ target: () => this.targetElement,
31
+ contexts: [
32
+ playingContext,
33
+ loopContext,
34
+ currentTimeContext,
35
+ durationContext,
36
+ targetTimegroupContext,
37
+ focusedElementContext,
38
+ efContext,
39
+ fetchContext,
40
+ focusContext,
41
+ efConfigurationContext
42
+ ]
43
+ });
44
+ render() {
45
+ return html`<slot></slot>`;
46
+ }
47
+ };
48
+ _decorate([property({ type: String })], EFControls.prototype, "target", void 0);
49
+ _decorate([state()], EFControls.prototype, "targetElement", void 0);
50
+ EFControls = _decorate([customElement("ef-controls")], EFControls);
51
+ export { EFControls };
@@ -1,5 +1,6 @@
1
1
  import { LitElement, nothing, PropertyValueMap, ReactiveController, TemplateResult } from 'lit';
2
2
  import { EFAudio } from '../elements/EFAudio.js';
3
+ import { Caption } from '../elements/EFCaptions.js';
3
4
  import { EFImage } from '../elements/EFImage.js';
4
5
  import { TemporalMixinInterface } from '../elements/EFTemporal.js';
5
6
  import { EFTimegroup } from '../elements/EFTimegroup.js';
@@ -45,7 +46,41 @@ export declare class EFVideoFilmstrip extends FilmstripItem {
45
46
  contents(): TemplateResult<1>;
46
47
  }
47
48
  export declare class EFCaptionsFilmstrip extends FilmstripItem {
48
- contents(): TemplateResult<1>;
49
+ render(): TemplateResult<1>;
50
+ renderCaptionsData(captionsData: Caption | null | undefined): TemplateResult<1>;
51
+ renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing;
52
+ }
53
+ export declare class EFCaptionsActiveWordFilmstrip extends FilmstripItem {
54
+ get captionsTrackStyles(): {
55
+ position: string;
56
+ left: string;
57
+ width: string;
58
+ };
59
+ render(): TemplateResult<1>;
60
+ }
61
+ export declare class EFCaptionsSegmentFilmstrip extends FilmstripItem {
62
+ get captionsTrackStyles(): {
63
+ position: string;
64
+ left: string;
65
+ width: string;
66
+ };
67
+ render(): TemplateResult<1>;
68
+ }
69
+ export declare class EFCaptionsBeforeWordFilmstrip extends FilmstripItem {
70
+ get captionsTrackStyles(): {
71
+ position: string;
72
+ left: string;
73
+ width: string;
74
+ };
75
+ render(): TemplateResult<1>;
76
+ }
77
+ export declare class EFCaptionsAfterWordFilmstrip extends FilmstripItem {
78
+ get captionsTrackStyles(): {
79
+ position: string;
80
+ left: string;
81
+ width: string;
82
+ };
83
+ render(): TemplateResult<1>;
49
84
  }
50
85
  export declare class EFWaveformFilmstrip extends FilmstripItem {
51
86
  contents(): TemplateResult<1>;
@@ -147,6 +182,10 @@ declare global {
147
182
  "ef-audio-filmstrip": EFAudioFilmstrip;
148
183
  "ef-video-filmstrip": EFVideoFilmstrip;
149
184
  "ef-captions-filmstrip": EFCaptionsFilmstrip;
185
+ "ef-captions-active-word-filmstrip": EFCaptionsActiveWordFilmstrip;
186
+ "ef-captions-segment-filmstrip": EFCaptionsSegmentFilmstrip;
187
+ "ef-captions-before-word-filmstrip": EFCaptionsBeforeWordFilmstrip;
188
+ "ef-captions-after-word-filmstrip": EFCaptionsAfterWordFilmstrip;
150
189
  "ef-waveform-filmstrip": EFWaveformFilmstrip;
151
190
  "ef-image-filmstrip": EFImageFilmstrip;
152
191
  "ef-html-filmstrip": EFHTMLFilmstrip;
@@ -162,11 +162,232 @@ let EFVideoFilmstrip = class EFVideoFilmstrip$1 extends FilmstripItem {
162
162
  };
163
163
  EFVideoFilmstrip = _decorate([customElement("ef-video-filmstrip")], EFVideoFilmstrip);
164
164
  let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
165
- contents() {
166
- return html` 📝 `;
165
+ render() {
166
+ const captions = this.element;
167
+ const captionsData = captions.unifiedCaptionsDataTask.value;
168
+ return html`<div style=${styleMap(this.gutterStyles)}>
169
+ <div
170
+ class="bg-slate-300 relative"
171
+ ?data-focused=${this.isFocused}
172
+ @mouseenter=${() => {
173
+ if (this.focusContext) this.focusContext.focusedElement = this.element;
174
+ }}
175
+ @mouseleave=${() => {
176
+ if (this.focusContext) this.focusContext.focusedElement = null;
177
+ }}
178
+ >
179
+ <div
180
+ ?data-focused=${this.isFocused}
181
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400 overflow-hidden"
182
+ style=${styleMap(this.trimPortionStyles)}
183
+ >
184
+ 📝 ${this.renderCaptionsData(captionsData)}
185
+ </div>
186
+ </div>
187
+ ${this.renderChildren()}
188
+ </div>`;
189
+ }
190
+ renderCaptionsData(captionsData) {
191
+ if (!captionsData) return html``;
192
+ const captions = this.element;
193
+ const rootTimegroup = captions.rootTimegroup;
194
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
195
+ const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;
196
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1e3;
197
+ const segmentElements = captionsData.segments.map((segment) => {
198
+ const isActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
199
+ return html`<div
200
+ class="absolute border border-slate-600 text-xs overflow-hidden flex items-center ${isActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-slate-100"}"
201
+ style=${styleMap({
202
+ left: `${this.pixelsPerMs * segment.start * 1e3}px`,
203
+ width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
204
+ height: "100%",
205
+ top: "0px"
206
+ })}
207
+ title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
208
+ >
209
+ <span class="px-0.5 text-[8px] ${isActive ? "font-bold" : ""}">${segment.text}</span>
210
+ </div>`;
211
+ });
212
+ return html`${segmentElements}`;
213
+ }
214
+ renderChildren() {
215
+ return super.renderChildren();
167
216
  }
168
217
  };
169
218
  EFCaptionsFilmstrip = _decorate([customElement("ef-captions-filmstrip")], EFCaptionsFilmstrip);
219
+ let EFCaptionsActiveWordFilmstrip = class EFCaptionsActiveWordFilmstrip$1 extends FilmstripItem {
220
+ get captionsTrackStyles() {
221
+ const parentCaptions = this.element.closest("ef-captions");
222
+ return {
223
+ position: "relative",
224
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
225
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
226
+ };
227
+ }
228
+ render() {
229
+ const parentCaptions = this.element.closest("ef-captions");
230
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
231
+ if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
232
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
233
+ 🗣️ Active Word
234
+ </div>
235
+ </div>`;
236
+ const rootTimegroup = parentCaptions.rootTimegroup;
237
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
238
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
239
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1e3;
240
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
241
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
242
+ ${captionsData.word_segments.map((word) => {
243
+ const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
244
+ return html`<div
245
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-blue-50 border-blue-200"}"
246
+ style=${styleMap({
247
+ left: `${this.pixelsPerMs * word.start * 1e3}px`,
248
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
249
+ height: "100%",
250
+ top: "0px"
251
+ })}
252
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
253
+ >
254
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-yellow-200">${word.text.trim()}</span>` : ""}
255
+ </div>`;
256
+ })}
257
+ </div>
258
+ </div>`;
259
+ }
260
+ };
261
+ EFCaptionsActiveWordFilmstrip = _decorate([customElement("ef-captions-active-word-filmstrip")], EFCaptionsActiveWordFilmstrip);
262
+ let EFCaptionsSegmentFilmstrip = class EFCaptionsSegmentFilmstrip$1 extends FilmstripItem {
263
+ get captionsTrackStyles() {
264
+ const parentCaptions = this.element.closest("ef-captions");
265
+ return {
266
+ position: "relative",
267
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
268
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
269
+ };
270
+ }
271
+ render() {
272
+ const parentCaptions = this.element.closest("ef-captions");
273
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
274
+ if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
275
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
276
+ 📄 Segment
277
+ </div>
278
+ </div>`;
279
+ const rootTimegroup = parentCaptions.rootTimegroup;
280
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
281
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
282
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1e3;
283
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
284
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
285
+ ${captionsData.segments.map((segment) => {
286
+ const isCurrentlyActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
287
+ return html`<div
288
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-green-50 border-green-200"}"
289
+ style=${styleMap({
290
+ left: `${this.pixelsPerMs * segment.start * 1e3}px`,
291
+ width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
292
+ height: "100%",
293
+ top: "0px"
294
+ })}
295
+ title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
296
+ >
297
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-green-200">${segment.text}</span>` : ""}
298
+ </div>`;
299
+ })}
300
+ </div>
301
+ </div>`;
302
+ }
303
+ };
304
+ EFCaptionsSegmentFilmstrip = _decorate([customElement("ef-captions-segment-filmstrip")], EFCaptionsSegmentFilmstrip);
305
+ let EFCaptionsBeforeWordFilmstrip = class EFCaptionsBeforeWordFilmstrip$1 extends FilmstripItem {
306
+ get captionsTrackStyles() {
307
+ const parentCaptions = this.element.closest("ef-captions");
308
+ return {
309
+ position: "relative",
310
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
311
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
312
+ };
313
+ }
314
+ render() {
315
+ const parentCaptions = this.element.closest("ef-captions");
316
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
317
+ if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
318
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
319
+ ⬅️ Before
320
+ </div>
321
+ </div>`;
322
+ const rootTimegroup = parentCaptions.rootTimegroup;
323
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
324
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
325
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1e3;
326
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
327
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
328
+ ${captionsData.word_segments.map((word) => {
329
+ const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
330
+ return html`<div
331
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
332
+ style=${styleMap({
333
+ left: `${this.pixelsPerMs * word.start * 1e3}px`,
334
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
335
+ height: "100%",
336
+ top: "0px"
337
+ })}
338
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
339
+ >
340
+ <!-- No text for before tracks - they're redundant -->
341
+ </div>`;
342
+ })}
343
+ </div>
344
+ </div>`;
345
+ }
346
+ };
347
+ EFCaptionsBeforeWordFilmstrip = _decorate([customElement("ef-captions-before-word-filmstrip")], EFCaptionsBeforeWordFilmstrip);
348
+ let EFCaptionsAfterWordFilmstrip = class EFCaptionsAfterWordFilmstrip$1 extends FilmstripItem {
349
+ get captionsTrackStyles() {
350
+ const parentCaptions = this.element.closest("ef-captions");
351
+ return {
352
+ position: "relative",
353
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
354
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
355
+ };
356
+ }
357
+ render() {
358
+ const parentCaptions = this.element.closest("ef-captions");
359
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
360
+ if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
361
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
362
+ ➡️ After
363
+ </div>
364
+ </div>`;
365
+ const rootTimegroup = parentCaptions.rootTimegroup;
366
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
367
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
368
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1e3;
369
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
370
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
371
+ ${captionsData.word_segments.map((word) => {
372
+ const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
373
+ return html`<div
374
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
375
+ style=${styleMap({
376
+ left: `${this.pixelsPerMs * word.start * 1e3}px`,
377
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
378
+ height: "100%",
379
+ top: "0px"
380
+ })}
381
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
382
+ >
383
+ <!-- No text for after tracks - they're redundant -->
384
+ </div>`;
385
+ })}
386
+ </div>
387
+ </div>`;
388
+ }
389
+ };
390
+ EFCaptionsAfterWordFilmstrip = _decorate([customElement("ef-captions-after-word-filmstrip")], EFCaptionsAfterWordFilmstrip);
170
391
  let EFWaveformFilmstrip = class EFWaveformFilmstrip$1 extends FilmstripItem {
171
392
  contents() {
172
393
  return html` 🌊 `;
@@ -369,6 +590,22 @@ const renderFilmstripChildren = (children, pixelsPerMs) => {
369
590
  .element=${child}
370
591
  .pixelsPerMs=${pixelsPerMs}
371
592
  ></ef-captions-filmstrip>`;
593
+ if (child instanceof EFCaptionsActiveWord) return html`<ef-captions-active-word-filmstrip
594
+ .element=${child}
595
+ .pixelsPerMs=${pixelsPerMs}
596
+ ></ef-captions-active-word-filmstrip>`;
597
+ if (child.tagName === "EF-CAPTIONS-SEGMENT") return html`<ef-captions-segment-filmstrip
598
+ .element=${child}
599
+ .pixelsPerMs=${pixelsPerMs}
600
+ ></ef-captions-segment-filmstrip>`;
601
+ if (child.tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD") return html`<ef-captions-before-word-filmstrip
602
+ .element=${child}
603
+ .pixelsPerMs=${pixelsPerMs}
604
+ ></ef-captions-before-word-filmstrip>`;
605
+ if (child.tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD") return html`<ef-captions-after-word-filmstrip
606
+ .element=${child}
607
+ .pixelsPerMs=${pixelsPerMs}
608
+ ></ef-captions-after-word-filmstrip>`;
372
609
  if (child instanceof EFWaveform) return html`<ef-waveform-filmstrip
373
610
  .element=${child}
374
611
  .pixelsPerMs=${pixelsPerMs}
@@ -551,7 +788,7 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
551
788
  @mousedown=${this.startScrub}
552
789
  >
553
790
  <div
554
- class="border-red pointer-events-none absolute z-10 h-full w-[2px] border-r-2 border-red-700"
791
+ class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2 border-red-700"
555
792
  style=${styleMap({
556
793
  left: `${this.pixelsPerMs * this.currentTimeMs}px`,
557
794
  top: `${this.timelineScrolltop}px`
@@ -1,11 +1,12 @@
1
1
  import { focusedElementContext } from "./focusedElementContext.js";
2
2
  import { ContextMixin } from "./ContextMixin.js";
3
+ import { EFTargetable } from "../elements/TargetController.js";
3
4
  import { TWMixin } from "./TWMixin2.js";
4
5
  import { provide } from "@lit/context";
5
6
  import { LitElement, css, html } from "lit";
6
7
  import { customElement } from "lit/decorators.js";
7
8
  import _decorate from "@oxc-project/runtime/helpers/decorate";
8
- let EFPreview = class EFPreview$1 extends ContextMixin(TWMixin(LitElement)) {
9
+ let EFPreview = class EFPreview$1 extends EFTargetable(ContextMixin(TWMixin(LitElement))) {
9
10
  static {
10
11
  this.styles = [css`
11
12
  :host {
@@ -4,14 +4,15 @@ export declare class EFScrubber extends LitElement {
4
4
  static styles: import('lit').CSSResult[];
5
5
  context?: ContextMixinInterface | null;
6
6
  playing: boolean;
7
- private lastTimeUpdateProgress;
7
+ currentTimeMs: number;
8
+ durationMs: number;
8
9
  private scrubProgress;
9
- private isDragging;
10
+ private isMoving;
10
11
  private scrubberRef?;
11
12
  private updateProgress;
12
- private boundHandleMouseDown;
13
- private boundHandleMouseMove;
14
- private boundHandleMouseUp;
13
+ private boundHandlePointerDown;
14
+ private boundHandlePointerMove;
15
+ private boundHandlePointerUp;
15
16
  render(): import('lit-html').TemplateResult<1>;
16
17
  connectedCallback(): void;
17
18
  disconnectedCallback(): void;