@editframe/elements 0.33.0-beta → 0.34.6-beta

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 (251) hide show
  1. package/dist/EF_FRAMEGEN.js +5 -3
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.94.0 → _@oxc-project_runtime@0.95.0}/helpers/decorate.js +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +7 -4
  5. package/dist/canvas/EFCanvas.js +1 -1
  6. package/dist/canvas/EFCanvasItem.d.ts +4 -4
  7. package/dist/canvas/EFCanvasItem.js +1 -1
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +95 -0
  9. package/dist/canvas/overlays/SelectionOverlay.js +1 -1
  10. package/dist/canvas/selection/SelectionController.js +7 -11
  11. package/dist/canvas/selection/SelectionController.js.map +1 -1
  12. package/dist/elements/EFAudio.d.ts +25 -7
  13. package/dist/elements/EFAudio.js +31 -61
  14. package/dist/elements/EFAudio.js.map +1 -1
  15. package/dist/elements/EFCaptions.d.ts +65 -52
  16. package/dist/elements/EFCaptions.js +186 -400
  17. package/dist/elements/EFCaptions.js.map +1 -1
  18. package/dist/elements/EFImage.d.ts +34 -6
  19. package/dist/elements/EFImage.js +114 -79
  20. package/dist/elements/EFImage.js.map +1 -1
  21. package/dist/elements/EFMedia/AssetIdMediaEngine.js +17 -17
  22. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  23. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -25
  24. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  25. package/dist/elements/EFMedia/BaseMediaEngine.js +4 -4
  26. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
  28. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  29. package/dist/elements/EFMedia/JitMediaEngine.js +31 -17
  30. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  31. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  33. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +17 -9
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  35. package/dist/elements/EFMedia.d.ts +66 -20
  36. package/dist/elements/EFMedia.js +412 -30
  37. package/dist/elements/EFMedia.js.map +1 -1
  38. package/dist/elements/EFPanZoom.d.ts +4 -4
  39. package/dist/elements/EFPanZoom.js +1 -1
  40. package/dist/elements/EFSourceMixin.js +43 -15
  41. package/dist/elements/EFSourceMixin.js.map +1 -1
  42. package/dist/elements/EFSurface.d.ts +23 -10
  43. package/dist/elements/EFSurface.js +64 -22
  44. package/dist/elements/EFSurface.js.map +1 -1
  45. package/dist/elements/EFTemporal.d.ts +8 -2
  46. package/dist/elements/EFTemporal.js +42 -31
  47. package/dist/elements/EFTemporal.js.map +1 -1
  48. package/dist/elements/EFText.d.ts +5 -4
  49. package/dist/elements/EFText.js +11 -2
  50. package/dist/elements/EFText.js.map +1 -1
  51. package/dist/elements/EFTextSegment.d.ts +4 -4
  52. package/dist/elements/EFTextSegment.js +1 -1
  53. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  54. package/dist/elements/EFThumbnailStrip.js +1 -1
  55. package/dist/elements/EFTimegroup.d.ts +22 -8
  56. package/dist/elements/EFTimegroup.js +203 -115
  57. package/dist/elements/EFTimegroup.js.map +1 -1
  58. package/dist/elements/EFVideo.d.ts +57 -20
  59. package/dist/elements/EFVideo.js +324 -72
  60. package/dist/elements/EFVideo.js.map +1 -1
  61. package/dist/elements/EFWaveform.d.ts +33 -7
  62. package/dist/elements/EFWaveform.js +103 -59
  63. package/dist/elements/EFWaveform.js.map +1 -1
  64. package/dist/elements/renderTemporalAudio.js +14 -3
  65. package/dist/elements/renderTemporalAudio.js.map +1 -1
  66. package/dist/getRenderInfo.d.ts +2 -2
  67. package/dist/gui/ContextMixin.js +1 -1
  68. package/dist/gui/Controllable.d.ts +2 -0
  69. package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
  70. package/dist/gui/EFActiveRootTemporal.js +1 -1
  71. package/dist/gui/EFConfiguration.d.ts +4 -4
  72. package/dist/gui/EFConfiguration.js +1 -1
  73. package/dist/gui/EFControls.d.ts +2 -2
  74. package/dist/gui/EFControls.js +1 -1
  75. package/dist/gui/EFDial.d.ts +4 -4
  76. package/dist/gui/EFDial.js +1 -1
  77. package/dist/gui/EFFilmstrip.d.ts +3 -2
  78. package/dist/gui/EFFilmstrip.js +1 -1
  79. package/dist/gui/EFFitScale.js +1 -1
  80. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  81. package/dist/gui/EFFocusOverlay.js +1 -1
  82. package/dist/gui/EFOverlayItem.d.ts +4 -4
  83. package/dist/gui/EFOverlayItem.js +1 -1
  84. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  85. package/dist/gui/EFOverlayLayer.js +1 -1
  86. package/dist/gui/EFPause.d.ts +4 -4
  87. package/dist/gui/EFPause.js +1 -1
  88. package/dist/gui/EFPlay.d.ts +4 -4
  89. package/dist/gui/EFPlay.js +1 -1
  90. package/dist/gui/EFPreview.d.ts +4 -4
  91. package/dist/gui/EFPreview.js +1 -1
  92. package/dist/gui/EFResizableBox.d.ts +4 -4
  93. package/dist/gui/EFResizableBox.js +1 -1
  94. package/dist/gui/EFScrubber.d.ts +4 -4
  95. package/dist/gui/EFScrubber.js +1 -1
  96. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  97. package/dist/gui/EFTimeDisplay.js +1 -1
  98. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  99. package/dist/gui/EFTimelineRuler.js +1 -1
  100. package/dist/gui/EFToggleLoop.d.ts +4 -4
  101. package/dist/gui/EFToggleLoop.js +1 -1
  102. package/dist/gui/EFTogglePlay.d.ts +4 -4
  103. package/dist/gui/EFTogglePlay.js +1 -1
  104. package/dist/gui/EFTransformHandles.d.ts +4 -4
  105. package/dist/gui/EFTransformHandles.js +1 -1
  106. package/dist/gui/EFWorkbench.d.ts +5 -4
  107. package/dist/gui/EFWorkbench.js +1 -1
  108. package/dist/gui/PlaybackController.d.ts +10 -2
  109. package/dist/gui/PlaybackController.js +52 -30
  110. package/dist/gui/PlaybackController.js.map +1 -1
  111. package/dist/gui/TWMixin.js +1 -1
  112. package/dist/gui/TWMixin.js.map +1 -1
  113. package/dist/gui/TargetOrContextMixin.js +1 -1
  114. package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
  115. package/dist/gui/hierarchy/EFHierarchy.js +1 -1
  116. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  117. package/dist/gui/hierarchy/EFHierarchyItem.js +1 -1
  118. package/dist/gui/timeline/EFTimeline.d.ts +6 -2
  119. package/dist/gui/timeline/EFTimeline.js +1 -1
  120. package/dist/gui/timeline/EFTimelineRow.d.ts +57 -0
  121. package/dist/gui/timeline/EFTimelineRow.js +1 -1
  122. package/dist/gui/timeline/TrimHandles.d.ts +4 -4
  123. package/dist/gui/timeline/TrimHandles.js +1 -1
  124. package/dist/gui/timeline/tracks/AudioTrack.d.ts +2 -0
  125. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  126. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +58 -0
  127. package/dist/gui/timeline/tracks/CaptionsTrack.js +1 -1
  128. package/dist/gui/timeline/tracks/HTMLTrack.d.ts +13 -0
  129. package/dist/gui/timeline/tracks/HTMLTrack.js +1 -1
  130. package/dist/gui/timeline/tracks/ImageTrack.d.ts +14 -0
  131. package/dist/gui/timeline/tracks/ImageTrack.js +1 -1
  132. package/dist/gui/timeline/tracks/TextTrack.d.ts +26 -0
  133. package/dist/gui/timeline/tracks/TextTrack.js +1 -1
  134. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +47 -0
  135. package/dist/gui/timeline/tracks/TimegroupTrack.js +4 -12
  136. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  137. package/dist/gui/timeline/tracks/TrackItem.d.ts +81 -0
  138. package/dist/gui/timeline/tracks/TrackItem.js +1 -1
  139. package/dist/gui/timeline/tracks/VideoTrack.d.ts +25 -0
  140. package/dist/gui/timeline/tracks/VideoTrack.js +1 -1
  141. package/dist/gui/timeline/tracks/WaveformTrack.d.ts +14 -0
  142. package/dist/gui/timeline/tracks/WaveformTrack.js +1 -1
  143. package/dist/gui/timeline/tracks/ensureTrackItemInit.d.ts +1 -0
  144. package/dist/gui/timeline/tracks/preloadTracks.d.ts +9 -0
  145. package/dist/gui/tree/EFTree.d.ts +5 -4
  146. package/dist/gui/tree/EFTree.js +1 -1
  147. package/dist/gui/tree/EFTreeItem.d.ts +4 -4
  148. package/dist/gui/tree/EFTreeItem.js +1 -1
  149. package/dist/index.d.ts +4 -1
  150. package/dist/preview/AdaptiveResolutionTracker.js +6 -14
  151. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  152. package/dist/preview/FrameController.d.ts +123 -0
  153. package/dist/preview/FrameController.js +216 -0
  154. package/dist/preview/FrameController.js.map +1 -0
  155. package/dist/preview/RenderContext.d.ts +1 -0
  156. package/dist/preview/RenderContext.js +193 -0
  157. package/dist/preview/RenderContext.js.map +1 -0
  158. package/dist/preview/encoding/canvasEncoder.js +166 -0
  159. package/dist/preview/encoding/canvasEncoder.js.map +1 -0
  160. package/dist/preview/encoding/mainThreadEncoder.js +39 -0
  161. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -0
  162. package/dist/preview/encoding/types.d.ts +1 -0
  163. package/dist/preview/encoding/workerEncoder.js +58 -0
  164. package/dist/preview/encoding/workerEncoder.js.map +1 -0
  165. package/dist/preview/logger.js +41 -0
  166. package/dist/preview/logger.js.map +1 -0
  167. package/dist/preview/previewTypes.js +11 -10
  168. package/dist/preview/previewTypes.js.map +1 -1
  169. package/dist/preview/renderTimegroupPreview.js +259 -236
  170. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  171. package/dist/preview/renderTimegroupToCanvas.d.ts +5 -0
  172. package/dist/preview/renderTimegroupToCanvas.js +100 -489
  173. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  174. package/dist/preview/renderTimegroupToVideo.d.ts +1 -0
  175. package/dist/preview/renderTimegroupToVideo.js +80 -22
  176. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  177. package/dist/preview/renderers.js.map +1 -1
  178. package/dist/preview/rendering/inlineImages.js +56 -0
  179. package/dist/preview/rendering/inlineImages.js.map +1 -0
  180. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  181. package/dist/preview/rendering/renderToImage.js +120 -0
  182. package/dist/preview/rendering/renderToImage.js.map +1 -0
  183. package/dist/preview/rendering/renderToImageForeignObject.js +135 -0
  184. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -0
  185. package/dist/preview/rendering/renderToImageNative.d.ts +1 -0
  186. package/dist/preview/rendering/renderToImageNative.js +129 -0
  187. package/dist/preview/rendering/renderToImageNative.js.map +1 -0
  188. package/dist/preview/rendering/svgSerializer.js +43 -0
  189. package/dist/preview/rendering/svgSerializer.js.map +1 -0
  190. package/dist/preview/rendering/types.d.ts +2 -0
  191. package/dist/preview/statsTrackingStrategy.js +3 -1
  192. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  193. package/dist/preview/workers/WorkerPool.js +8 -57
  194. package/dist/preview/workers/WorkerPool.js.map +1 -1
  195. package/dist/render/EFRenderAPI.d.ts +35 -0
  196. package/dist/render/EFRenderAPI.js +1 -0
  197. package/dist/render/EFRenderAPI.js.map +1 -1
  198. package/dist/sandbox/PlaybackControls.d.ts +1 -0
  199. package/dist/sandbox/ScenarioRunner.d.ts +1 -0
  200. package/dist/sandbox/defineSandbox.d.ts +1 -0
  201. package/dist/sandbox/index.d.ts +3 -0
  202. package/dist/style.css +3 -0
  203. package/dist/transcoding/types/index.d.ts +6 -3
  204. package/package.json +2 -3
  205. package/test/EFVideo.framegen.browsertest.ts +8 -1
  206. package/test/profilingPlugin.ts +1 -3
  207. package/test/setup.ts +23 -1
  208. package/dist/EF_INTERACTIVE.js +0 -7
  209. package/dist/EF_INTERACTIVE.js.map +0 -1
  210. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +0 -50
  211. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +0 -12
  212. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +0 -104
  213. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +0 -1
  214. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -168
  215. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +0 -1
  216. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +0 -46
  217. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +0 -1
  218. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +0 -49
  219. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +0 -1
  220. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +0 -30
  221. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +0 -1
  222. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +0 -49
  223. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +0 -1
  224. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +0 -47
  225. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +0 -1
  226. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -140
  227. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +0 -1
  228. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +0 -13
  229. package/dist/elements/EFMedia/shared/BufferUtils.js +0 -86
  230. package/dist/elements/EFMedia/shared/BufferUtils.js.map +0 -1
  231. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +0 -17
  232. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +0 -90
  233. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +0 -1
  234. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +0 -80
  235. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +0 -1
  236. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -49
  237. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +0 -1
  238. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -58
  239. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +0 -1
  240. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +0 -71
  241. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +0 -1
  242. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -52
  243. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +0 -1
  244. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -50
  245. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +0 -1
  246. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +0 -109
  247. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +0 -1
  248. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +0 -12
  249. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +0 -97
  250. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +0 -1
  251. package/dist/elements/SampleBuffer.d.ts +0 -19
