@editframe/elements 0.21.0-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 (142) hide show
  1. package/dist/EF_FRAMEGEN.js +2 -3
  2. package/dist/attachContextRoot.d.ts +1 -0
  3. package/dist/attachContextRoot.js +9 -0
  4. package/dist/elements/ContextProxiesController.d.ts +1 -2
  5. package/dist/elements/EFAudio.js +2 -2
  6. package/dist/elements/EFCaptions.d.ts +1 -3
  7. package/dist/elements/EFCaptions.js +59 -51
  8. package/dist/elements/EFImage.js +2 -2
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -2
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +1 -3
  11. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  12. package/dist/elements/EFMedia/BufferedSeekingInput.js +2 -4
  13. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
  14. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -2
  15. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -9
  16. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -3
  17. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +4 -7
  18. package/dist/elements/EFMedia.d.ts +19 -0
  19. package/dist/elements/EFMedia.js +19 -2
  20. package/dist/elements/EFSourceMixin.js +1 -1
  21. package/dist/elements/EFSurface.js +1 -1
  22. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  23. package/dist/elements/EFTemporal.d.ts +10 -0
  24. package/dist/elements/EFTemporal.js +82 -5
  25. package/dist/elements/EFThumbnailStrip.js +9 -16
  26. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  27. package/dist/elements/EFTimegroup.d.ts +35 -14
  28. package/dist/elements/EFTimegroup.js +72 -120
  29. package/dist/elements/EFVideo.d.ts +10 -0
  30. package/dist/elements/EFVideo.js +15 -2
  31. package/dist/elements/EFWaveform.js +10 -18
  32. package/dist/elements/SampleBuffer.js +1 -2
  33. package/dist/elements/TargetController.js +2 -2
  34. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  35. package/dist/elements/renderTemporalAudio.js +35 -0
  36. package/dist/elements/updateAnimations.js +7 -10
  37. package/dist/gui/ContextMixin.d.ts +5 -5
  38. package/dist/gui/ContextMixin.js +151 -117
  39. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  40. package/dist/gui/Controllable.d.ts +15 -0
  41. package/dist/gui/Controllable.js +9 -0
  42. package/dist/gui/EFConfiguration.js +1 -1
  43. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  44. package/dist/gui/EFControls.d.ts +18 -4
  45. package/dist/gui/EFControls.js +67 -25
  46. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  47. package/dist/gui/EFDial.d.ts +18 -0
  48. package/dist/gui/EFDial.js +141 -0
  49. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  50. package/dist/gui/EFFilmstrip.d.ts +12 -2
  51. package/dist/gui/EFFilmstrip.js +140 -34
  52. package/dist/gui/EFFitScale.js +2 -4
  53. package/dist/gui/EFFocusOverlay.js +1 -1
  54. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  55. package/dist/gui/EFPause.d.ts +23 -0
  56. package/dist/gui/EFPause.js +59 -0
  57. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  58. package/dist/gui/EFPlay.d.ts +23 -0
  59. package/dist/gui/EFPlay.js +59 -0
  60. package/dist/gui/EFPreview.d.ts +4 -0
  61. package/dist/gui/EFPreview.js +15 -6
  62. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  63. package/dist/gui/EFResizableBox.d.ts +34 -0
  64. package/dist/gui/EFResizableBox.js +547 -0
  65. package/dist/gui/EFScrubber.d.ts +9 -3
  66. package/dist/gui/EFScrubber.js +7 -7
  67. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  68. package/dist/gui/EFTimeDisplay.js +5 -5
  69. package/dist/gui/EFToggleLoop.d.ts +9 -3
  70. package/dist/gui/EFToggleLoop.js +6 -4
  71. package/dist/gui/EFTogglePlay.d.ts +12 -4
  72. package/dist/gui/EFTogglePlay.js +24 -19
  73. package/dist/gui/EFWorkbench.js +1 -1
  74. package/dist/gui/PlaybackController.d.ts +67 -0
  75. package/dist/gui/PlaybackController.js +310 -0
  76. package/dist/gui/TWMixin.js +1 -1
  77. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  78. package/dist/gui/TargetOrContextMixin.js +98 -0
  79. package/dist/gui/efContext.d.ts +2 -2
  80. package/dist/index.d.ts +4 -0
  81. package/dist/index.js +5 -1
  82. package/dist/otel/setupBrowserTracing.d.ts +1 -1
  83. package/dist/otel/setupBrowserTracing.js +6 -4
  84. package/dist/otel/tracingHelpers.js +1 -2
  85. package/dist/style.css +1 -1
  86. package/package.json +5 -5
  87. package/src/elements/ContextProxiesController.ts +10 -10
  88. package/src/elements/EFAudio.ts +1 -0
  89. package/src/elements/EFCaptions.browsertest.ts +128 -58
  90. package/src/elements/EFCaptions.ts +60 -34
  91. package/src/elements/EFImage.browsertest.ts +1 -2
  92. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  93. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  94. package/src/elements/EFMedia.browsertest.ts +8 -15
  95. package/src/elements/EFMedia.ts +38 -7
  96. package/src/elements/EFSurface.browsertest.ts +2 -6
  97. package/src/elements/EFSurface.ts +1 -0
  98. package/src/elements/EFTemporal.browsertest.ts +58 -1
  99. package/src/elements/EFTemporal.ts +140 -4
  100. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  101. package/src/elements/EFThumbnailStrip.ts +1 -0
  102. package/src/elements/EFTimegroup.browsertest.ts +6 -7
  103. package/src/elements/EFTimegroup.ts +162 -244
  104. package/src/elements/EFVideo.browsertest.ts +143 -47
  105. package/src/elements/EFVideo.ts +26 -0
  106. package/src/elements/FetchContext.browsertest.ts +7 -2
  107. package/src/elements/TargetController.browsertest.ts +1 -0
  108. package/src/elements/TargetController.ts +1 -0
  109. package/src/elements/renderTemporalAudio.ts +108 -0
  110. package/src/elements/updateAnimations.browsertest.ts +181 -6
  111. package/src/elements/updateAnimations.ts +6 -6
  112. package/src/gui/ContextMixin.browsertest.ts +274 -27
  113. package/src/gui/ContextMixin.ts +230 -175
  114. package/src/gui/Controllable.browsertest.ts +258 -0
  115. package/src/gui/Controllable.ts +41 -0
  116. package/src/gui/EFControls.browsertest.ts +294 -80
  117. package/src/gui/EFControls.ts +139 -28
  118. package/src/gui/EFDial.browsertest.ts +84 -0
  119. package/src/gui/EFDial.ts +172 -0
  120. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  121. package/src/gui/EFFilmstrip.ts +213 -23
  122. package/src/gui/EFPause.browsertest.ts +202 -0
  123. package/src/gui/EFPause.ts +73 -0
  124. package/src/gui/EFPlay.browsertest.ts +202 -0
  125. package/src/gui/EFPlay.ts +73 -0
  126. package/src/gui/EFPreview.ts +20 -5
  127. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  128. package/src/gui/EFResizableBox.ts +898 -0
  129. package/src/gui/EFScrubber.ts +7 -5
  130. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  131. package/src/gui/EFTimeDisplay.ts +3 -1
  132. package/src/gui/EFToggleLoop.ts +6 -5
  133. package/src/gui/EFTogglePlay.ts +30 -23
  134. package/src/gui/PlaybackController.ts +522 -0
  135. package/src/gui/TWMixin.css +3 -0
  136. package/src/gui/TargetOrContextMixin.ts +185 -0
  137. package/src/gui/efContext.ts +2 -2
  138. package/src/otel/setupBrowserTracing.ts +17 -12
  139. package/test/cache-integration-verification.browsertest.ts +1 -1
  140. package/types.json +1 -1
  141. package/dist/elements/ContextProxiesController.js +0 -49
  142. /package/dist/_virtual/{_@oxc-project_runtime@0.93.0 → _@oxc-project_runtime@0.94.0}/helpers/decorate.js +0 -0
