@editframe/elements 0.20.4-beta.0 → 0.23.6-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 (183) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +49 -11
  4. package/dist/_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js +7 -0
  5. package/dist/attachContextRoot.d.ts +1 -0
  6. package/dist/attachContextRoot.js +9 -0
  7. package/dist/elements/ContextProxiesController.d.ts +1 -2
  8. package/dist/elements/EFAudio.js +5 -9
  9. package/dist/elements/EFCaptions.d.ts +1 -3
  10. package/dist/elements/EFCaptions.js +112 -129
  11. package/dist/elements/EFImage.js +6 -7
  12. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -5
  13. package/dist/elements/EFMedia/AssetMediaEngine.js +36 -33
  14. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
  15. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  16. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -78
  17. package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +7 -13
  19. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -3
  20. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  21. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
  22. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  23. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
  24. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  25. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
  26. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +9 -25
  27. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -17
  28. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  29. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  30. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  31. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
  32. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  33. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  35. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
  36. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
  37. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  38. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
  40. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -18
  41. package/dist/elements/EFMedia.d.ts +19 -0
  42. package/dist/elements/EFMedia.js +44 -25
  43. package/dist/elements/EFSourceMixin.js +5 -7
  44. package/dist/elements/EFSurface.js +6 -9
  45. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  46. package/dist/elements/EFTemporal.d.ts +10 -0
  47. package/dist/elements/EFTemporal.js +100 -41
  48. package/dist/elements/EFThumbnailStrip.js +23 -73
  49. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  50. package/dist/elements/EFTimegroup.d.ts +35 -14
  51. package/dist/elements/EFTimegroup.js +138 -181
  52. package/dist/elements/EFVideo.d.ts +16 -2
  53. package/dist/elements/EFVideo.js +156 -108
  54. package/dist/elements/EFWaveform.js +23 -40
  55. package/dist/elements/SampleBuffer.js +3 -7
  56. package/dist/elements/TargetController.js +5 -5
  57. package/dist/elements/durationConverter.js +4 -4
  58. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  59. package/dist/elements/renderTemporalAudio.js +35 -0
  60. package/dist/elements/updateAnimations.js +19 -43
  61. package/dist/gui/ContextMixin.d.ts +5 -5
  62. package/dist/gui/ContextMixin.js +167 -162
  63. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  64. package/dist/gui/Controllable.d.ts +15 -0
  65. package/dist/gui/Controllable.js +9 -0
  66. package/dist/gui/EFConfiguration.js +7 -7
  67. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  68. package/dist/gui/EFControls.d.ts +18 -4
  69. package/dist/gui/EFControls.js +70 -28
  70. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  71. package/dist/gui/EFDial.d.ts +18 -0
  72. package/dist/gui/EFDial.js +141 -0
  73. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  74. package/dist/gui/EFFilmstrip.d.ts +12 -2
  75. package/dist/gui/EFFilmstrip.js +214 -129
  76. package/dist/gui/EFFitScale.js +5 -8
  77. package/dist/gui/EFFocusOverlay.js +4 -4
  78. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  79. package/dist/gui/EFPause.d.ts +23 -0
  80. package/dist/gui/EFPause.js +59 -0
  81. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  82. package/dist/gui/EFPlay.d.ts +23 -0
  83. package/dist/gui/EFPlay.js +59 -0
  84. package/dist/gui/EFPreview.d.ts +4 -0
  85. package/dist/gui/EFPreview.js +18 -9
  86. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  87. package/dist/gui/EFResizableBox.d.ts +34 -0
  88. package/dist/gui/EFResizableBox.js +547 -0
  89. package/dist/gui/EFScrubber.d.ts +9 -3
  90. package/dist/gui/EFScrubber.js +13 -13
  91. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  92. package/dist/gui/EFTimeDisplay.js +8 -8
  93. package/dist/gui/EFToggleLoop.d.ts +9 -3
  94. package/dist/gui/EFToggleLoop.js +7 -5
  95. package/dist/gui/EFTogglePlay.d.ts +12 -4
  96. package/dist/gui/EFTogglePlay.js +26 -21
  97. package/dist/gui/EFWorkbench.js +5 -5
  98. package/dist/gui/PlaybackController.d.ts +67 -0
  99. package/dist/gui/PlaybackController.js +310 -0
  100. package/dist/gui/TWMixin.js +1 -1
  101. package/dist/gui/TWMixin2.js +1 -1
  102. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  103. package/dist/gui/TargetOrContextMixin.js +98 -0
  104. package/dist/gui/efContext.d.ts +2 -2
  105. package/dist/index.d.ts +5 -0
  106. package/dist/index.js +5 -1
  107. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  108. package/dist/otel/BridgeSpanExporter.js +87 -0
  109. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  110. package/dist/otel/setupBrowserTracing.js +32 -0
  111. package/dist/otel/tracingHelpers.d.ts +34 -0
  112. package/dist/otel/tracingHelpers.js +112 -0
  113. package/dist/style.css +1 -1
  114. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  115. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  116. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  117. package/dist/utils/LRUCache.js +6 -53
  118. package/package.json +13 -5
  119. package/src/elements/ContextProxiesController.ts +10 -10
  120. package/src/elements/EFAudio.ts +1 -0
  121. package/src/elements/EFCaptions.browsertest.ts +128 -56
  122. package/src/elements/EFCaptions.ts +60 -34
  123. package/src/elements/EFImage.browsertest.ts +1 -2
  124. package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
  125. package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
  126. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  127. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
  129. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  130. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  131. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
  132. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  133. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
  134. package/src/elements/EFMedia.browsertest.ts +8 -15
  135. package/src/elements/EFMedia.ts +54 -8
  136. package/src/elements/EFSurface.browsertest.ts +2 -6
  137. package/src/elements/EFSurface.ts +1 -0
  138. package/src/elements/EFTemporal.browsertest.ts +58 -1
  139. package/src/elements/EFTemporal.ts +140 -4
  140. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  141. package/src/elements/EFThumbnailStrip.ts +1 -0
  142. package/src/elements/EFTimegroup.browsertest.ts +16 -15
  143. package/src/elements/EFTimegroup.ts +281 -275
  144. package/src/elements/EFVideo.browsertest.ts +162 -74
  145. package/src/elements/EFVideo.ts +229 -101
  146. package/src/elements/FetchContext.browsertest.ts +7 -2
  147. package/src/elements/TargetController.browsertest.ts +1 -0
  148. package/src/elements/TargetController.ts +1 -0
  149. package/src/elements/renderTemporalAudio.ts +108 -0
  150. package/src/elements/updateAnimations.browsertest.ts +181 -6
  151. package/src/elements/updateAnimations.ts +6 -6
  152. package/src/gui/ContextMixin.browsertest.ts +274 -27
  153. package/src/gui/ContextMixin.ts +230 -175
  154. package/src/gui/Controllable.browsertest.ts +258 -0
  155. package/src/gui/Controllable.ts +41 -0
  156. package/src/gui/EFControls.browsertest.ts +294 -80
  157. package/src/gui/EFControls.ts +139 -28
  158. package/src/gui/EFDial.browsertest.ts +84 -0
  159. package/src/gui/EFDial.ts +172 -0
  160. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  161. package/src/gui/EFFilmstrip.ts +213 -23
  162. package/src/gui/EFPause.browsertest.ts +202 -0
  163. package/src/gui/EFPause.ts +73 -0
  164. package/src/gui/EFPlay.browsertest.ts +202 -0
  165. package/src/gui/EFPlay.ts +73 -0
  166. package/src/gui/EFPreview.ts +20 -5
  167. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  168. package/src/gui/EFResizableBox.ts +898 -0
  169. package/src/gui/EFScrubber.ts +7 -5
  170. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  171. package/src/gui/EFTimeDisplay.ts +3 -1
  172. package/src/gui/EFToggleLoop.ts +6 -5
  173. package/src/gui/EFTogglePlay.ts +30 -23
  174. package/src/gui/PlaybackController.ts +522 -0
  175. package/src/gui/TWMixin.css +3 -0
  176. package/src/gui/TargetOrContextMixin.ts +185 -0
  177. package/src/gui/efContext.ts +2 -2
  178. package/src/otel/BridgeSpanExporter.ts +150 -0
  179. package/src/otel/setupBrowserTracing.ts +73 -0
  180. package/src/otel/tracingHelpers.ts +251 -0
  181. package/test/cache-integration-verification.browsertest.ts +1 -1
  182. package/types.json +1 -1
  183. package/dist/elements/ContextProxiesController.js +0 -69