@@ -1,13 +1,8 @@
1
+ import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
1
2
  import { EFFramegen } from "../EF_FRAMEGEN.js";
2
- import { InputTask } from "./EFMedia/shared/MediaTaskUtils.js";
3
- import { MediaEngine } from "../transcoding/types/index.js";
4
- import { MediaBufferState } from "./EFMedia/shared/BufferUtils.js";
5
3
  import { EFMedia } from "./EFMedia.js";
6
- import { VideoBufferState } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
7
- import { Task } from "@lit/task";
8
4
  import * as lit2 from "lit";
9
5
  import { PropertyValueMap } from "lit";
10
- import * as mediabunny0 from "mediabunny";
11
6
  import * as lit_html2 from "lit-html";
12
7
  import * as lit_html_directives_ref0 from "lit-html/directives/ref";
13
8
 
@@ -26,7 +21,8 @@ interface LoadingState {
26
21
  */
27
22
 
28
23
  declare const EFVideo_base: typeof EFMedia;
29
- declare class EFVideo extends EFVideo_base {
24
+ declare class EFVideo extends EFVideo_base implements FrameRenderable {
25
+ #private;
30
26
  static styles: lit2.CSSResult[];
31
27
  canvasRef: lit_html_directives_ref0.Ref<HTMLCanvasElement>;
32
28
  /**
@@ -44,18 +40,32 @@ declare class EFVideo extends EFVideo_base {
44
40
  * @domAttribute "enable-video-buffering"
45
41
  */
46
42
  enableVideoBuffering: boolean;
47
- unifiedVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
48
- videoBufferTask: Task<readonly [number], VideoBufferState>;
49
- scrubVideoBufferTask: Task<readonly [any], MediaBufferState>;
50
- scrubVideoInputTask: InputTask;
51
- scrubVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
52
- scrubVideoSegmentIdTask: Task<readonly [MediaEngine | undefined, number], number | undefined>;
53
- scrubVideoSegmentFetchTask: Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
54
- scrubVideoInitSegmentFetchTask: Task<readonly [MediaEngine | undefined], ArrayBuffer>;
55
43
  /**
56
- * Delayed loading state manager for user feedback
44
+ * Query readiness state for a given time.
45
+ * @implements FrameRenderable
46
+ *
47
+ * Note: The timeMs parameter is the root timegroup's time. We check against
48
+ * this.currentSourceTimeMs since that's what we cache in prepareFrame.
49
+ */
50
+ getFrameState(_timeMs: number): FrameState;
51
+ /**
52
+ * Async preparation - seeks video and caches the sample.
53
+ * @implements FrameRenderable
54
+ *
55
+ * Note: The timeMs parameter is the root timegroup's time. We ignore it and
56
+ * use this.currentSourceTimeMs instead, which accounts for:
57
+ * - Our position within the parent timegroup (ownCurrentTimeMs)
58
+ * - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)
57
59
  */
58
- private delayedLoadingState;
60
+ prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void>;
61
+ /**
62
+ * Synchronous render - paints cached video sample to canvas.
63
+ * @implements FrameRenderable
64
+ *
65
+ * Note: The timeMs parameter is the root timegroup's time. We use
66
+ * this.currentSourceTimeMs to match what prepareFrame cached.
67
+ */
68
+ renderFrame(_timeMs: number): void;
59
69
  /**
60
70
  * Loading state for user feedback
61
71
  */
@@ -68,7 +78,11 @@ declare class EFVideo extends EFVideo_base {
68
78
  protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
69
79
  render(): lit_html2.TemplateResult<1>;
70
80
  get canvasElement(): HTMLCanvasElement | undefined;
71
- frameTask: Task<readonly [number], void>;
81
+ /**
82
+ * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
83
+ * This is a compatibility wrapper that delegates to the new system.
84
+ */
85
+ frameTask: FrameTask;
72
86
  /**
73
87
  * Start a delayed loading operation for testing
74
88
  */
@@ -106,9 +120,9 @@ declare class EFVideo extends EFVideo_base {
106
120
  private isFrameRenderingActive;
107
121
  /**
108
122
  * Legacy getter for fragment index task
109
- * Still used by EFCaptions - maps to unified video seek task
123
+ * Still used by EFCaptions - maps to frameTask
110
124
  */
111
- get fragmentIndexTask(): Task<readonly [number], mediabunny0.VideoSample | undefined>;
125
+ get fragmentIndexTask(): FrameTask;
112
126
  /**
113
127
  * Helper method for tests: wait for the current frame to be ready
114
128
  * This encapsulates the complexity of ensuring the video has updated
@@ -117,6 +131,29 @@ declare class EFVideo extends EFVideo_base {
117
131
  * @returns Promise that resolves when the frame is ready
118
132
  */
119
133
  waitForFrameReady(): Promise<void>;
134
+ /**
135
+ * Capture a video frame directly at a source media timestamp.
136
+ * Bypasses the frameTask system - designed for export/rendering.
137
+ * Does NOT paint to the element's internal canvas.
138
+ *
139
+ * Uses the same routing logic as unified video system:
140
+ * - "auto": main track for production rendering, follows normal routing otherwise
141
+ * - "scrub": force low-res scrub track (for thumbnails)
142
+ * - "main": force full-quality main track
143
+ *
144
+ * @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
145
+ * @param options - Capture options including quality and abort signal
146
+ * @returns Frame data for serialization
147
+ * @public
148
+ */
149
+ captureFrameAtSourceTime(sourceTimeMs: number, options?: {
150
+ quality?: "auto" | "scrub" | "main";
151
+ signal?: AbortSignal;
152
+ }): Promise<{
153
+ dataUrl: string;
154
+ width: number;
155
+ height: number;
156
+ }>;
120
157
  /**
121
158
  * Pre-fetch scrub segments for given timestamps.
122
159
  * Loads 30-second segments sequentially, emitting progress events.
@@ -1,18 +1,12 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { updateAnimations } from "./updateAnimations.js";
2
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
3
  import { TWMixin } from "../gui/TWMixin2.js";
3
- import { withSpan, withSpanSync } from "../otel/tracingHelpers.js";
4
+ import { withSpanSync } from "../otel/tracingHelpers.js";
5
+ import { PRIORITY_VIDEO, createFrameTaskWrapper } from "../preview/FrameController.js";
4
6
  import { EFMedia } from "./EFMedia.js";
5
- import { updateAnimations } from "./updateAnimations.js";
6
7
  import { DelayedLoadingState } from "../DelayedLoadingState.js";
7
- import { makeScrubVideoBufferTask } from "./EFMedia/videoTasks/makeScrubVideoBufferTask.js";
8
- import { makeScrubVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js";
9
- import { makeScrubVideoInputTask } from "./EFMedia/videoTasks/makeScrubVideoInputTask.js";
10
- import { makeScrubVideoSeekTask } from "./EFMedia/videoTasks/makeScrubVideoSeekTask.js";
11
- import { makeScrubVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js";
12
- import { makeScrubVideoSegmentIdTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js";
13
- import { makeUnifiedVideoSeekTask } from "./EFMedia/videoTasks/makeUnifiedVideoSeekTask.js";
14
- import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
15
- import { Task } from "@lit/task";
8
+ import { MainVideoInputCache } from "./EFMedia/videoTasks/MainVideoInputCache.js";
9
+ import { ScrubInputCache } from "./EFMedia/videoTasks/ScrubInputCache.js";
16
10
  import debug from "debug";
17
11
  import { css, html } from "lit";
18
12
  import { customElement, property, state } from "lit/decorators.js";
@@ -20,6 +14,8 @@ import { context, trace } from "@opentelemetry/api";
20
14
  import { createRef, ref } from "lit/directives/ref.js";
21
15
 
22
16
  //#region src/elements/EFVideo.ts
17
+ const mainVideoInputCache = new MainVideoInputCache();
18
+ const scrubInputCache = new ScrubInputCache();
23
19
  const log = debug("ef:elements:EFVideo");
24
20
  let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
25
21
  static {
@@ -82,68 +78,196 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
82
78
  }
83
79
  `];
84
80
  }
81
+ /**
82
+ * Cached video sample for the current frame.
83
+ * Set by prepareFrame(), consumed by renderFrame().
84
+ */
85
+ #cachedVideoSample = void 0;
86
+ #cachedVideoSampleTimeMs = void 0;
87
+ /**
88
+ * Query readiness state for a given time.
89
+ * @implements FrameRenderable
90
+ *
91
+ * Note: The timeMs parameter is the root timegroup's time. We check against
92
+ * this.currentSourceTimeMs since that's what we cache in prepareFrame.
93
+ */
94
+ getFrameState(_timeMs) {
95
+ const sourceTimeMs = this.currentSourceTimeMs;
96
+ const hasCache = this.#cachedVideoSample !== void 0 && this.#cachedVideoSampleTimeMs === sourceTimeMs;
97
+ return {
98
+ needsPreparation: !hasCache,
99
+ isReady: hasCache,
100
+ priority: PRIORITY_VIDEO
101
+ };
102
+ }
103
+ /**
104
+ * Async preparation - seeks video and caches the sample.
105
+ * @implements FrameRenderable
106
+ *
107
+ * Note: The timeMs parameter is the root timegroup's time. We ignore it and
108
+ * use this.currentSourceTimeMs instead, which accounts for:
109
+ * - Our position within the parent timegroup (ownCurrentTimeMs)
110
+ * - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)
111
+ */
112
+ async prepareFrame(_timeMs, signal) {
113
+ signal.throwIfAborted();
114
+ const sourceTimeMs = this.currentSourceTimeMs;
115
+ const mediaEngine = await this.getMediaEngine(signal);
116
+ if (!mediaEngine) {
117
+ this.#cachedVideoSample = void 0;
118
+ this.#cachedVideoSampleTimeMs = sourceTimeMs;
119
+ return;
120
+ }
121
+ signal.throwIfAborted();
122
+ try {
123
+ const videoSample = await this.#fetchVideoSampleForFrame(mediaEngine, sourceTimeMs, signal);
124
+ signal.throwIfAborted();
125
+ this.#cachedVideoSample = videoSample;
126
+ this.#cachedVideoSampleTimeMs = sourceTimeMs;
127
+ } catch (error) {
128
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
129
+ console.warn(`Video seek error at ${sourceTimeMs}ms:`, error);
130
+ this.#cachedVideoSample = void 0;
131
+ this.#cachedVideoSampleTimeMs = sourceTimeMs;
132
+ }
133
+ }
134
+ /**
135
+ * Synchronous render - paints cached video sample to canvas.
136
+ * @implements FrameRenderable
137
+ *
138
+ * Note: The timeMs parameter is the root timegroup's time. We use
139
+ * this.currentSourceTimeMs to match what prepareFrame cached.
140
+ */
141
+ renderFrame(_timeMs) {
142
+ const sourceTimeMs = this.currentSourceTimeMs;
143
+ if (this.#cachedVideoSampleTimeMs === sourceTimeMs && this.#cachedVideoSample) {
144
+ const videoFrame = this.#cachedVideoSample.toVideoFrame();
145
+ try {
146
+ this.displayFrame(videoFrame, sourceTimeMs);
147
+ } finally {
148
+ videoFrame.close();
149
+ }
150
+ }
151
+ if (!this.parentTimegroup) updateAnimations(this);
152
+ }
153
+ /**
154
+ * Fetch video sample for a given time.
155
+ *
156
+ * Uses a quality routing strategy:
157
+ * - In production rendering: always use main (full quality) track
158
+ * - In preview mode: try scrub track first for faster scrubbing, fall back to main
159
+ * - If main track segment is already cached: use it (avoid redundant lower-quality fetch)
160
+ */
161
+ async #fetchVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
162
+ const mainRendition = mediaEngine.videoRendition;
163
+ if (mainRendition) {
164
+ const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
165
+ if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition)) return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
166
+ }
167
+ if (this.isInProductionRenderingMode()) return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
168
+ if (mediaEngine.getScrubVideoRendition?.()) {
169
+ const scrubSample = await this.#getScrubVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
170
+ if (scrubSample) return scrubSample;
171
+ }
172
+ return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
173
+ }
174
+ /**
175
+ * Get scrub (low-resolution) video sample for fast preview scrubbing.
176
+ * Used in preview mode for faster response during timeline scrubbing.
177
+ */
178
+ async #getScrubVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
179
+ const scrubRendition = mediaEngine.getScrubVideoRendition?.();
180
+ if (!scrubRendition) return;
181
+ const scrubRenditionWithSrc = {
182
+ ...scrubRendition,
183
+ src: mediaEngine.src
184
+ };
185
+ const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
186
+ if (segmentId === void 0) return;
187
+ const scrubInput = await scrubInputCache.getOrCreateInput(mediaEngine.src, segmentId, async () => {
188
+ let initSegment;
189
+ let mediaSegment;
190
+ try {
191
+ [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal)]);
192
+ } catch (error) {
193
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
194
+ return;
195
+ }
196
+ if (!initSegment || !mediaSegment) return;
197
+ signal.throwIfAborted();
198
+ const combinedBlob = new Blob([initSegment, mediaSegment]);
199
+ signal.throwIfAborted();
200
+ const arrayBuffer = await combinedBlob.arrayBuffer();
201
+ signal.throwIfAborted();
202
+ const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
203
+ return new BufferedSeekingInput(arrayBuffer, {
204
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
205
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
206
+ startTimeOffsetMs: scrubRendition.startTimeOffsetMs
207
+ });
208
+ });
209
+ if (!scrubInput) return;
210
+ signal.throwIfAborted();
211
+ const videoTrack = await scrubInput.getFirstVideoTrack();
212
+ if (!videoTrack) return;
213
+ signal.throwIfAborted();
214
+ return scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
215
+ }
216
+ /**
217
+ * Get main video sample for a given time.
218
+ */
219
+ async #getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
220
+ const videoRendition = mediaEngine.getVideoRendition?.() ?? mediaEngine.videoRendition;
221
+ if (!videoRendition) return;
222
+ const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, videoRendition);
223
+ if (segmentId === void 0) return;
224
+ const mainInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
225
+ let initSegment;
226
+ let mediaSegment;
227
+ try {
228
+ [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, signal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal)]);
229
+ } catch (error) {
230
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
231
+ if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") || error.message.includes("File not found") || error.message.includes("Media segment not found") || error.message.includes("Init segment not found") || error.message.includes("Track not found"))) return;
232
+ throw error;
233
+ }
234
+ if (!initSegment || !mediaSegment) return;
235
+ signal.throwIfAborted();
236
+ const combinedBlob = new Blob([initSegment, mediaSegment]);
237
+ signal.throwIfAborted();
238
+ const arrayBuffer = await combinedBlob.arrayBuffer();
239
+ signal.throwIfAborted();
240
+ const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
241
+ return new BufferedSeekingInput(arrayBuffer, {
242
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
243
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
244
+ startTimeOffsetMs: videoRendition.startTimeOffsetMs
245
+ });
246
+ });
247
+ if (!mainInput) return;
248
+ signal.throwIfAborted();
249
+ const videoTrack = await mainInput.getFirstVideoTrack();
250
+ if (!videoTrack) return;
251
+ signal.throwIfAborted();
252
+ return mainInput.seek(videoTrack.id, desiredSeekTimeMs);
253
+ }
254
+ /**
255
+ * Delayed loading state manager for user feedback
256
+ */
257
+ #delayedLoadingState;
85
258
  constructor() {
86
259
  super();
87
260
  this.canvasRef = createRef();
88
261
  this.videoBufferDurationMs = 1e4;
89
262
  this.maxVideoBufferFetches = 2;
90
263
  this.enableVideoBuffering = true;
91
- this.unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);
92
- this.videoBufferTask = makeVideoBufferTask(this);
93
- this.scrubVideoBufferTask = makeScrubVideoBufferTask(this);
94
- this.scrubVideoInputTask = makeScrubVideoInputTask(this);
95
- this.scrubVideoSeekTask = makeScrubVideoSeekTask(this);
96
- this.scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);
97
- this.scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);
98
- this.scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);
99
264
  this.loadingState = {
100
265
  isLoading: false,
101
266
  operation: null,
102
267
  message: ""
103
268
  };