@@ -93,10 +93,20 @@ export declare class EFVideo extends EFVideo_base {
93
93
  * Still used by EFCaptions - maps to unified video seek task
94
94
  */
95
95
  get fragmentIndexTask(): Task<readonly [number], import('mediabunny').VideoSample | undefined>;
96
+ /**
97
+ * Helper method for tests: wait for the current frame to be ready
98
+ * This encapsulates the complexity of ensuring the video has updated
99
+ * and its frameTask has completed.
100
+ *
101
+ * @returns Promise that resolves when the frame is ready
102
+ */
103
+ waitForFrameReady(): Promise<void>;
96
104
  /**
97
105
  * Clean up resources when component is disconnected
98
106
  */
99
107
  disconnectedCallback(): void;
108
+ didBecomeRoot(): void;
109
+ didBecomeChild(): void;
100
110
  }
101
111
  declare global {
102
112
  interface HTMLElementTagNameMap {
@@ -1,7 +1,8 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
2
+ import { TWMixin } from "../gui/TWMixin2.js";
2
3
  import { withSpan, withSpanSync } from "../otel/tracingHelpers.js";
3
4
  import { EFMedia } from "./EFMedia.js";
4
- import { TWMixin } from "../gui/TWMixin2.js";
5
+ import { updateAnimations } from "./updateAnimations.js";
5
6
  import { DelayedLoadingState } from "../DelayedLoadingState.js";
6
7
  import { makeScrubVideoBufferTask } from "./EFMedia/videoTasks/makeScrubVideoBufferTask.js";
7
8
  import { makeScrubVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js";
@@ -125,6 +126,8 @@ var EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
125
126
  span.setAttribute("aborted", true);
126
127
  return;
127
128
  }
129
+ this.paint(this.desiredSeekTimeMs, span);
130
+ if (!this.parentTimegroup) updateAnimations(this);
128
131
  const t4 = performance.now();
129
132
  this.paint(_desiredSeekTimeMs, span);
130
133
  const t5 = performance.now();
@@ -303,10 +306,20 @@ var EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
303
306
  get fragmentIndexTask() {
304
307
  return this.unifiedVideoSeekTask;
305
308
  }
309
+ async waitForFrameReady() {
310
+ await this.updateComplete;
311
+ await this.frameTask.run();
312
+ }
306
313
  disconnectedCallback() {
307
314
  super.disconnectedCallback();
308
315
  this.delayedLoadingState.clearAllLoading();
309
316
  }
317
+ didBecomeRoot() {
318
+ super.didBecomeRoot();
319
+ }
320
+ didBecomeChild() {
321
+ super.didBecomeChild();
322
+ }
310
323
  };
311
324
  __decorate([property({
312
325
  type: Number,
@@ -1,8 +1,8 @@
1
1
  import { EF_RENDERING } from "../EF_RENDERING.js";
2
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
2
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
3
3
  import { EFTemporal } from "./EFTemporal.js";
4
- import { TargetController } from "./TargetController.js";
5
4
  import { TWMixin } from "../gui/TWMixin2.js";
5
+ import { TargetController } from "./TargetController.js";
6
6
  import { CrossUpdateController } from "./CrossUpdateController.js";
7
7
  import { Task } from "@lit/task";
8
8
  import { LitElement, css, html } from "lit";
@@ -271,13 +271,11 @@ var EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
271
271
  frequencyData.forEach((value, i) => {
272
272
  const normalizedValue = Math.min(value / 255 * 2, 1);
273
273
  const x = startX + i / (frequencyData.length - 1) * availableWidth;
274
- const barHeight = normalizedValue * waveHeight;
275
- const y = (waveHeight - barHeight) / 2;
274
+ const y = (waveHeight - normalizedValue * waveHeight) / 2;
276
275
  if (i === 0) path.moveTo(x, y);
277
276
  else {
278
277
  const prevX = startX + (i - 1) / (frequencyData.length - 1) * availableWidth;
279
- const prevBarHeight = Math.min((frequencyData[i - 1] ?? 0) / 255 * 2, 1) * waveHeight;
280
- const prevY = (waveHeight - prevBarHeight) / 2;
278
+ const prevY = (waveHeight - Math.min((frequencyData[i - 1] ?? 0) / 255 * 2, 1) * waveHeight) / 2;
281
279
  const xc = (prevX + x) / 2;
282
280
  const yc = (prevY + y) / 2;
283
281
  path.quadraticCurveTo(prevX, prevY, xc, yc);
@@ -286,13 +284,11 @@ var EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
286
284
  for (let i = frequencyData.length - 1; i >= 0; i--) {
287
285
  const normalizedValue = Math.min((frequencyData[i] ?? 0) / 255 * 2, 1);
288
286
  const x = startX + i / (frequencyData.length - 1) * availableWidth;
289
- const barHeight = normalizedValue * waveHeight;
290
- const y = (waveHeight + barHeight) / 2;
287
+ const y = (waveHeight + normalizedValue * waveHeight) / 2;
291
288
  if (i === frequencyData.length - 1) path.lineTo(x, y);
292
289
  else {
293
290
  const nextX = startX + (i + 1) / (frequencyData.length - 1) * availableWidth;
294
- const nextBarHeight = Math.min((frequencyData[i + 1] ?? 0) / 255 * 2, 1) * waveHeight;
295
- const nextY = (waveHeight + nextBarHeight) / 2;
291
+ const nextY = (waveHeight + Math.min((frequencyData[i + 1] ?? 0) / 255 * 2, 1) * waveHeight) / 2;
296
292
  const xc = (nextX + x) / 2;
297
293
  const yc = (nextY + y) / 2;
298
294
  path.quadraticCurveTo(nextX, nextY, xc, yc);
@@ -319,13 +315,11 @@ var EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
319
315
  frequencyData.forEach((value, i) => {
320
316
  const normalizedValue = Math.min(value / 255 * 2, 1);
321
317
  const x = startX + i / (frequencyData.length - 1) * availableWidth;
322
- const barHeight = normalizedValue * (waveHeight / 2);
323
- const y = (waveHeight - barHeight * 2) / 2;
318
+ const y = (waveHeight - normalizedValue * (waveHeight / 2) * 2) / 2;
324
319
  if (i === 0) path.moveTo(x, y);
325
320
  else {
326
321
  const prevX = startX + (i - 1) / (frequencyData.length - 1) * availableWidth;
327
- const prevBarHeight = (frequencyData[i - 1] ?? 0) / 255 * (waveHeight / 2);
328
- const prevY = (waveHeight - prevBarHeight * 2) / 2;
322
+ const prevY = (waveHeight - (frequencyData[i - 1] ?? 0) / 255 * (waveHeight / 2) * 2) / 2;
329
323
  const xc = (prevX + x) / 2;
330
324
  const yc = (prevY + y) / 2;
331
325
  path.quadraticCurveTo(prevX, prevY, xc, yc);
@@ -334,13 +328,11 @@ var EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
334
328
  for (let i = frequencyData.length - 1; i >= 0; i--) {
335
329
  const normalizedValue = Math.min((frequencyData[i] ?? 0) / 255 * 2, 1);
336
330
  const x = startX + i / (frequencyData.length - 1) * availableWidth;
337
- const barHeight = normalizedValue * (waveHeight / 2);
338
- const y = (waveHeight + barHeight * 2) / 2;
331
+ const y = (waveHeight + normalizedValue * (waveHeight / 2) * 2) / 2;
339
332
  if (i === frequencyData.length - 1) path.lineTo(x, y);
340
333
  else {
341
334
  const nextX = startX + (i + 1) / (frequencyData.length - 1) * availableWidth;
342
- const nextBarHeight = (frequencyData[i + 1] ?? 0) / 255 * (waveHeight / 2);
343
- const nextY = (waveHeight + nextBarHeight * 2) / 2;
335
+ const nextY = (waveHeight + (frequencyData[i + 1] ?? 0) / 255 * (waveHeight / 2) * 2) / 2;
344
336
  const xc = (nextX + x) / 2;
345
337
  const yc = (nextY + y) / 2;
346
338
  path.quadraticCurveTo(nextX, nextY, xc, yc);
@@ -31,8 +31,7 @@ var SampleBuffer = class {
31
31
  const targetTimeMs = roundToMilliseconds(desiredSeekTimeMs);
32
32
  for (const sample of currentBuffer) {
33
33
  const sampleStartMs = roundToMilliseconds((sample.timestamp || 0) * 1e3);
34
- const sampleDurationMs = roundToMilliseconds((sample.duration || 0) * 1e3);
35
- const sampleEndMs = roundToMilliseconds(sampleStartMs + sampleDurationMs);
34
+ const sampleEndMs = roundToMilliseconds(sampleStartMs + roundToMilliseconds((sample.duration || 0) * 1e3));
36
35
  if (targetTimeMs >= sampleStartMs && targetTimeMs < sampleEndMs) return sample;
37
36
  }
38
37
  }
@@ -103,6 +103,7 @@ var TargetController = class {
103
103
  this.disconnectFromTarget();
104
104
  this.host.targetElement = newTarget ?? null;
105
105
  this.connectToTarget();
106
+ this.host.requestUpdate("targetElement");
106
107
  }
107
108
  }
108
109
  connectToTarget() {
@@ -118,8 +119,7 @@ var TargetController = class {
118
119
  }
119
120
  }
120
121
  get registry() {
121
- const root = this.host.getRootNode();
122
- return getRegistry(root);
122
+ return getRegistry(this.host.getRootNode());
123
123
  }
124
124
  hostDisconnected() {
125
125
  this.disconnectFromTarget();
@@ -0,0 +1,10 @@
1
+ import { EFMedia } from './EFMedia.js';
2
+ interface TemporalAudioHost {
3
+ startTimeMs: number;
4
+ endTimeMs: number;
5
+ durationMs: number;
6
+ getMediaElements(): EFMedia[];
7
+ waitForMediaDurations?(): Promise<void>;
8
+ }
9
+ export declare function renderTemporalAudio(host: TemporalAudioHost, fromMs: number, toMs: number): Promise<AudioBuffer>;
10
+ export {};
@@ -0,0 +1,35 @@
1
+ async function renderTemporalAudio(host, fromMs, toMs) {
2
+ const aacFrames = 48e3 * ((toMs - fromMs) / 1e3) / 1024;
3
+ const contextSize = Math.round(aacFrames) * 1024;
4
+ if (contextSize <= 0) throw new Error(`Duration must be greater than 0 when rendering audio. ${contextSize}ms`);
5
+ const audioContext = new OfflineAudioContext(2, contextSize, 48e3);
6
+ if (host.waitForMediaDurations) await host.waitForMediaDurations();
7
+ const abortController = new AbortController();
8
+ await Promise.all(host.getMediaElements().map(async (mediaElement) => {
9
+ if (mediaElement.mute) return;
10
+ const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
11
+ const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
12
+ if (!(mediaStartsBeforeEnd && mediaEndsAfterStart)) return;
13
+ const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
14
+ const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
15
+ if (mediaLocalFromMs >= mediaLocalToMs) return;
16
+ const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
17
+ const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
18
+ const mediaSourceToMs = mediaLocalToMs + sourceInMs;
19
+ const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
20
+ if (!audio) return;
21
+ const bufferSource = audioContext.createBufferSource();
22
+ bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
23
+ bufferSource.connect(audioContext.destination);
24
+ const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
25
+ const offsetInBufferMs = mediaSourceFromMs - audio.startMs;
26
+ const safeOffsetMs = Math.max(0, offsetInBufferMs);
27
+ const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
28
+ const availableAudioMs = audio.endMs - audio.startMs;
29
+ const actualDurationMs = Math.min(requestedDurationMs, availableAudioMs - safeOffsetMs);
30
+ if (actualDurationMs <= 0) return;
31
+ bufferSource.start(ctxStartMs / 1e3, safeOffsetMs / 1e3, actualDurationMs / 1e3);
32
+ }));
33
+ return audioContext.startRendering();
34
+ }
35
+ export { renderTemporalAudio };
@@ -5,25 +5,23 @@ var PROGRESS_PROPERTY = "--ef-progress";
5
5
  var DURATION_PROPERTY = "--ef-duration";
6
6
  var TRANSITION_DURATION_PROPERTY = "--ef-transition-duration";
7
7
  var TRANSITION_OUT_START_PROPERTY = "--ef-transition-out-start";
8
- var TIMEGROUP_TAGNAME = "ef-timegroup";
9
8
  const evaluateTemporalState = (element) => {
10
9
  const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
11
10
  const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
12
- const useInclusiveEnd = element.tagName.toLowerCase() === TIMEGROUP_TAGNAME && !element.parentTimegroup;
13
- 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;
14
14
  return {
15
15
  progress,
16
- isVisible,
16
+ isVisible: element.startTimeMs <= timelineTimeMs && (useInclusiveEnd ? element.endTimeMs >= timelineTimeMs : element.endTimeMs > timelineTimeMs),
17
17
  timelineTimeMs
18
18
  };
19
19
  };
20
20
  const evaluateTemporalStateForAnimation = (element) => {
21
21
  const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
22
- const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
23
- const isVisible = element.startTimeMs <= timelineTimeMs && element.endTimeMs >= timelineTimeMs;
24
22
  return {
25
- progress,
26
- 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,
27
25
  timelineTimeMs
28
26
  };
29
27
  };
@@ -72,8 +70,7 @@ var coordinateAnimationsForSingleElement = (element) => {
72
70
  const updateAnimations = (element) => {
73
71
  const temporalState = evaluateTemporalState(element);
74
72
  deepGetTemporalElements(element).forEach((temporalElement) => {
75
- const temporalState$1 = evaluateTemporalState(temporalElement);
76
- updateVisualState(temporalElement, temporalState$1);
73
+ updateVisualState(temporalElement, evaluateTemporalState(temporalElement));
77
74
  });
78
75
  updateVisualState(element, temporalState);
79
76
  if (evaluateTemporalStateForAnimation(element).isVisible) coordinateAnimationsForSingleElement(element);
@@ -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,17 +1,18 @@
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";
5
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.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";
6
8
  import { efConfigurationContext } from "./EFConfiguration.js";
7
9
  import { efContext } from "./efContext.js";
8
10
  import { fetchContext } from "./fetchContext.js";
9
11
  import { focusContext } from "./focusContext.js";
10
12
  import { focusedElementContext } from "./focusedElementContext.js";
11
- import { loopContext, playingContext } from "./playingContext.js";
12
- import { consume, createContext, provide } from "@lit/context";
13
+ import { ContextProvider, consume, createContext, provide } from "@lit/context";
13
14
  import { property, state } from "lit/decorators.js";
14
- const targetTimegroupContext = createContext("target-timegroup");
15
+ const targetTemporalContext = createContext(Symbol("target-temporal"));
15
16
  var contextMixinSymbol = Symbol("contextMixin");
16
17
  function isContextMixin(value) {
17
18
  return typeof value === "object" && value !== null && contextMixinSymbol in value.constructor;
@@ -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 = {}) => {
@@ -53,14 +53,16 @@ function ContextMixin(superClass) {
53
53
  throw error;
54
54
  }
55
55
  };
56
- this.playing = false;
57
- this.loop = false;
58
56
  this.rendering = false;
59
- this.currentTimeMs = NaN;
60
57
  }
61
58
  static {
62
59
  this[contextMixinSymbol] = true;
63
60
  }
61
+ #playingProvider;
62
+ #loopProvider;
63
+ #currentTimeMsProvider;
64
+ #targetTemporalProvider;
65
+ #loop = false;
64
66
  #apiHost;
65
67
  get apiHost() {
66
68
  return this.#apiHost ?? this.efConfiguration?.apiHost ?? "";
@@ -68,6 +70,56 @@ function ContextMixin(superClass) {
68
70
  set apiHost(value) {
69
71
  this.#apiHost = value;
70
72
  }
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
+ };
71
123
  #getTokenCacheKey(url) {
72
124
  try {
73
125
  const urlObj = new URL(url);
@@ -120,16 +172,35 @@ function ContextMixin(superClass) {
120
172
  set signingURL(value) {
121
173
  this.#signingURL = value;
122
174
  }
123
- #FPS = 30;
124
- #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
+ }
125
196
  #timegroupObserver = new MutationObserver((mutations) => {
126
197
  let shouldUpdate = false;
127
198
  for (const mutation of mutations) if (mutation.type === "childList") {
128
- const newTimegroup = this.querySelector("ef-timegroup");
129
- if (newTimegroup !== this.targetTimegroup) {
130
- this.targetTimegroup = newTimegroup;
199
+ const newTemporal = this.findRootTemporal();
200
+ if (newTemporal !== this.targetTemporal) {
201
+ this.targetTemporal = newTemporal;
131
202
  shouldUpdate = true;
132
- } 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;
133
204
  } else if (mutation.type === "attributes") {
134
205
  if ([
135
206
  "duration",
@@ -138,128 +209,90 @@ function ContextMixin(superClass) {
138
209
  "trimend",
139
210
  "sourcein",
140
211
  "sourceout"
141
- ].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;
142
213
  }
143
214
  if (shouldUpdate) queueMicrotask(() => {
144
215
  this.updateDurationProperties();
145
216
  this.requestUpdate();
146
- if (this.targetTimegroup) this.targetTimegroup.requestUpdate();
217
+ if (this.targetTemporal) this.targetTemporal.requestUpdate();
147
218
  });
148
219
  });
149
220
  updateDurationProperties() {
150
- const newDuration = this.targetTimegroup?.durationMs ?? 0;
151
- const newEndTime = this.targetTimegroup?.endTimeMs ?? 0;
221
+ const newDuration = this.targetTemporal?.durationMs ?? 0;
222
+ const newEndTime = this.targetTemporal?.endTimeMs ?? 0;
152
223
  if (this.durationMs !== newDuration) this.durationMs = newDuration;
153
224
  if (this.endTimeMs !== newEndTime) this.endTimeMs = newEndTime;
154
225
  }
155
226
  connectedCallback() {
156
227
  super.connectedCallback();
157
- 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();
158
245
  this.updateDurationProperties();
159
246
  this.#timegroupObserver.observe(this, {
160
247
  childList: true,
161
248
  subtree: true,
162
249
  attributes: true
163
250
  });
164
- if (this.playing) this.startPlayback();
165
251
  }
166
252
  disconnectedCallback() {
167
253
  super.disconnectedCallback();
168
254
  this.#timegroupObserver.disconnect();
169
- this.stopPlayback();
170
- }
171
- update(changedProperties) {
172
- if (changedProperties.has("playing")) if (this.playing) this.startPlayback();
173
- else this.stopPlayback();
174
- if (changedProperties.has("currentTimeMs") && this.targetTimegroup && !Number.isNaN(this.currentTimeMs)) {
175
- if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
176
- if (this.isConnected) {
177
- if (this.targetTimegroup.currentTimeMs === this.currentTimeMs) return;
178
- this.targetTimegroup.currentTimeMs = this.currentTimeMs;
179
- }
180
- }
255
+ if (this.#targetTemporal?.playbackController) {
256
+ this.#targetTemporal.playbackController.removeListener(this.#onControllerUpdate);
257
+ this.#controllerSubscribed = false;
181
258
  }
182
- super.update(changedProperties);
183
- }
184
- play() {
185
- this.playing = true;
259
+ this.pause();
186
260
  }
187
- pause() {
188
- this.playing = false;
189
- }
190
- #playbackAudioContext = null;
191
- #playbackAnimationFrameRequest = null;
192
- #AUDIO_PLAYBACK_SLICE_MS = 47 * 1024 / 48e3 * 1e3;
193
- #syncPlayheadToAudioContext(target, startMs) {
194
- const rawTimeMs = startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1e3;
195
- const nextTimeMs = Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;
196
- if (nextTimeMs !== this.currentTimeMs) this.currentTimeMs = nextTimeMs;
197
- this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
198
- this.#syncPlayheadToAudioContext(target, startMs);
199
- });
200
- }
201
- async stopPlayback() {
202
- if (this.#playbackAudioContext) {
203
- 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);
204
270
  }
205
- if (this.#playbackAnimationFrameRequest) cancelAnimationFrame(this.#playbackAnimationFrameRequest);
206
- this.#playbackAudioContext = null;
207
- this.#playbackAnimationFrameRequest = null;
208
271
  }
209
- async startPlayback() {
210
- await this.stopPlayback();
211
- const timegroup = this.targetTimegroup;
212
- if (!timegroup) return;
213
- await timegroup.waitForMediaDurations();
214
- let currentMs = timegroup.currentTimeMs;
215
- const fromMs = currentMs;
216
- const toMs = timegroup.endTimeMs;
217
- if (fromMs >= toMs) {
218
- this.pause();
219
- 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
+ }
220
284
  }
221
- let bufferCount = 0;
222
- this.#playbackAudioContext = new AudioContext({ latencyHint: "playback" });
223
- if (this.#playbackAnimationFrameRequest) cancelAnimationFrame(this.#playbackAnimationFrameRequest);
224
- this.#syncPlayheadToAudioContext(timegroup, currentMs);
225
- const playbackContext = this.#playbackAudioContext;
226
- if (playbackContext.state === "suspended") {
227
- console.warn("AudioContext is suspended, media playback will not work until user has interacted with page.");
228
- this.playing = false;
229
- 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
+ }
230
291
  }
231
- await playbackContext.suspend();
232
- const fillBuffer = async () => {
233
- if (bufferCount > 2) return;
234
- if (await queueBufferSource()) fillBuffer();
235
- };
236
- const queueBufferSource = async () => {
237
- if (currentMs >= toMs) return false;
238
- const startMs = currentMs;
239
- const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
240
- currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
241
- const audioBuffer = await timegroup.renderAudio(startMs, endMs);
242
- bufferCount++;
243
- const source = playbackContext.createBufferSource();
244
- source.buffer = audioBuffer;
245
- source.connect(playbackContext.destination);
246
- source.start((startMs - fromMs) / 1e3);
247
- source.onended = () => {
248
- bufferCount--;
249
- if (endMs >= toMs) {
250
- this.pause();
251
- if (this.loop) this.updateComplete.then(() => {
252
- this.currentTimeMs = 0;
253
- this.updateComplete.then(() => {
254
- this.play();
255
- });
256
- });
257
- } else fillBuffer();
258
- };
259
- return true;
260
- };
261
- await fillBuffer();
262
- await playbackContext.resume();
292
+ this.targetTemporal.playbackController.play();
293
+ }
294
+ pause() {
295
+ if (this.targetTemporal?.playbackController) this.targetTemporal.playbackController.pause();
263
296
  }
264
297
  }
265
298
  __decorate([consume({
@@ -273,7 +306,7 @@ function ContextMixin(superClass) {
273
306
  attribute: "api-host"
274
307
  })], ContextElement.prototype, "apiHost", null);
275
308
  __decorate([provide({ context: efContext })], ContextElement.prototype, "efContext", void 0);
276
- __decorate([provide({ context: targetTimegroupContext }), state()], ContextElement.prototype, "targetTimegroup", void 0);
309
+ __decorate([state()], ContextElement.prototype, "targetTemporal", null);
277
310
  __decorate([provide({ context: durationContext }), property({ type: Number })], ContextElement.prototype, "durationMs", void 0);
278
311
  __decorate([property({ type: Number })], ContextElement.prototype, "endTimeMs", void 0);
279
312
  __decorate([provide({ context: fetchContext })], ContextElement.prototype, "fetch", void 0);
@@ -281,16 +314,17 @@ function ContextMixin(superClass) {
281
314
  type: String,
282
315
  attribute: "signing-url"
283
316
  })], ContextElement.prototype, "signingURL", null);
284
- __decorate([provide({ context: playingContext }), property({
317
+ __decorate([property({
285
318
  type: Boolean,
286
319
  reflect: true
287
- })], ContextElement.prototype, "playing", void 0);
288
- __decorate([provide({ context: loopContext }), property({
320
+ })], ContextElement.prototype, "playing", null);
321
+ __decorate([property({
289
322
  type: Boolean,
290
- reflect: true
291
- })], ContextElement.prototype, "loop", void 0);
323
+ reflect: true,
324
+ attribute: "loop"
325
+ })], ContextElement.prototype, "loop", null);
292
326
  __decorate([property({ type: Boolean })], ContextElement.prototype, "rendering", void 0);
293
- __decorate([provide({ context: currentTimeContext }), property({ type: Number })], ContextElement.prototype, "currentTimeMs", void 0);
327
+ __decorate([property({ type: Number })], ContextElement.prototype, "currentTimeMs", null);
294
328
  return ContextElement;
295
329
  }
296
- export { ContextMixin, isContextMixin, targetTimegroupContext };
330
+ export { ContextMixin, isContextMixin, targetTemporalContext };
File without changes