@@ -1,44 +1,31 @@
1
1
  import { deepGetTemporalElements } from "./EFTemporal.js";
2
- const ANIMATION_PRECISION_OFFSET = .1;
3
- const DEFAULT_ANIMATION_ITERATIONS = 1;
4
- const PROGRESS_PROPERTY = "--ef-progress";
5
- const DURATION_PROPERTY = "--ef-duration";
6
- const TRANSITION_DURATION_PROPERTY = "--ef-transition-duration";
7
- const TRANSITION_OUT_START_PROPERTY = "--ef-transition-out-start";
8
- const TIMEGROUP_TAGNAME = "ef-timegroup";
9
- /**
10
- * Evaluates what the element's state should be based on the timeline
11
- */
2
+ var ANIMATION_PRECISION_OFFSET = .1;
3
+ var DEFAULT_ANIMATION_ITERATIONS = 1;
4
+ var PROGRESS_PROPERTY = "--ef-progress";
5
+ var DURATION_PROPERTY = "--ef-duration";
6
+ var TRANSITION_DURATION_PROPERTY = "--ef-transition-duration";
7
+ var TRANSITION_OUT_START_PROPERTY = "--ef-transition-out-start";
12
8
  const evaluateTemporalState = (element) => {
13
9
  const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
14
10
  const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
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);
11
+ const isRootElement = !element.parentTimegroup;
12
+ const isLastElementInComposition = element.endTimeMs === element.rootTimegroup?.endTimeMs;
13
+ const useInclusiveEnd = isRootElement || isLastElementInComposition;
18
14
  return {
19
15
  progress,
20
- isVisible,
16
+ isVisible: element.startTimeMs <= timelineTimeMs && (useInclusiveEnd ? element.endTimeMs >= timelineTimeMs : element.endTimeMs > timelineTimeMs),
21
17
  timelineTimeMs
22
18
  };