104
- this.frameTask = new Task(this, {
105
- autoRun: false,
106
- args: () => [this.desiredSeekTimeMs],
107
- onError: (error) => {
108
- this.frameTask.taskComplete.catch(() => {});
109
- if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
110
- if (error instanceof Error && !error.message.includes("Video rendition unavailable") && !error.message.includes("No valid media source") && !error.message.includes("Sample not found for time")) console.error("frameTask error", error);
111
- },
112
- onComplete: () => {},
113
- task: async ([_desiredSeekTimeMs], { signal }) => {
114
- const t0 = performance.now();
115
- await withSpan("video.frameTask", {
116
- elementId: this.id || "unknown",
117
- desiredSeekTimeMs: _desiredSeekTimeMs,
118
- src: this.src || "none"
119
- }, void 0, async (span) => {
120
- const t1 = performance.now();
121
- span.setAttribute("preworkMs", t1 - t0);
122
- this.unifiedVideoSeekTask.run().catch(() => {});
123
- const t2 = performance.now();
124
- span.setAttribute("seekRunMs", t2 - t1);
125
- try {
126
- await this.unifiedVideoSeekTask.taskComplete;
127
- } catch (error) {
128
- if (error instanceof DOMException && error.name === "AbortError") {
129
- signal?.throwIfAborted();
130
- return;
131
- }
132
- throw error;
133
- }
134
- const t3 = performance.now();
135
- span.setAttribute("seekAwaitMs", t3 - t2);
136
- signal?.throwIfAborted();
137
- const t4 = performance.now();
138
- this.paint(_desiredSeekTimeMs, span);
139
- const t5 = performance.now();
140
- span.setAttribute("paintMs", t5 - t4);
141
- if (!this.parentTimegroup) updateAnimations(this);
142
- span.setAttribute("totalFrameMs", t5 - t0);
143
- });
144
- }
145
- });
146
- this.delayedLoadingState = new DelayedLoadingState(250, (isLoading, message) => {
269
+ this.frameTask = createFrameTaskWrapper(this, { getTimeMs: () => this.desiredSeekTimeMs });
270
+ this.#delayedLoadingState = new DelayedLoadingState(250, (isLoading, message) => {
147
271
  this.setLoadingState(isLoading, null, message);
148
272
  });