23
19
  };
24
- /**
25
- * Evaluates element visibility specifically for animation coordination
26
- * Uses inclusive end boundaries to prevent animation jumps at exact boundaries
27
- */
28
20
  const evaluateTemporalStateForAnimation = (element) => {
29
21
  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;
32
22
  return {
33
- progress,
34
- isVisible,
23
+ progress: element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs)),
24
+ isVisible: element.startTimeMs <= timelineTimeMs && element.endTimeMs >= timelineTimeMs,
35
25
  timelineTimeMs
36
26
  };
37
27
  };
38
- /**
39
- * Updates the visual state (CSS + display) to match temporal state
40
- */
41
- const updateVisualState = (element, state) => {
28
+ var updateVisualState = (element, state) => {
42
29
  element.style.setProperty(PROGRESS_PROPERTY, `${state.progress * 100}%`);
43
30
  if (!state.isVisible) {
44
31
  if (element.style.display !== "none") element.style.display = "none";
@@ -49,17 +36,13 @@ const updateVisualState = (element, state) => {
49
36
  element.style.setProperty(TRANSITION_DURATION_PROPERTY, `${element.parentTimegroup?.overlapMs ?? 0}ms`);
50
37
  element.style.setProperty(TRANSITION_OUT_START_PROPERTY, `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`);
51
38
  };
52
- /**
53
- * Coordinates animations for a single element and its subtree, using the element as the time source
54
- */
55
- const coordinateAnimationsForSingleElement = (element) => {
39
+ var coordinateAnimationsForSingleElement = (element) => {
56
40
  const animations = element.getAnimations({ subtree: true });
57
41
  for (const animation of animations) {
58
42
  if (animation.playState === "running") animation.pause();
59
43
  const effect = animation.effect;
60
44
  if (!(effect && effect instanceof KeyframeEffect)) continue;
61
- const target = effect.target;
62
- if (!target) continue;
45
+ if (!effect.target) continue;
63
46
  const timing = effect.getTiming();
64
47
  const duration = Number(timing.duration) || 0;
65
48
  const delay = Number(timing.delay) || 0;
@@ -76,8 +59,7 @@ const coordinateAnimationsForSingleElement = (element) => {
76
59
  const adjustedTime = currentTime - delay;
77
60
  const currentIteration = Math.floor(adjustedTime / duration);
78
61
  const currentIterationTime = adjustedTime % duration;
79
- const totalAnimationLength = delay + duration * iterations;
80
- const maxSafeCurrentTime = totalAnimationLength - ANIMATION_PRECISION_OFFSET;
62
+ const maxSafeCurrentTime = delay + duration * iterations - ANIMATION_PRECISION_OFFSET;
81
63
  if (currentIteration >= iterations) animation.currentTime = maxSafeCurrentTime;
82
64
  else {
83
65
  const proposedCurrentTime = Math.min(currentIterationTime, duration - ANIMATION_PRECISION_OFFSET) + delay;
@@ -85,21 +67,15 @@ const coordinateAnimationsForSingleElement = (element) => {
85
67
  }
86
68
  }
87
69
  };
88
- /**
89
- * Main function: synchronizes DOM element with timeline
90
- */
91
70
  const updateAnimations = (element) => {
92
71
  const temporalState = evaluateTemporalState(element);
93
72
  deepGetTemporalElements(element).forEach((temporalElement) => {
94
- const temporalState$1 = evaluateTemporalState(temporalElement);
95
- updateVisualState(temporalElement, temporalState$1);
73
+ updateVisualState(temporalElement, evaluateTemporalState(temporalElement));
96
74
  });
97
75
  updateVisualState(element, temporalState);
98
- const animationState = evaluateTemporalStateForAnimation(element);
99
- if (animationState.isVisible) coordinateAnimationsForSingleElement(element);
76
+ if (evaluateTemporalStateForAnimation(element).isVisible) coordinateAnimationsForSingleElement(element);
100
77
  deepGetTemporalElements(element).forEach((temporalElement) => {
101
- const childAnimationState = evaluateTemporalStateForAnimation(temporalElement);
102
- if (childAnimationState.isVisible) coordinateAnimationsForSingleElement(temporalElement);
78
+ if (evaluateTemporalStateForAnimation(temporalElement).isVisible) coordinateAnimationsForSingleElement(temporalElement);
103
79
  });
104
80
  };
105
81
  export { evaluateTemporalStateForAnimation, updateAnimations };
@@ -1,7 +1,7 @@
1
1
  import { LitElement } from 'lit';
2
- import { EFTimegroup } from '../elements/EFTimegroup.js';
3
- export declare const targetTimegroupContext: {
4
- __context__: EFTimegroup | null;
2
+ import { TemporalMixinInterface } from '../elements/EFTemporal.js';
3
+ export declare const targetTemporalContext: {
4
+ __context__: TemporalMixinInterface | null;
5
5
  };
6
6
  export declare class ContextMixinInterface extends LitElement {
7
7
  signingURL?: string;
@@ -11,8 +11,8 @@ export declare class ContextMixinInterface extends LitElement {
11
11
  loop: boolean;
12
12
  currentTimeMs: number;
13
13
  focusedElement?: HTMLElement;
14
- targetTimegroup: EFTimegroup | null;
15
- play(): void;
14
+ targetTemporal: TemporalMixinInterface | null;
15
+ play(): Promise<void>;
16
16
  pause(): void;
17
17
  }
18
18
  export declare function isContextMixin(value: any): value is ContextMixinInterface;
@@ -1,18 +1,19 @@
1
1
  import { EF_RENDERING } from "../EF_RENDERING.js";
2
- import { globalURLTokenDeduplicator } from "../transcoding/cache/URLTokenDeduplicator.js";
3
2
  import { currentTimeContext } from "./currentTimeContext.js";
4
3
  import { durationContext } from "./durationContext.js";
4
+ import { loopContext, playingContext } from "./playingContext.js";
5
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
6
+ import { isEFTemporal } from "../elements/EFTemporal.js";
7
+ import { globalURLTokenDeduplicator } from "../transcoding/cache/URLTokenDeduplicator.js";
5
8
  import { efConfigurationContext } from "./EFConfiguration.js";
6
9
  import { efContext } from "./efContext.js";
7
10
  import { fetchContext } from "./fetchContext.js";
8
11
  import { focusContext } from "./focusContext.js";
9
12
  import { focusedElementContext } from "./focusedElementContext.js";
10
- import { loopContext, playingContext } from "./playingContext.js";
11
- import { consume, createContext, provide } from "@lit/context";
13
+ import { ContextProvider, consume, createContext, provide } from "@lit/context";
12
14
  import { property, state } from "lit/decorators.js";
13
- import _decorate from "@oxc-project/runtime/helpers/decorate";
14
- const targetTimegroupContext = createContext("target-timegroup");
15
- const contextMixinSymbol = Symbol("contextMixin");
15
+ const targetTemporalContext = createContext(Symbol("target-temporal"));
16
+ var contextMixinSymbol = Symbol("contextMixin");
16
17
  function isContextMixin(value) {
17
18
  return typeof value === "object" && value !== null && contextMixinSymbol in value.constructor;
18
19
  }
@@ -23,7 +24,6 @@ function ContextMixin(superClass) {
23
24
  this.efConfiguration = null;
24
25
  this.focusContext = this;
25
26
  this.efContext = this;
26
- this.targetTimegroup = null;
27
27
  this.durationMs = 0;
28
28
  this.endTimeMs = 0;
29
29
  this.fetch = async (url, init = {}) => {
@@ -37,10 +37,7 @@ function ContextMixin(superClass) {
37
37
  method: "POST",
38
38
  body: JSON.stringify(signingPayload)
39
39
  });
40
- if (response.ok) {
41
- const tokenData = await response.json();
42
- return tokenData.token;
43
- }
40
+ if (response.ok) return (await response.json()).token;
44
41
  throw new Error(`Failed to sign URL: ${url}. SigningURL: ${this.signingURL} ${response.status} ${response.statusText}`);
45
42
  } catch (error) {
46
43
  console.error("ContextMixin urlToken fetch error", url, error);
@@ -56,14 +53,16 @@ function ContextMixin(superClass) {
56
53
  throw error;
57
54
  }
58
55
  };
59
- this.playing = false;
60
- this.loop = false;
61
56
  this.rendering = false;
62
- this.currentTimeMs = NaN;
63
57
  }
64
58
  static {
65
59
  this[contextMixinSymbol] = true;
66
60
  }
61
+ #playingProvider;
62
+ #loopProvider;
63
+ #currentTimeMsProvider;
64
+ #targetTemporalProvider;
65
+ #loop = false;
67
66
  #apiHost;
68
67
  get apiHost() {
69
68
  return this.#apiHost ?? this.efConfiguration?.apiHost ?? "";
@@ -71,15 +70,56 @@ function ContextMixin(superClass) {
71
70
  set apiHost(value) {
72
71
  this.#apiHost = value;
73
72
  }
74
- /**
75
- * Generate a cache key for URL token based on signing strategy
76
- *
77
- * Uses unified prefix + parameter matching approach:
78
- * - For transcode URLs: signs base "/api/v1/transcode" + params like {url: "source.mp4"}
79
- * - For regular URLs: signs full URL with empty params {}
80
- * - All validation uses prefix matching + exhaustive parameter matching
81
- * - Multiple transcode segments with same source share one token (reduces round-trips)
82
- */
73
+ #targetTemporal = null;
74
+ get targetTemporal() {
75
+ return this.#targetTemporal;
76
+ }
77
+ #controllerSubscribed = false;
78
+ findRootTemporal() {
79
+ const findRecursive = (element) => {
80
+ if (isEFTemporal(element)) return element;
81
+ for (const child of element.children) {
82
+ const found = findRecursive(child);
83
+ if (found) return found;
84
+ }
85
+ return null;
86
+ };
87
+ for (const child of this.children) {
88
+ const found = findRecursive(child);
89
+ if (found) return found;
90
+ }
91
+ return null;
92
+ }
93
+ set targetTemporal(value) {
94
+ if (this.#targetTemporal === value) return;
95
+ if (this.#targetTemporal?.playbackController) {
96
+ this.#targetTemporal.playbackController.removeListener(this.#onControllerUpdate);
97
+ this.#controllerSubscribed = false;
98
+ }
99
+ this.#targetTemporal = value;
100
+ this.#targetTemporalProvider?.setValue(value);
101
+ this.requestUpdate("targetTemporal");
102
+ this.requestUpdate("playing");
103
+ this.requestUpdate("loop");
104
+ this.requestUpdate("currentTimeMs");
105
+ if (value?.playbackController && this.#loop) value.playbackController.setLoop(this.#loop);
106
+ if (value && !value.playbackController) value.updateComplete?.then(() => {
107
+ if (value === this.#targetTemporal && !this.#controllerSubscribed) this.requestUpdate();
108
+ });
109
+ }
110
+ #onControllerUpdate = (event) => {
111
+ switch (event.property) {
112
+ case "playing":
113
+ this.#playingProvider.setValue(event.value);
114
+ break;
115
+ case "loop":
116
+ this.#loopProvider.setValue(event.value);
117
+ break;
118
+ case "currentTimeMs":
119
+ this.#currentTimeMsProvider.setValue(event.value);
120
+ break;
121
+ }
122
+ };
83
123
  #getTokenCacheKey(url) {
84
124
  try {
85
125
  const urlObj = new URL(url);
@@ -87,9 +127,8 @@ function ContextMixin(superClass) {
87
127
  const urlParam = urlObj.searchParams.get("url");
88
128
  if (urlParam) {
89
129
  const basePath = `${urlObj.origin}/api/v1/transcode`;
90
- const cacheKey = `${basePath}?url=${urlParam}`;
91
130
  return {
92
- cacheKey,
131
+ cacheKey: `${basePath}?url=${urlParam}`,
93
132
  signingPayload: {
94
133
  url: basePath,
95
134
  params: { url: urlParam }
@@ -108,11 +147,6 @@ function ContextMixin(superClass) {
108
147
  };
109
148
  }
110
149
  }
111
- /**
112
- * Parse JWT token to extract safe expiration time (with buffer)
113
- * @param token JWT token string
114
- * @returns Safe expiration timestamp in milliseconds (actual expiry minus buffer), or 0 if parsing fails
115
- */
116
150
  #parseTokenExpiration(token) {
117
151
  try {
118
152
  const parts = token.split(".");
@@ -124,202 +158,173 @@ function ContextMixin(superClass) {
124
158
  const exp = parsed.exp;
125
159
  const iat = parsed.iat;
126
160
  if (!exp) return 0;
127
- const lifetimeSeconds = iat ? exp - iat : 3600;
128
- const tenPercentBufferMs = lifetimeSeconds * .1 * 1e3;
129
- const fiveMinutesMs = 5 * 60 * 1e3;
130
- const bufferMs = Math.min(fiveMinutesMs, tenPercentBufferMs);
161
+ const tenPercentBufferMs = (iat ? exp - iat : 3600) * .1 * 1e3;
162
+ const bufferMs = Math.min(300 * 1e3, tenPercentBufferMs);
131
163
  return exp * 1e3 - bufferMs;
132
164
  } catch {
133
165
  return 0;
134
166
  }
135
167
  }
136
168
  #signingURL;
137
- /**
138
- * A URL that will be used to generated signed tokens for accessing media files from the
139
- * editframe API. This is used to authenticate media requests per-user.
140
- */
141
169
  get signingURL() {
142
170
  return this.#signingURL ?? this.efConfiguration?.signingURL ?? "";
143
171
  }
144
172
  set signingURL(value) {
145
173
  this.#signingURL = value;
146
174
  }
147
- #FPS = 30;
148
- #MS_PER_FRAME = 1e3 / this.#FPS;
175
+ get playing() {
176
+ return this.targetTemporal?.playbackController?.playing ?? false;
177
+ }
178
+ set playing(value) {
179
+ if (this.targetTemporal?.playbackController) this.targetTemporal.playbackController.setPlaying(value);
180
+ }
181
+ get loop() {
182
+ return this.targetTemporal?.playbackController?.loop ?? this.#loop;
183
+ }
184
+ set loop(value) {
185
+ const oldValue = this.#loop;
186
+ this.#loop = value;
187
+ if (this.targetTemporal?.playbackController) this.targetTemporal.playbackController.setLoop(value);
188
+ this.requestUpdate("loop", oldValue);
189
+ }
190
+ get currentTimeMs() {
191
+ return this.targetTemporal?.playbackController?.currentTimeMs ?? NaN;
192
+ }
193
+ set currentTimeMs(value) {
194
+ if (this.targetTemporal?.playbackController) this.targetTemporal.playbackController.setCurrentTimeMs(value);
195
+ }
149
196
  #timegroupObserver = new MutationObserver((mutations) => {
150
197
  let shouldUpdate = false;
151
198
  for (const mutation of mutations) if (mutation.type === "childList") {
152
- const newTimegroup = this.querySelector("ef-timegroup");
153
- if (newTimegroup !== this.targetTimegroup) {
154
- this.targetTimegroup = newTimegroup;
199
+ const newTemporal = this.findRootTemporal();
200
+ if (newTemporal !== this.targetTemporal) {
201
+ this.targetTemporal = newTemporal;
155
202
  shouldUpdate = true;
156
- } else if (mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
203
+ } else if (mutation.target instanceof Element && isEFTemporal(mutation.target)) shouldUpdate = true;
157
204
  } else if (mutation.type === "attributes") {
158
- const durationAffectingAttributes = [
205
+ if ([
159
206
  "duration",
160
207
  "mode",
161
208
  "trimstart",
162
209
  "trimend",
163
210
  "sourcein",
164
211
  "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;
212
+ ].includes(mutation.attributeName || "") || mutation.target instanceof Element && isEFTemporal(mutation.target)) shouldUpdate = true;
167
213
  }
168
214
  if (shouldUpdate) queueMicrotask(() => {
169
215
  this.updateDurationProperties();
170
216
  this.requestUpdate();
171
- if (this.targetTimegroup) this.targetTimegroup.requestUpdate();
217
+ if (this.targetTemporal) this.targetTemporal.requestUpdate();
172
218
  });
173
219
  });
174
- /**
175
- * Update duration properties when timegroup changes
176
- */
177
220
  updateDurationProperties() {
178
- const newDuration = this.targetTimegroup?.durationMs ?? 0;
179
- const newEndTime = this.targetTimegroup?.endTimeMs ?? 0;
221
+ const newDuration = this.targetTemporal?.durationMs ?? 0;
222
+ const newEndTime = this.targetTemporal?.endTimeMs ?? 0;
180
223
  if (this.durationMs !== newDuration) this.durationMs = newDuration;
181
224
  if (this.endTimeMs !== newEndTime) this.endTimeMs = newEndTime;
182
225
  }
183
226
  connectedCallback() {
184
227
  super.connectedCallback();
185
- this.targetTimegroup = this.querySelector("ef-timegroup");
228
+ this.#playingProvider = new ContextProvider(this, {
229
+ context: playingContext,
230
+ initialValue: this.playing
231
+ });
232
+ this.#loopProvider = new ContextProvider(this, {
233
+ context: loopContext,
234
+ initialValue: this.loop
235
+ });
236
+ this.#currentTimeMsProvider = new ContextProvider(this, {
237
+ context: currentTimeContext,
238
+ initialValue: this.currentTimeMs
239
+ });
240
+ this.#targetTemporalProvider = new ContextProvider(this, {
241
+ context: targetTemporalContext,
242
+ initialValue: this.targetTemporal
243
+ });
244
+ this.targetTemporal = this.findRootTemporal();
186
245
  this.updateDurationProperties();
187
246
  this.#timegroupObserver.observe(this, {
188
247
  childList: true,
189
248
  subtree: true,
190
249
  attributes: true
191
250
  });
192
- if (this.playing) this.startPlayback();
193
251
  }
194
252
  disconnectedCallback() {
195
253
  super.disconnectedCallback();
196
254
  this.#timegroupObserver.disconnect();
197
- this.stopPlayback();
198
- }
199
- update(changedProperties) {
200
- if (changedProperties.has("playing")) if (this.playing) this.startPlayback();
201
- else this.stopPlayback();
202
- if (changedProperties.has("currentTimeMs") && this.targetTimegroup && !Number.isNaN(this.currentTimeMs)) {
203
- if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
204
- if (this.isConnected) {
205
- if (this.targetTimegroup.currentTimeMs === this.currentTimeMs) return;
206
- this.targetTimegroup.currentTimeMs = this.currentTimeMs;
207
- }
208
- }
255
+ if (this.#targetTemporal?.playbackController) {
256
+ this.#targetTemporal.playbackController.removeListener(this.#onControllerUpdate);
257
+ this.#controllerSubscribed = false;
209
258
  }
210
- super.update(changedProperties);
211
- }
212
- play() {
213
- this.playing = true;
214
- }
215
- pause() {
216
- this.playing = false;
217
- }
218
- #playbackAudioContext = null;
219
- #playbackAnimationFrameRequest = null;
220
- #AUDIO_PLAYBACK_SLICE_MS = 47 * 1024 / 48e3 * 1e3;
221
- #syncPlayheadToAudioContext(target, startMs) {
222
- const rawTimeMs = startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1e3;
223
- const nextTimeMs = Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;
224
- if (nextTimeMs !== this.currentTimeMs) this.currentTimeMs = nextTimeMs;
225
- this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
226
- this.#syncPlayheadToAudioContext(target, startMs);
227
- });
259
+ this.pause();
228
260
  }
229
- async stopPlayback() {
230
- if (this.#playbackAudioContext) {
231
- if (this.#playbackAudioContext.state !== "closed") await this.#playbackAudioContext.close();
261
+ updated(changedProperties) {
262
+ super.updated?.(changedProperties);
263
+ if (!this.#controllerSubscribed && this.#targetTemporal?.playbackController) {
264
+ this.#targetTemporal.playbackController.addListener(this.#onControllerUpdate);
265
+ this.#controllerSubscribed = true;
266
+ if (this.#loop) this.#targetTemporal.playbackController.setLoop(this.#loop);
267
+ this.#playingProvider.setValue(this.playing);
268
+ this.#loopProvider.setValue(this.loop);
269
+ this.#currentTimeMsProvider.setValue(this.currentTimeMs);
232
270
  }
233
- if (this.#playbackAnimationFrameRequest) cancelAnimationFrame(this.#playbackAnimationFrameRequest);
234
- this.#playbackAudioContext = null;
235
- this.#playbackAnimationFrameRequest = null;
236
271
  }
237
- async startPlayback() {
238
- await this.stopPlayback();
239
- const timegroup = this.targetTimegroup;
240
- if (!timegroup) return;
241
- await timegroup.waitForMediaDurations();
242
- let currentMs = timegroup.currentTimeMs;
243
- const fromMs = currentMs;
244
- const toMs = timegroup.endTimeMs;
245
- if (fromMs >= toMs) {
246
- this.pause();
247
- return;
272
+ async play() {
273
+ if (!this.targetTemporal) {
274
+ const potentialTemporalTags = Array.from(this.children).map((el) => el.tagName.toLowerCase()).filter((tag) => tag.startsWith("ef-"));
275
+ await Promise.all(potentialTemporalTags.map((tag) => customElements.whenDefined(tag).catch(() => {})));
276
+ const foundTemporal = this.findRootTemporal();
277
+ if (foundTemporal) {
278
+ this.targetTemporal = foundTemporal;
279
+ await foundTemporal.updateComplete;
280
+ } else {
281
+ console.warn("No temporal element found to play");
282
+ return;
283
+ }
248
284
  }
249
- let bufferCount = 0;
250
- this.#playbackAudioContext = new AudioContext({ latencyHint: "playback" });
251
- if (this.#playbackAnimationFrameRequest) cancelAnimationFrame(this.#playbackAnimationFrameRequest);
252
- this.#syncPlayheadToAudioContext(timegroup, currentMs);
253
- const playbackContext = this.#playbackAudioContext;
254
- if (playbackContext.state === "suspended") {
255
- console.warn("AudioContext is suspended, media playback will not work until user has interacted with page.");
256
- this.playing = false;
257
- return;
285
+ if (!this.targetTemporal.playbackController) {
286
+ await this.targetTemporal.updateComplete;
287
+ if (!this.targetTemporal.playbackController) {
288
+ console.warn("PlaybackController not available for temporal element");
289
+ return;
290
+ }
258
291
  }
259
- await playbackContext.suspend();
260
- const fillBuffer = async () => {
261
- if (bufferCount > 2) return;
262
- const canFillBuffer = await queueBufferSource();
263
- if (canFillBuffer) fillBuffer();
264
- };
265
- const queueBufferSource = async () => {
266
- if (currentMs >= toMs) return false;
267
- const startMs = currentMs;
268
- const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
269
- currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
270
- const audioBuffer = await timegroup.renderAudio(startMs, endMs);
271
- bufferCount++;
272
- const source = playbackContext.createBufferSource();
273
- source.buffer = audioBuffer;
274
- source.connect(playbackContext.destination);
275
- source.start((startMs - fromMs) / 1e3);
276
- source.onended = () => {
277
- bufferCount--;
278
- if (endMs >= toMs) {
279
- this.pause();
280
- if (this.loop) this.updateComplete.then(() => {
281
- this.currentTimeMs = 0;
282
- this.updateComplete.then(() => {
283
- this.play();
284
- });
285
- });
286
- } else fillBuffer();
287
- };
288
- return true;
289
- };
290
- await fillBuffer();
291
- await playbackContext.resume();
292
+ this.targetTemporal.playbackController.play();
293
+ }
294
+ pause() {
295
+ if (this.targetTemporal?.playbackController) this.targetTemporal.playbackController.pause();
292
296
  }
293
297
  }
294
- _decorate([consume({
298
+ __decorate([consume({
295
299
  context: efConfigurationContext,
296
300
  subscribe: true
297
301
  })], ContextElement.prototype, "efConfiguration", void 0);
298
- _decorate([provide({ context: focusContext })], ContextElement.prototype, "focusContext", void 0);
299
- _decorate([provide({ context: focusedElementContext }), state()], ContextElement.prototype, "focusedElement", void 0);
300
- _decorate([property({
302
+ __decorate([provide({ context: focusContext })], ContextElement.prototype, "focusContext", void 0);
303
+ __decorate([provide({ context: focusedElementContext }), state()], ContextElement.prototype, "focusedElement", void 0);
304
+ __decorate([property({
301
305
  type: String,
302
306
  attribute: "api-host"
303
307
  })], ContextElement.prototype, "apiHost", null);
304
- _decorate([provide({ context: efContext })], ContextElement.prototype, "efContext", void 0);
305
- _decorate([provide({ context: targetTimegroupContext }), state()], ContextElement.prototype, "targetTimegroup", void 0);
306
- _decorate([provide({ context: durationContext }), property({ type: Number })], ContextElement.prototype, "durationMs", void 0);
307
- _decorate([property({ type: Number })], ContextElement.prototype, "endTimeMs", void 0);
308
- _decorate([provide({ context: fetchContext })], ContextElement.prototype, "fetch", void 0);
309
- _decorate([property({
308
+ __decorate([provide({ context: efContext })], ContextElement.prototype, "efContext", void 0);
309
+ __decorate([state()], ContextElement.prototype, "targetTemporal", null);
310
+ __decorate([provide({ context: durationContext }), property({ type: Number })], ContextElement.prototype, "durationMs", void 0);
311
+ __decorate([property({ type: Number })], ContextElement.prototype, "endTimeMs", void 0);
312
+ __decorate([provide({ context: fetchContext })], ContextElement.prototype, "fetch", void 0);
313
+ __decorate([property({
310
314
  type: String,
311
315
  attribute: "signing-url"
312
316
  })], ContextElement.prototype, "signingURL", null);
313
- _decorate([provide({ context: playingContext }), property({
317
+ __decorate([property({
314
318
  type: Boolean,
315
319
  reflect: true
316
- })], ContextElement.prototype, "playing", void 0);
317
- _decorate([provide({ context: loopContext }), property({
320
+ })], ContextElement.prototype, "playing", null);
321
+ __decorate([property({
318
322
  type: Boolean,
319
- reflect: true
320
- })], ContextElement.prototype, "loop", void 0);
321
- _decorate([property({ type: Boolean })], ContextElement.prototype, "rendering", void 0);
322
- _decorate([provide({ context: currentTimeContext }), property({ type: Number })], ContextElement.prototype, "currentTimeMs", void 0);
323
+ reflect: true,
324
+ attribute: "loop"
325
+ })], ContextElement.prototype, "loop", null);
326
+ __decorate([property({ type: Boolean })], ContextElement.prototype, "rendering", void 0);
327
+ __decorate([property({ type: Number })], ContextElement.prototype, "currentTimeMs", null);
323
328
  return ContextElement;
324
329
  }
325
- export { ContextMixin, isContextMixin, targetTimegroupContext };
330
+ export { ContextMixin, isContextMixin, targetTemporalContext };
File without changes
@@ -0,0 +1,15 @@
1
+ import { LitElement } from 'lit';
2
+ import { TemporalMixinInterface } from '../elements/EFTemporal.js';
3
+ import { ContextMixinInterface } from './ContextMixin.js';
4
+ export declare class ControllableInterface extends LitElement {
5
+ playing: boolean;
6
+ loop: boolean;
7
+ currentTimeMs: number;
8
+ durationMs: number;
9
+ play(): void | Promise<void>;
10
+ pause(): void;
11
+ }
12
+ export declare function isControllable(value: any): value is ControllableInterface;
13
+ export type ControllableElement = ContextMixinInterface | (TemporalMixinInterface & {
14
+ playbackController: NonNullable<TemporalMixinInterface["playbackController"]>;
15
+ });
@@ -0,0 +1,9 @@
1
+ import { isEFTemporal } from "../elements/EFTemporal.js";
2
+ import { isContextMixin } from "./ContextMixin.js";
3
+ function isControllable(value) {
4
+ if (!value || typeof value !== "object") return false;
5
+ if (isContextMixin(value)) return true;
6
+ if (isEFTemporal(value)) return value.playbackController !== void 0;
7
+ return false;
8
+ }
9
+ export { isControllable };
@@ -1,9 +1,9 @@
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
2
  import { createContext, provide } from "@lit/context";
2
3
  import { LitElement, css, html } from "lit";
3
4
  import { customElement, property } from "lit/decorators.js";
4
- import _decorate from "@oxc-project/runtime/helpers/decorate";
5
5
  const efConfigurationContext = createContext(Symbol("efConfigurationContext"));
6
- let EFConfiguration = class EFConfiguration$1 extends LitElement {
6
+ var EFConfiguration = class EFConfiguration$1 extends LitElement {
7
7
  constructor(..._args) {
8
8
  super(..._args);
9
9
  this.efConfiguration = this;
@@ -21,18 +21,18 @@ let EFConfiguration = class EFConfiguration$1 extends LitElement {
21
21
  return html`<slot></slot>`;
22
22
  }
23
23
  };
24
- _decorate([provide({ context: efConfigurationContext })], EFConfiguration.prototype, "efConfiguration", void 0);
25
- _decorate([property({
24
+ __decorate([provide({ context: efConfigurationContext })], EFConfiguration.prototype, "efConfiguration", void 0);
25
+ __decorate([property({
26
26
  type: String,
27
27
  attribute: "api-host"
28
28
  })], EFConfiguration.prototype, "apiHost", void 0);
29
- _decorate([property({
29
+ __decorate([property({
30
30
  type: String,
31
31
  attribute: "signing-url"
32
32
  })], EFConfiguration.prototype, "signingURL", void 0);
33
- _decorate([property({
33
+ __decorate([property({
34
34
  type: String,
35
35
  attribute: "media-engine"
36
36
  })], EFConfiguration.prototype, "mediaEngine", void 0);
37
- EFConfiguration = _decorate([customElement("ef-configuration")], EFConfiguration);
37
+ EFConfiguration = __decorate([customElement("ef-configuration")], EFConfiguration);
38
38
  export { EFConfiguration, efConfigurationContext };