149
273
  }
@@ -176,13 +300,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
176
300
  * Start a delayed loading operation for testing
177
301
  */
178
302
  startDelayedLoading(operationId, message, options = {}) {
179
- this.delayedLoadingState.startLoading(operationId, message, options);
303
+ this.#delayedLoadingState.startLoading(operationId, message, options);
180
304
  }
181
305
  /**
182
306
  * Clear a delayed loading operation for testing
183
307
  */
184
308
  clearDelayedLoading(operationId) {
185
- this.delayedLoadingState.clearLoading(operationId);
309
+ this.#delayedLoadingState.clearLoading(operationId);
186
310
  }
187
311
  /**
188
312
  * Set loading state for user feedback
@@ -212,7 +336,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
212
336
  span.setAttribute("modeCheckMs", t1 - t0);
213
337
  try {
214
338
  const t2 = performance.now();
215
- const videoSample = this.unifiedVideoSeekTask.value;
339
+ const videoSample = this.#cachedVideoSample;
216
340
  span.setAttribute("hasVideoSample", !!videoSample);
217
341
  span.setAttribute("valueAccessMs", t2 - t1);
218
342
  if (videoSample) {
@@ -230,7 +354,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
230
354
  }
231
355
  }
232
356
  } catch (error) {
233
- console.warn("Unified video pipeline error:", error);
357
+ console.warn("Video pipeline error:", error);
234
358
  }
235
359
  if (!isProductionRendering) {
236
360
  const isInRenderClone = !!this.closest(".ef-render-clone-container");
@@ -339,11 +463,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
339
463
  }
340
464
  /**
341
465
  * Legacy getter for fragment index task
342
- * Still used by EFCaptions - maps to unified video seek task
466
+ * Still used by EFCaptions - maps to frameTask
343
467
  */
344
468
  get fragmentIndexTask() {
345
- return this.unifiedVideoSeekTask;
469
+ return this.frameTask;
346
470
  }
471
+ #pendingFrameReadyTime = null;
472
+ #pendingFrameReadyPromise = null;
347
473
  /**
348
474
  * Helper method for tests: wait for the current frame to be ready
349
475
  * This encapsulates the complexity of ensuring the video has updated
@@ -354,6 +480,19 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
354
480
  async waitForFrameReady() {
355
481
  const currentTime = this.currentSourceTimeMs;
356
482
  if (this.desiredSeekTimeMs !== currentTime) this.desiredSeekTimeMs = currentTime;
483
+ if (this.#pendingFrameReadyTime === currentTime && this.#pendingFrameReadyPromise) return this.#pendingFrameReadyPromise;
484
+ this.#pendingFrameReadyTime = currentTime;
485
+ this.#pendingFrameReadyPromise = this.#doWaitForFrameReady(currentTime);
486
+ try {
487
+ await this.#pendingFrameReadyPromise;
488
+ } finally {
489
+ if (this.#pendingFrameReadyTime === currentTime) {
490
+ this.#pendingFrameReadyTime = null;
491
+ this.#pendingFrameReadyPromise = null;
492
+ }
493
+ }
494
+ }
495
+ async #doWaitForFrameReady(_targetTimeMs) {
357
496
  await this.updateComplete;
358
497
  try {
359
498
  await this.frameTask.run();
@@ -363,6 +502,119 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
363
502
  }
364
503
  }
365
504
  /**
505
+ * Capture a video frame directly at a source media timestamp.
506
+ * Bypasses the frameTask system - designed for export/rendering.
507
+ * Does NOT paint to the element's internal canvas.
508
+ *
509
+ * Uses the same routing logic as unified video system:
510
+ * - "auto": main track for production rendering, follows normal routing otherwise
511
+ * - "scrub": force low-res scrub track (for thumbnails)
512
+ * - "main": force full-quality main track
513
+ *
514
+ * @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
515
+ * @param options - Capture options including quality and abort signal
516
+ * @returns Frame data for serialization
517
+ * @public
518
+ */
519
+ async captureFrameAtSourceTime(sourceTimeMs, options = {}) {
520
+ const { quality = "auto", signal } = options;
521
+ signal?.throwIfAborted();
522
+ const mediaEngine = await this.getMediaEngine(signal);
523
+ signal?.throwIfAborted();
524
+ if (!mediaEngine) throw new Error("No media engine available for frame capture");
525
+ const useMainTrack = quality === "main" || quality === "auto" && this.isInProductionRenderingMode();
526
+ let videoSample;
527
+ const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
528
+ signal?.throwIfAborted();
529
+ if (useMainTrack) {
530
+ const videoRendition = mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;
531
+ if (!videoRendition) throw new Error("No video rendition available");
532
+ const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, videoRendition);
533
+ if (segmentId === void 0) throw new Error(`Cannot compute segment ID for time ${sourceTimeMs}ms`);
534
+ const seekingInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
535
+ const fetchSignal = signal ?? new AbortController().signal;
536
+ const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, fetchSignal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, fetchSignal)]);
537
+ if (!initSegment || !mediaSegment) return;
538
+ return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
539
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
540
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
541
+ startTimeOffsetMs: videoRendition.startTimeOffsetMs
542
+ });
543
+ });
544
+ signal?.throwIfAborted();
545
+ if (!seekingInput) throw new Error(`Failed to fetch video segments for time ${sourceTimeMs}ms`);
546
+ const videoTrack = await seekingInput.getFirstVideoTrack();
547
+ signal?.throwIfAborted();
548
+ if (!videoTrack) throw new Error("No video track found in segment");
549
+ videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
550
+ signal?.throwIfAborted();
551
+ } else {
552
+ const scrubRendition = mediaEngine.getScrubVideoRendition?.();
553
+ if (!scrubRendition) return this.captureFrameAtSourceTime(sourceTimeMs, {
554
+ quality: "main",
555
+ signal
556
+ });
557
+ const scrubRenditionWithSrc = {
558
+ ...scrubRendition,
559
+ src: mediaEngine.src
560
+ };
561
+ const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, scrubRenditionWithSrc);
562
+ if (segmentId === void 0) throw new Error(`Cannot compute scrub segment ID for time ${sourceTimeMs}ms`);
563
+ const seekingInput = await scrubInputCache.getOrCreateInput(mediaEngine.src, segmentId, async () => {
564
+ const scrubFetchSignal = signal ?? new AbortController().signal;
565
+ const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, scrubFetchSignal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, scrubFetchSignal)]);
566
+ if (!initSegment || !mediaSegment) return;
567
+ return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
568
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
569
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
570
+ startTimeOffsetMs: scrubRendition.startTimeOffsetMs
571
+ });
572
+ });
573
+ signal?.throwIfAborted();
574
+ if (!seekingInput) return this.captureFrameAtSourceTime(sourceTimeMs, {
575
+ quality: "main",
576
+ signal
577
+ });
578
+ const videoTrack = await seekingInput.getFirstVideoTrack();
579
+ signal?.throwIfAborted();
580
+ if (!videoTrack) return this.captureFrameAtSourceTime(sourceTimeMs, {
581
+ quality: "main",
582
+ signal
583
+ });
584
+ videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
585
+ signal?.throwIfAborted();
586
+ }
587
+ if (!videoSample) throw new Error(`No video sample found at ${sourceTimeMs}ms`);
588
+ const videoFrame = videoSample.toVideoFrame();
589
+ try {
590
+ signal?.throwIfAborted();
591
+ const canvas = new OffscreenCanvas(videoFrame.codedWidth, videoFrame.codedHeight);
592
+ const ctx = canvas.getContext("2d");
593
+ if (!ctx) throw new Error("Failed to get 2d context from OffscreenCanvas");
594
+ ctx.drawImage(videoFrame, 0, 0);
595
+ signal?.throwIfAborted();
596
+ const blob = await canvas.convertToBlob({
597
+ type: "image/jpeg",
598
+ quality: .92
599
+ });
600
+ signal?.throwIfAborted();
601
+ const dataUrl = await new Promise((resolve, reject) => {
602
+ const reader = new FileReader();
603
+ reader.onload = () => resolve(reader.result);
604
+ reader.onerror = reject;
605
+ reader.readAsDataURL(blob);
606
+ });
607
+ signal?.throwIfAborted();
608
+ return {
609
+ dataUrl,
610
+ width: videoFrame.codedWidth,
611
+ height: videoFrame.codedHeight
612
+ };
613
+ } finally {
614
+ videoFrame.close();
615
+ }
616
+ }
617
+ /**
366
618
  * Pre-fetch scrub segments for given timestamps.
367
619
  * Loads 30-second segments sequentially, emitting progress events.
368
620
  * This ensures scrub track is cached for fast thumbnail generation.
@@ -373,7 +625,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
373
625
  * @public
374
626
  */
375
627
  async prefetchScrubSegments(timestamps, onProgress) {
376
- const mediaEngine = await this.mediaEngineTask.taskComplete;
628
+ const mediaEngine = await this.getMediaEngine();
377
629
  if (!mediaEngine) {
378
630
  log("prefetchScrubSegments: no media engine available");
379
631
  return;
@@ -444,7 +696,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
444
696
  * @public
445
697
  */
446
698
  async prefetchMainVideoSegments(timestamps, onProgress) {
447
- const mediaEngine = await this.mediaEngineTask.taskComplete;
699
+ const mediaEngine = await this.getMediaEngine();
448
700
  if (!mediaEngine) {
449
701
  log("prefetchMainVideoSegments: no media engine available");
450
702
  return;
@@ -492,7 +744,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
492
744
  */
493
745
  disconnectedCallback() {
494
746
  super.disconnectedCallback();
495
- this.delayedLoadingState.clearAllLoading();
747
+ this.#delayedLoadingState.clearAllLoading();
496
748
  }
497
749
  didBecomeRoot() {
498
750
  super.didBecomeRoot();