@editframe/elements 0.33.0-beta → 0.34.5-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 +99 -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,3 +1,4 @@
1
+ import { updateAnimations } from "./elements/updateAnimations.js";
1
2
  import { clearCurrentFrameSpan, enableTracing, extractParentContext, setCurrentFrameSpan, withSpan, withSpanAndContext } from "./otel/tracingHelpers.js";
2
3
  import { shallowGetTimegroups } from "./elements/EFTimegroup.js";
3
4
  import { setupBrowserTracing } from "./otel/setupBrowserTracing.js";
@@ -173,7 +174,7 @@ var EFFramegen = class {
173
174
  if (!firstGroup) throw new Error("No temporal elements found");
174
175
  const startingTimeMs = renderOptions.encoderOptions.fromMs;
175
176
  await firstGroup.waitForMediaDurations();
176
- await firstGroup.waitForFrameTasks();
177
+ await firstGroup.frameController.renderFrame(startingTimeMs, { onAnimationsUpdate: (root) => updateAnimations(root) });
177
178
  this.frameDurationMs = 1e3 / renderOptions.encoderOptions.video.framerate;
178
179
  this.time = startingTimeMs;
179
180
  if (this.showFrameBox) {
@@ -200,8 +201,9 @@ var EFFramegen = class {
200
201
  const firstGroup = shallowGetTimegroups(workbench)[0];
201
202
  if (!firstGroup) throw new Error("No temporal elements found");
202
203
  const frameTime = this.renderOptions.encoderOptions.fromMs + frameNumber * this.frameDurationMs;
203
- firstGroup.currentTimeMs = Number(Number(frameTime).toFixed(5));
204
- await firstGroup.waitForFrameTasks();
204
+ const frameTimeMs = Number(Number(frameTime).toFixed(5));
205
+ firstGroup.currentTimeMs = frameTimeMs;
206
+ await firstGroup.frameController.renderFrame(frameTimeMs, { onAnimationsUpdate: (root) => updateAnimations(root) });
205
207
  if (this.showFrameBox) this.frameBox.innerHTML = `
206
208
  <div>🖼️ Frame: ${frameNumber}</div>
207
209
  <div>🕛 Segment: ${this.time.toFixed(4)}</div>
@@ -1 +1 @@
1
- {"version":3,"file":"EF_FRAMEGEN.js","names":[],"sources":["../src/EF_FRAMEGEN.ts"],"sourcesContent":["import type { VideoRenderOptions } from \"@editframe/assets\";\n\nimport { shallowGetTimegroups } from \"./elements/EFTimegroup.js\";\nimport { setupBrowserTracing } from \"./otel/setupBrowserTracing.js\";\nimport {\n clearCurrentFrameSpan,\n enableTracing,\n extractParentContext,\n setCurrentFrameSpan,\n type TraceContext,\n withSpan,\n withSpanAndContext,\n} from \"./otel/tracingHelpers.js\";\n\ninterface Bridge {\n onInitialize: (\n callback: (\n renderOptions: VideoRenderOptions,\n traceContext?: TraceContext,\n otelEndpoint?: string,\n ) => void,\n ) => void;\n\n initialized(): void;\n\n onBeginFrame(\n callback: (\n frameNumber: number,\n isLast: boolean,\n traceContext?: TraceContext,\n ) => void,\n ): void;\n\n onTriggerCanvas(callback: (traceContext?: TraceContext) => void): void;\n\n frameReady(frameNumber: number, audioSamples: ArrayBuffer): void;\n\n error(error: Error): void;\n\n syncLog(sequence: number, message: string, callback: () => void): void;\n\n exportSpans?: (endpoint: string, payload: string) => void;\n}\n\ndeclare global {\n interface Window {\n EF_FRAMEGEN?: EFFramegen;\n FRAMEGEN_BRIDGE?: Bridge;\n FRAMEGEN_BINDING?: any;\n FRAMEGEN_BINDING_error?: (error: Error) => void;\n EF_RENDERING?: () => boolean;\n }\n}\n\nclass TriggerCanvas {\n private canvas: HTMLCanvasElement;\n private ctx: CanvasRenderingContext2D;\n\n private canvasInitialized = false;\n\n constructor() {\n this.canvas = document.createElement(\"canvas\");\n const ctx = this.canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.ctx = ctx;\n this.ctx.fillStyle = \"transparent\";\n }\n\n initialize() {\n if (this.canvasInitialized) return;\n this.canvasInitialized = true;\n this.canvas.width = 1;\n this.canvas.height = 1;\n Object.assign(this.canvas.style, {\n position: \"fixed\",\n top: \"0px\",\n left: \"0px\",\n width: \"100%\",\n height: \"100%\",\n zIndex: \"100000\",\n });\n document.body.appendChild(this.canvas);\n }\n\n trigger() {\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n}\n\nexport class EFFramegen {\n time = 0;\n frameDurationMs = 0;\n audioBufferPromise?: Promise<AudioBuffer>;\n renderOptions?: VideoRenderOptions;\n frameBox = document.createElement(\"div\");\n BRIDGE: typeof window.FRAMEGEN_BRIDGE;\n triggerCanvas = new TriggerCanvas();\n verificationCanvas?: HTMLCanvasElement;\n verificationCtx?: CanvasRenderingContext2D;\n private logSequence = 0;\n\n // Frame sequence coordination\n public frameTasksInProgress = false;\n public currentFrameNumber = 0;\n\n trace(...args: any[]) {\n console.trace(\"[EF_FRAMEGEN]\", ...args);\n }\n\n async syncLog(...args: any[]): Promise<void> {\n if (!this.BRIDGE) {\n // Fallback to regular console.log if no bridge\n console.log(\"[EF_FRAMEGEN]\", ...args);\n return;\n }\n\n const sequence = ++this.logSequence;\n const message = args\n .map((arg) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg),\n )\n .join(\" \");\n\n return new Promise<void>((resolve) => {\n // biome-ignore lint/style/noNonNullAssertion: We know BRIDGE is set due to the guard above\n this.BRIDGE!.syncLog(sequence, message, () => {\n resolve();\n });\n });\n }\n\n private initializeVerificationCanvas() {\n if (this.verificationCanvas) {\n return;\n }\n\n this.verificationCanvas = document.createElement(\"canvas\");\n const ctx = this.verificationCanvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Verification canvas 2d context not ready\");\n this.verificationCtx = ctx;\n\n // Size to match the workbench width, 1 pixel height for verification strip\n const workbench = document.querySelector(\"ef-workbench\") as HTMLElement;\n if (workbench) {\n this.verificationCanvas.width = workbench.clientWidth;\n this.verificationCanvas.height = 1;\n\n // Position at the bottom of the workbench (beyond content height)\n Object.assign(this.verificationCanvas.style, {\n position: \"fixed\",\n left: \"0px\",\n bottom: \"0px\",\n width: `${workbench.clientWidth}px`,\n height: \"1px\",\n zIndex: \"99999\", // Above trigger canvas\n });\n\n document.body.appendChild(this.verificationCanvas);\n }\n }\n\n private drawVerificationStrip(frameNumber: number) {\n this.initializeVerificationCanvas();\n\n if (!this.verificationCanvas || !this.verificationCtx) {\n return;\n }\n\n const width = this.verificationCanvas.width;\n const height = this.verificationCanvas.height;\n\n // Clear the strip\n this.verificationCtx.clearRect(0, 0, width, height);\n\n // Encode frame number into RGB (24-bit)\n // R=high byte, G=middle byte, B=low byte\n const red = Math.floor(frameNumber / (256 * 256)) % 256;\n const green = Math.floor(frameNumber / 256) % 256;\n const blue = frameNumber % 256;\n\n // Fill the entire strip with the encoded frame number\n this.verificationCtx.fillStyle = `rgb(${red}, ${green}, ${blue})`;\n this.verificationCtx.fillRect(0, 0, width, height);\n }\n\n constructor() {\n this.BRIDGE = window.FRAMEGEN_BRIDGE;\n if (this.BRIDGE) {\n this.connectToBridge();\n }\n }\n\n /**\n * Helper method to get the workbench and set its rendering state.\n * This ensures consistent state management across the framegen lifecycle.\n */\n private setWorkbenchRendering(isRendering: boolean) {\n const workbench = document.querySelector(\"ef-workbench\");\n if (workbench) {\n workbench.rendering = isRendering;\n }\n }\n\n connectToBridge() {\n const BRIDGE = this.BRIDGE;\n if (!BRIDGE) {\n throw new Error(\"No BRIDGE when attempting to connect to bridge\");\n }\n\n BRIDGE.onInitialize(async (renderOptions, traceContext, otelEndpoint) => {\n // Only enable tracing if explicitly requested in renderOptions\n if (renderOptions.enableTracing && otelEndpoint) {\n enableTracing();\n await setupBrowserTracing({\n otelEndpoint,\n serviceName: \"telecine-browser\",\n bridge: BRIDGE,\n useBatching: true, // Batch spans to reduce overhead during rendering\n });\n }\n\n const parentContext = extractParentContext(traceContext);\n\n await withSpan(\n \"browser.initialize\",\n {\n width: renderOptions.encoderOptions.video.width,\n height: renderOptions.encoderOptions.video.height,\n fps: renderOptions.encoderOptions.video.framerate,\n durationMs:\n renderOptions.encoderOptions.toMs -\n renderOptions.encoderOptions.fromMs,\n },\n parentContext,\n async () => {\n try {\n await this.initialize(renderOptions);\n } catch (error) {\n // If initialization fails, ensure rendering state is cleared\n this.setWorkbenchRendering(false);\n console.error(\n \"[EF_FRAMEGEN.connectToBridge] error initializing\",\n error,\n );\n throw error;\n }\n },\n );\n\n BRIDGE.initialized();\n });\n\n BRIDGE.onBeginFrame((frameNumber, isLast, traceContext) => {\n const parentContext = extractParentContext(traceContext);\n withSpanAndContext(\n \"browser.frame.render\",\n {\n frameNumber,\n isLast,\n },\n parentContext,\n async (span, _spanContext) => {\n // Store the span itself for child operations\n // This allows spans created in Lit Tasks to use it as their parent\n setCurrentFrameSpan(span);\n\n try {\n await this.beginFrame(frameNumber, isLast);\n } catch (error) {\n // If an error occurs during rendering, ensure rendering state is cleared\n this.setWorkbenchRendering(false);\n throw error;\n } finally {\n clearCurrentFrameSpan();\n }\n },\n ).catch((error) => {\n console.error(\"[EF_FRAMEGEN.beginFrame] error:\", error);\n // Ensure rendering state is cleared on error\n this.setWorkbenchRendering(false);\n clearCurrentFrameSpan();\n throw error;\n });\n });\n\n BRIDGE.onTriggerCanvas((traceContext) => {\n const parentContext = extractParentContext(traceContext);\n\n withSpan(\"browser.canvas.trigger\", {}, parentContext, async () => {\n this.triggerCanvas.trigger();\n }).catch((error) => {\n console.error(\"[EF_FRAMEGEN.triggerCanvas] error:\", error);\n });\n });\n }\n\n get showFrameBox() {\n return this.renderOptions?.showFrameBox ?? false;\n }\n\n async initialize(renderOptions: VideoRenderOptions) {\n this.renderOptions = renderOptions;\n\n const workbench = document.querySelector(\"ef-workbench\");\n if (!workbench) {\n throw new Error(\"No workbench found\");\n }\n this.setWorkbenchRendering(true);\n workbench.playing = false;\n const timegroups = shallowGetTimegroups(workbench);\n const firstGroup = timegroups[0];\n if (!firstGroup) {\n throw new Error(\"No temporal elements found\");\n }\n const startingTimeMs = renderOptions.encoderOptions.fromMs;\n await firstGroup.waitForMediaDurations();\n await firstGroup.waitForFrameTasks();\n\n this.frameDurationMs = 1000 / renderOptions.encoderOptions.video.framerate;\n\n this.time = startingTimeMs;\n if (this.showFrameBox) {\n Object.assign(this.frameBox.style, {\n width: \"200px\",\n height: \"100px\",\n font: \"10px Arial\",\n backgroundColor: \"white\",\n position: \"absolute\",\n top: \"0px\",\n right: \"0px\",\n zIndex: \"100000\",\n });\n document.body.prepend(this.frameBox);\n }\n\n this.triggerCanvas.initialize();\n\n // These times are aligned to the audio frame boundaries\n // And they include padding if any.\n this.audioBufferPromise = firstGroup.renderAudio(\n renderOptions.encoderOptions.alignedFromUs / 1000,\n renderOptions.encoderOptions.alignedToUs / 1000,\n );\n }\n\n async beginFrame(frameNumber: number, isLast: boolean) {\n if (this.renderOptions === undefined) {\n throw new Error(\"No renderOptions\");\n }\n const workbench = document.querySelector(\"ef-workbench\");\n if (!workbench) {\n throw new Error(\"No workbench found\");\n }\n this.setWorkbenchRendering(true);\n const timegroups = shallowGetTimegroups(workbench);\n const firstGroup = timegroups[0];\n if (!firstGroup) {\n throw new Error(\"No temporal elements found\");\n }\n\n // Calculate base frame time using normal progression\n const frameTime =\n this.renderOptions.encoderOptions.fromMs +\n frameNumber * this.frameDurationMs;\n firstGroup.currentTimeMs = Number(Number(frameTime).toFixed(5));\n await firstGroup.waitForFrameTasks();\n if (this.showFrameBox) {\n this.frameBox.innerHTML = `\n <div>🖼️ Frame: ${frameNumber}</div>\n <div>🕛 Segment: ${this.time.toFixed(4)}</div>\n <div>🕛 Frame: ${firstGroup.currentTimeMs.toFixed(4)}</div>\n <div> from-to: ${this.renderOptions.encoderOptions.fromMs.toFixed(4)} - ${this.renderOptions.encoderOptions.toMs.toFixed(4)}</div>\n `;\n }\n\n // Draw verification pixel strip for frame verification\n this.drawVerificationStrip(frameNumber);\n\n if (isLast && this.audioBufferPromise) {\n // Currently we emit the audio in one belch at the end of the render.\n // This is not ideal, but it's the simplest thing that could possibly work.\n // We could either emit it slices, or in parallel with the video.\n // But in any case, it's fine for now.\n const renderedAudio = await this.audioBufferPromise;\n\n const channelCount = renderedAudio.numberOfChannels;\n\n const interleavedSamples = new Float32Array(\n channelCount * renderedAudio.length,\n );\n\n for (let i = 0; i < renderedAudio.length; i++) {\n for (let j = 0; j < channelCount; j++) {\n interleavedSamples.set(\n renderedAudio.getChannelData(j).slice(i, i + 1),\n i * channelCount + j,\n );\n }\n }\n\n if (this.BRIDGE) {\n this.BRIDGE.frameReady(frameNumber, interleavedSamples.buffer);\n } else {\n const fileReader = new FileReader();\n fileReader.readAsDataURL(new Blob([interleavedSamples.buffer]));\n await new Promise((resolve, reject) => {\n fileReader.onload = resolve;\n fileReader.onerror = reject;\n });\n return fileReader.result;\n }\n \n // Rendering is complete after the last frame\n this.setWorkbenchRendering(false);\n } else {\n if (this.BRIDGE) {\n this.BRIDGE.frameReady(frameNumber, new ArrayBuffer(0));\n } else {\n const fileReader = new FileReader();\n fileReader.readAsDataURL(new Blob([]));\n await new Promise((resolve, reject) => {\n fileReader.onload = resolve;\n fileReader.onerror = reject;\n });\n return fileReader.result;\n }\n }\n }\n}\n\nif (typeof window !== \"undefined\") {\n window.EF_FRAMEGEN = new EFFramegen();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EF_FRAMEGEN.js","names":[],"sources":["../src/EF_FRAMEGEN.ts"],"sourcesContent":["import type { VideoRenderOptions } from \"@editframe/assets\";\n\nimport { shallowGetTimegroups } from \"./elements/EFTimegroup.js\";\nimport { updateAnimations } from \"./elements/updateAnimations.js\";\nimport { setupBrowserTracing } from \"./otel/setupBrowserTracing.js\";\nimport {\n clearCurrentFrameSpan,\n enableTracing,\n extractParentContext,\n setCurrentFrameSpan,\n type TraceContext,\n withSpan,\n withSpanAndContext,\n} from \"./otel/tracingHelpers.js\";\n\ninterface Bridge {\n onInitialize: (\n callback: (\n renderOptions: VideoRenderOptions,\n traceContext?: TraceContext,\n otelEndpoint?: string,\n ) => void,\n ) => void;\n\n initialized(): void;\n\n onBeginFrame(\n callback: (\n frameNumber: number,\n isLast: boolean,\n traceContext?: TraceContext,\n ) => void,\n ): void;\n\n onTriggerCanvas(callback: (traceContext?: TraceContext) => void): void;\n\n frameReady(frameNumber: number, audioSamples: ArrayBuffer): void;\n\n error(error: Error): void;\n\n syncLog(sequence: number, message: string, callback: () => void): void;\n\n exportSpans?: (endpoint: string, payload: string) => void;\n}\n\ndeclare global {\n interface Window {\n EF_FRAMEGEN?: EFFramegen;\n FRAMEGEN_BRIDGE?: Bridge;\n FRAMEGEN_BINDING?: any;\n FRAMEGEN_BINDING_error?: (error: Error) => void;\n EF_RENDERING?: () => boolean;\n }\n}\n\nclass TriggerCanvas {\n private canvas: HTMLCanvasElement;\n private ctx: CanvasRenderingContext2D;\n\n private canvasInitialized = false;\n\n constructor() {\n this.canvas = document.createElement(\"canvas\");\n const ctx = this.canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.ctx = ctx;\n this.ctx.fillStyle = \"transparent\";\n }\n\n initialize() {\n if (this.canvasInitialized) return;\n this.canvasInitialized = true;\n this.canvas.width = 1;\n this.canvas.height = 1;\n Object.assign(this.canvas.style, {\n position: \"fixed\",\n top: \"0px\",\n left: \"0px\",\n width: \"100%\",\n height: \"100%\",\n zIndex: \"100000\",\n });\n document.body.appendChild(this.canvas);\n }\n\n trigger() {\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n}\n\nexport class EFFramegen {\n time = 0;\n frameDurationMs = 0;\n audioBufferPromise?: Promise<AudioBuffer>;\n renderOptions?: VideoRenderOptions;\n frameBox = document.createElement(\"div\");\n BRIDGE: typeof window.FRAMEGEN_BRIDGE;\n triggerCanvas = new TriggerCanvas();\n verificationCanvas?: HTMLCanvasElement;\n verificationCtx?: CanvasRenderingContext2D;\n private logSequence = 0;\n\n // Frame sequence coordination\n public frameTasksInProgress = false;\n public currentFrameNumber = 0;\n\n trace(...args: any[]) {\n console.trace(\"[EF_FRAMEGEN]\", ...args);\n }\n\n async syncLog(...args: any[]): Promise<void> {\n if (!this.BRIDGE) {\n // Fallback to regular console.log if no bridge\n console.log(\"[EF_FRAMEGEN]\", ...args);\n return;\n }\n\n const sequence = ++this.logSequence;\n const message = args\n .map((arg) =>\n typeof arg === \"object\" ? JSON.stringify(arg) : String(arg),\n )\n .join(\" \");\n\n return new Promise<void>((resolve) => {\n // biome-ignore lint/style/noNonNullAssertion: We know BRIDGE is set due to the guard above\n this.BRIDGE!.syncLog(sequence, message, () => {\n resolve();\n });\n });\n }\n\n private initializeVerificationCanvas() {\n if (this.verificationCanvas) {\n return;\n }\n\n this.verificationCanvas = document.createElement(\"canvas\");\n const ctx = this.verificationCanvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Verification canvas 2d context not ready\");\n this.verificationCtx = ctx;\n\n // Size to match the workbench width, 1 pixel height for verification strip\n const workbench = document.querySelector(\"ef-workbench\") as HTMLElement;\n if (workbench) {\n this.verificationCanvas.width = workbench.clientWidth;\n this.verificationCanvas.height = 1;\n\n // Position at the bottom of the workbench (beyond content height)\n Object.assign(this.verificationCanvas.style, {\n position: \"fixed\",\n left: \"0px\",\n bottom: \"0px\",\n width: `${workbench.clientWidth}px`,\n height: \"1px\",\n zIndex: \"99999\", // Above trigger canvas\n });\n\n document.body.appendChild(this.verificationCanvas);\n }\n }\n\n private drawVerificationStrip(frameNumber: number) {\n this.initializeVerificationCanvas();\n\n if (!this.verificationCanvas || !this.verificationCtx) {\n return;\n }\n\n const width = this.verificationCanvas.width;\n const height = this.verificationCanvas.height;\n\n // Clear the strip\n this.verificationCtx.clearRect(0, 0, width, height);\n\n // Encode frame number into RGB (24-bit)\n // R=high byte, G=middle byte, B=low byte\n const red = Math.floor(frameNumber / (256 * 256)) % 256;\n const green = Math.floor(frameNumber / 256) % 256;\n const blue = frameNumber % 256;\n\n // Fill the entire strip with the encoded frame number\n this.verificationCtx.fillStyle = `rgb(${red}, ${green}, ${blue})`;\n this.verificationCtx.fillRect(0, 0, width, height);\n }\n\n constructor() {\n this.BRIDGE = window.FRAMEGEN_BRIDGE;\n if (this.BRIDGE) {\n this.connectToBridge();\n }\n }\n\n /**\n * Helper method to get the workbench and set its rendering state.\n * This ensures consistent state management across the framegen lifecycle.\n */\n private setWorkbenchRendering(isRendering: boolean) {\n const workbench = document.querySelector(\"ef-workbench\");\n if (workbench) {\n workbench.rendering = isRendering;\n }\n }\n\n connectToBridge() {\n const BRIDGE = this.BRIDGE;\n if (!BRIDGE) {\n throw new Error(\"No BRIDGE when attempting to connect to bridge\");\n }\n\n BRIDGE.onInitialize(async (renderOptions, traceContext, otelEndpoint) => {\n // Only enable tracing if explicitly requested in renderOptions\n if (renderOptions.enableTracing && otelEndpoint) {\n enableTracing();\n await setupBrowserTracing({\n otelEndpoint,\n serviceName: \"telecine-browser\",\n bridge: BRIDGE,\n useBatching: true, // Batch spans to reduce overhead during rendering\n });\n }\n\n const parentContext = extractParentContext(traceContext);\n\n await withSpan(\n \"browser.initialize\",\n {\n width: renderOptions.encoderOptions.video.width,\n height: renderOptions.encoderOptions.video.height,\n fps: renderOptions.encoderOptions.video.framerate,\n durationMs:\n renderOptions.encoderOptions.toMs -\n renderOptions.encoderOptions.fromMs,\n },\n parentContext,\n async () => {\n try {\n await this.initialize(renderOptions);\n } catch (error) {\n // If initialization fails, ensure rendering state is cleared\n this.setWorkbenchRendering(false);\n console.error(\n \"[EF_FRAMEGEN.connectToBridge] error initializing\",\n error,\n );\n throw error;\n }\n },\n );\n\n BRIDGE.initialized();\n });\n\n BRIDGE.onBeginFrame((frameNumber, isLast, traceContext) => {\n const parentContext = extractParentContext(traceContext);\n withSpanAndContext(\n \"browser.frame.render\",\n {\n frameNumber,\n isLast,\n },\n parentContext,\n async (span, _spanContext) => {\n // Store the span itself for child operations\n // This allows spans created in Lit Tasks to use it as their parent\n setCurrentFrameSpan(span);\n\n try {\n await this.beginFrame(frameNumber, isLast);\n } catch (error) {\n // If an error occurs during rendering, ensure rendering state is cleared\n this.setWorkbenchRendering(false);\n throw error;\n } finally {\n clearCurrentFrameSpan();\n }\n },\n ).catch((error) => {\n console.error(\"[EF_FRAMEGEN.beginFrame] error:\", error);\n // Ensure rendering state is cleared on error\n this.setWorkbenchRendering(false);\n clearCurrentFrameSpan();\n throw error;\n });\n });\n\n BRIDGE.onTriggerCanvas((traceContext) => {\n const parentContext = extractParentContext(traceContext);\n\n withSpan(\"browser.canvas.trigger\", {}, parentContext, async () => {\n this.triggerCanvas.trigger();\n }).catch((error) => {\n console.error(\"[EF_FRAMEGEN.triggerCanvas] error:\", error);\n });\n });\n }\n\n get showFrameBox() {\n return this.renderOptions?.showFrameBox ?? false;\n }\n\n async initialize(renderOptions: VideoRenderOptions) {\n this.renderOptions = renderOptions;\n\n const workbench = document.querySelector(\"ef-workbench\");\n if (!workbench) {\n throw new Error(\"No workbench found\");\n }\n this.setWorkbenchRendering(true);\n workbench.playing = false;\n const timegroups = shallowGetTimegroups(workbench);\n const firstGroup = timegroups[0];\n if (!firstGroup) {\n throw new Error(\"No temporal elements found\");\n }\n const startingTimeMs = renderOptions.encoderOptions.fromMs;\n await firstGroup.waitForMediaDurations();\n // Use FrameController for centralized frame rendering\n await firstGroup.frameController.renderFrame(startingTimeMs, {\n onAnimationsUpdate: (root) => updateAnimations(root as typeof firstGroup),\n });\n\n this.frameDurationMs = 1000 / renderOptions.encoderOptions.video.framerate;\n\n this.time = startingTimeMs;\n if (this.showFrameBox) {\n Object.assign(this.frameBox.style, {\n width: \"200px\",\n height: \"100px\",\n font: \"10px Arial\",\n backgroundColor: \"white\",\n position: \"absolute\",\n top: \"0px\",\n right: \"0px\",\n zIndex: \"100000\",\n });\n document.body.prepend(this.frameBox);\n }\n\n this.triggerCanvas.initialize();\n\n // These times are aligned to the audio frame boundaries\n // And they include padding if any.\n this.audioBufferPromise = firstGroup.renderAudio(\n renderOptions.encoderOptions.alignedFromUs / 1000,\n renderOptions.encoderOptions.alignedToUs / 1000,\n );\n }\n\n async beginFrame(frameNumber: number, isLast: boolean) {\n if (this.renderOptions === undefined) {\n throw new Error(\"No renderOptions\");\n }\n const workbench = document.querySelector(\"ef-workbench\");\n if (!workbench) {\n throw new Error(\"No workbench found\");\n }\n this.setWorkbenchRendering(true);\n const timegroups = shallowGetTimegroups(workbench);\n const firstGroup = timegroups[0];\n if (!firstGroup) {\n throw new Error(\"No temporal elements found\");\n }\n\n // Calculate base frame time using normal progression\n const frameTime =\n this.renderOptions.encoderOptions.fromMs +\n frameNumber * this.frameDurationMs;\n const frameTimeMs = Number(Number(frameTime).toFixed(5));\n firstGroup.currentTimeMs = frameTimeMs;\n // Use FrameController for centralized frame rendering\n await firstGroup.frameController.renderFrame(frameTimeMs, {\n onAnimationsUpdate: (root) => updateAnimations(root as typeof firstGroup),\n });\n if (this.showFrameBox) {\n this.frameBox.innerHTML = `\n <div>🖼️ Frame: ${frameNumber}</div>\n <div>🕛 Segment: ${this.time.toFixed(4)}</div>\n <div>🕛 Frame: ${firstGroup.currentTimeMs.toFixed(4)}</div>\n <div> from-to: ${this.renderOptions.encoderOptions.fromMs.toFixed(4)} - ${this.renderOptions.encoderOptions.toMs.toFixed(4)}</div>\n `;\n }\n\n // Draw verification pixel strip for frame verification\n this.drawVerificationStrip(frameNumber);\n\n if (isLast && this.audioBufferPromise) {\n // Currently we emit the audio in one belch at the end of the render.\n // This is not ideal, but it's the simplest thing that could possibly work.\n // We could either emit it slices, or in parallel with the video.\n // But in any case, it's fine for now.\n const renderedAudio = await this.audioBufferPromise;\n\n const channelCount = renderedAudio.numberOfChannels;\n\n const interleavedSamples = new Float32Array(\n channelCount * renderedAudio.length,\n );\n\n for (let i = 0; i < renderedAudio.length; i++) {\n for (let j = 0; j < channelCount; j++) {\n interleavedSamples.set(\n renderedAudio.getChannelData(j).slice(i, i + 1),\n i * channelCount + j,\n );\n }\n }\n\n if (this.BRIDGE) {\n this.BRIDGE.frameReady(frameNumber, interleavedSamples.buffer);\n } else {\n const fileReader = new FileReader();\n fileReader.readAsDataURL(new Blob([interleavedSamples.buffer]));\n await new Promise((resolve, reject) => {\n fileReader.onload = resolve;\n fileReader.onerror = reject;\n });\n return fileReader.result;\n }\n \n // Rendering is complete after the last frame\n this.setWorkbenchRendering(false);\n } else {\n if (this.BRIDGE) {\n this.BRIDGE.frameReady(frameNumber, new ArrayBuffer(0));\n } else {\n const fileReader = new FileReader();\n fileReader.readAsDataURL(new Blob([]));\n await new Promise((resolve, reject) => {\n fileReader.onload = resolve;\n fileReader.onerror = reject;\n });\n return fileReader.result;\n }\n }\n }\n}\n\nif (typeof window !== \"undefined\") {\n window.EF_FRAMEGEN = new EFFramegen();\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.94.0/helpers/decorate.js
1
+ //#region \0@oxc-project+runtime@0.95.0/helpers/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -2,9 +2,12 @@ import { TemporalMixinInterface } from "../elements/EFTemporal.js";
2
2
  import { CanvasElementData } from "./api/types.js";
3
3
  import { SelectionContext } from "./selection/selectionContext.js";
4
4
  import { PanZoomTransform } from "../elements/EFPanZoom.js";
5
- import * as lit33 from "lit";
5
+ import "./overlays/SelectionOverlay.js";
6
+ import "../gui/EFOverlayLayer.js";
7
+ import "../gui/EFTransformHandles.js";
8
+ import * as lit29 from "lit";
6
9
  import { LitElement } from "lit";
7
- import * as lit_html32 from "lit-html";
10
+ import * as lit_html28 from "lit-html";
8
11
 
9
12
  //#region src/canvas/EFCanvas.d.ts
10
13
  declare const EFCanvas_base: typeof LitElement;
@@ -84,7 +87,7 @@ declare const EFCanvas_base: typeof LitElement;
84
87
  * Manages existing elements (EF* elements, divs, etc.) and provides selection functionality.
85
88
  */
86
89
  declare class EFCanvas extends EFCanvas_base {
87
- static styles: lit33.CSSResult[];
90
+ static styles: lit29.CSSResult[];
88
91
  panZoomTransform?: PanZoomTransform;
89
92
  elementIdAttribute: string;
90
93
  enableTransformHandles: boolean;
@@ -299,7 +302,7 @@ declare class EFCanvas extends EFCanvas_base {
299
302
  * Cleanup transform handles.
300
303
  */
301
304
  private cleanupTransformHandles;
302
- render(): lit_html32.TemplateResult<1>;
305
+ render(): lit_html28.TemplateResult<1>;
303
306
  }
304
307
  declare global {
305
308
  interface HTMLElementTagNameMap {
@@ -1,4 +1,4 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
2
  import { TWMixin } from "../gui/TWMixin2.js";
3
3
  import { EFTargetable } from "../elements/TargetController.js";
4
4
  import { panZoomTransformContext } from "../gui/panZoomTransformContext.js";
@@ -1,6 +1,6 @@
1
- import * as lit34 from "lit";
1
+ import * as lit30 from "lit";
2
2
  import { LitElement } from "lit";
3
- import * as lit_html33 from "lit-html";
3
+ import * as lit_html29 from "lit-html";
4
4
 
5
5
  //#region src/canvas/EFCanvasItem.d.ts
6
6
 
@@ -28,7 +28,7 @@ import * as lit_html33 from "lit-html";
28
28
  * ```
29
29
  */
30
30
  declare class EFCanvasItem extends LitElement {
31
- static styles: lit34.CSSResult;
31
+ static styles: lit30.CSSResult;
32
32
  id: string;
33
33
  private canvas;
34
34
  private api;
@@ -43,7 +43,7 @@ declare class EFCanvasItem extends LitElement {
43
43
  * Unregister this element from the canvas.
44
44
  */
45
45
  private unregister;
46
- render(): lit_html33.TemplateResult<1>;
46
+ render(): lit_html29.TemplateResult<1>;
47
47
  }
48
48
  declare global {
49
49
  interface HTMLElementTagNameMap {
@@ -1,4 +1,4 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
2
  import { CanvasAPI } from "./api/CanvasAPI.js";
3
3
  import { LitElement, css, html } from "lit";
4
4
  import { customElement, property } from "lit/decorators.js";
@@ -0,0 +1,95 @@
1
+ import { SelectionContext } from "../selection/selectionContext.js";
2
+ import { PanZoomTransform } from "../../elements/EFPanZoom.js";
3
+ import * as lit36 from "lit";
4
+ import { LitElement } from "lit";
5
+ import * as lit_html34 from "lit-html";
6
+
7
+ //#region src/canvas/overlays/SelectionOverlay.d.ts
8
+ /**
9
+ * Selection overlay that renders unscaled selection indicators.
10
+ * Uses fixed positioning to ensure 1:1 pixel ratio regardless of zoom level.
11
+ */
12
+ declare class SelectionOverlay extends LitElement {
13
+ static styles: lit36.CSSResult[];
14
+ createRenderRoot(): this;
15
+ firstUpdated(changedProperties: Map<string | number | symbol, unknown>): void;
16
+ selectionFromContext?: SelectionContext;
17
+ panZoomTransformFromContext?: PanZoomTransform;
18
+ /**
19
+ * Selection context as fallback for when overlay is outside context providers (e.g., sibling of pan-zoom).
20
+ */
21
+ selection?: SelectionContext;
22
+ /**
23
+ * Pan/zoom transform as fallback for when overlay is outside context providers (e.g., sibling of pan-zoom).
24
+ */
25
+ panZoomTransform?: PanZoomTransform;
26
+ private canvasElement;
27
+ /**
28
+ * Canvas element property - can be set directly when overlay is outside context providers.
29
+ */
30
+ canvas?: HTMLElement;
31
+ /**
32
+ * Complete overlay state - calculated from targets using the abstraction layer.
33
+ * This is the SINGLE source of truth for overlay bounds.
34
+ */
35
+ private overlayState;
36
+ private lastSelectionMode;
37
+ private animationFrame?;
38
+ private rafLoopActive;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ /**
42
+ * React to selection context changes to ensure box selection visual updates.
43
+ * This is called whenever Lit detects a property change, including context updates.
44
+ * Note: We don't call requestUpdate() here to avoid the Lit warning about scheduling
45
+ * updates after an update completes. The RAF loop handles all updates.
46
+ */
47
+ updated(changedProperties: Map<string | number | symbol, unknown>): void;
48
+ /**
49
+ * Find the EFCanvas element.
50
+ * Handles both cases:
51
+ * 1. Overlay is inside EFCanvas's shadow DOM (old case)
52
+ * 2. Overlay is a sibling of ef-pan-zoom (new case - outside transform)
53
+ */
54
+ private findCanvasElement;
55
+ /**
56
+ * Start continuous RAF loop for smooth overlay updates.
57
+ */
58
+ private startRafLoop;
59
+ /**
60
+ * Stop RAF loop.
61
+ */
62
+ private stopRafLoop;
63
+ /**
64
+ * Continuous RAF loop to update overlays every frame using Lit render cycle.
65
+ */
66
+ private rafLoop;
67
+ /**
68
+ * Get the effective selection context (from context or property).
69
+ */
70
+ private get effectiveSelection();
71
+ /**
72
+ * Get the effective pan-zoom transform (from context or property).
73
+ */
74
+ private get effectivePanZoomTransform();
75
+ /**
76
+ * Update overlay data state using the abstraction layer.
77
+ *
78
+ * This method now uses the clean separation of:
79
+ * - SEMANTICS: getOverlayTargets() determines WHAT should be shown
80
+ * - MECHANISM: calculateOverlayState() determines HOW to show it
81
+ */
82
+ private updateOverlayData;
83
+ /**
84
+ * Read current transform directly from panzoom element.
85
+ * This ensures we always have fresh values instead of stale property/context.
86
+ */
87
+ private readCurrentTransform;
88
+ render(): lit_html34.TemplateResult<1>;
89
+ }
90
+ declare global {
91
+ interface HTMLElementTagNameMap {
92
+ "ef-canvas-selection-overlay": SelectionOverlay;
93
+ }
94
+ }
95
+ //# sourceMappingURL=SelectionOverlay.d.ts.map
@@ -1,4 +1,4 @@
1
- import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
2
  import { panZoomTransformContext } from "../../gui/panZoomTransformContext.js";
3
3
  import { selectionContext } from "../selection/selectionContext.js";
4
4
  import { calculateOverlayState, getOverlayTargets } from "./overlayState.js";
@@ -35,16 +35,7 @@ var SelectionController = class {
35
35
  */
36
36
  createContextProxy() {
37
37
  const controller = this;
38
- return {
39
- get selectedIds() {
40
- return controller.selectionModel.selectedIds;
41
- },
42
- get selectionMode() {
43
- return controller.selectionModel.selectionMode;
44
- },
45
- get boxSelectBounds() {
46
- return controller.selectionModel.boxSelectBounds;
47
- },
38
+ return new Proxy({
48
39
  select: (id) => {
49
40
  controller.selectionModel.select(id);
50
41
  },
@@ -96,7 +87,12 @@ var SelectionController = class {
96
87
  removeEventListener: (type, listener) => {
97
88
  controller.selectionModel.removeEventListener(type, listener);
98
89
  }
99
- };
90
+ }, { get(target, prop) {
91
+ if (prop === "selectedIds") return controller.selectionModel.selectedIds;
92
+ if (prop === "selectionMode") return controller.selectionModel.selectionMode;
93
+ if (prop === "boxSelectBounds") return controller.selectionModel.boxSelectBounds;
94
+ return target[prop];
95
+ } });
100
96
  }
101
97
  };
102
98
 
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionController.js","names":[],"sources":["../../../src/canvas/selection/SelectionController.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { SelectionModel } from \"./SelectionModel.js\";\nimport type { SelectionContext } from \"./selectionContext.js\";\n\n/**\n * Reactive controller that bridges SelectionModel and Lit element lifecycle.\n * Provides selection context to child elements.\n */\nexport class SelectionController implements ReactiveController {\n private host: ReactiveControllerHost;\n private selectionModel: SelectionModel;\n private hitTestFn: ((bounds: DOMRect) => string[]) | null = null;\n selectionContext: SelectionContext;\n\n constructor(host: ReactiveControllerHost) {\n this.host = host;\n this.selectionModel = new SelectionModel();\n this.selectionContext = this.createContextProxy();\n host.addController(this);\n\n // Listen to selection change events from the model\n // Use queueMicrotask to defer the update and avoid Lit warning about\n // scheduling updates after update completed (change-in-update)\n this.selectionModel.addEventListener(\"selectionchange\", () => {\n queueMicrotask(() => this.host.requestUpdate());\n });\n }\n\n hostConnected(): void {\n // Context is provided via @provide decorator\n }\n\n hostDisconnected(): void {\n // Cleanup if needed\n }\n\n /**\n * Set the hit test function for box selection.\n */\n setHitTest(fn: (bounds: DOMRect) => string[]): void {\n this.hitTestFn = fn;\n }\n\n /**\n * Get the underlying selection model.\n */\n getModel(): SelectionModel {\n return this.selectionModel;\n }\n\n /**\n * Create a proxy context that delegates to the selection model.\n */\n private createContextProxy(): SelectionContext {\n const controller = this;\n return {\n get selectedIds() {\n return controller.selectionModel.selectedIds;\n },\n get selectionMode() {\n return controller.selectionModel.selectionMode;\n },\n get boxSelectBounds() {\n return controller.selectionModel.boxSelectBounds;\n },\n select: (id: string) => {\n controller.selectionModel.select(id);\n // Event will trigger requestUpdate via event listener\n },\n selectMultiple: (ids: string[]) => {\n controller.selectionModel.selectMultiple(ids);\n // Event will trigger requestUpdate via event listener\n },\n addToSelection: (id: string) => {\n controller.selectionModel.addToSelection(id);\n // Event will trigger requestUpdate via event listener\n },\n deselect: (id: string) => {\n controller.selectionModel.deselect(id);\n // Event will trigger requestUpdate via event listener\n },\n toggle: (id: string) => {\n controller.selectionModel.toggle(id);\n // Event will trigger requestUpdate via event listener\n },\n clear: () => {\n controller.selectionModel.clear();\n // Event will trigger requestUpdate via event listener\n },\n startBoxSelect: (x: number, y: number) => {\n controller.selectionModel.startBoxSelect(x, y);\n queueMicrotask(() => controller.host.requestUpdate());\n },\n updateBoxSelect: (x: number, y: number) => {\n controller.selectionModel.updateBoxSelect(x, y);\n queueMicrotask(() => controller.host.requestUpdate());\n },\n endBoxSelect: (\n hitTest: (bounds: DOMRect) => string[],\n addToSelection?: boolean,\n ) => {\n const fn = hitTest || controller.hitTestFn;\n if (fn) {\n controller.selectionModel.endBoxSelect(fn, addToSelection ?? false);\n }\n // Event will trigger requestUpdate via event listener\n },\n createGroup: (ids: string[]) => {\n const groupId = controller.selectionModel.createGroup(ids);\n return groupId;\n },\n ungroup: (groupId: string) => {\n controller.selectionModel.ungroup(groupId);\n },\n selectGroup: (groupId: string) => {\n controller.selectionModel.selectGroup(groupId);\n // Event will trigger requestUpdate via event listener\n },\n getGroupId: (elementId: string) => {\n return controller.selectionModel.getGroupId(elementId);\n },\n getGroupElements: (groupId: string) => {\n return controller.selectionModel.getGroupElements(groupId);\n },\n addEventListener: (\n type: \"selectionchange\",\n listener: (event: CustomEvent) => void,\n ) => {\n controller.selectionModel.addEventListener(type, listener as EventListener);\n },\n removeEventListener: (\n type: \"selectionchange\",\n listener: (event: CustomEvent) => void,\n ) => {\n controller.selectionModel.removeEventListener(type, listener as EventListener);\n },\n };\n }\n}\n"],"mappings":";;;;;;;AAQA,IAAa,sBAAb,MAA+D;CAM7D,YAAY,MAA8B;mBAHkB;AAI1D,OAAK,OAAO;AACZ,OAAK,iBAAiB,IAAI,gBAAgB;AAC1C,OAAK,mBAAmB,KAAK,oBAAoB;AACjD,OAAK,cAAc,KAAK;AAKxB,OAAK,eAAe,iBAAiB,yBAAyB;AAC5D,wBAAqB,KAAK,KAAK,eAAe,CAAC;IAC/C;;CAGJ,gBAAsB;CAItB,mBAAyB;;;;CAOzB,WAAW,IAAyC;AAClD,OAAK,YAAY;;;;;CAMnB,WAA2B;AACzB,SAAO,KAAK;;;;;CAMd,AAAQ,qBAAuC;EAC7C,MAAM,aAAa;AACnB,SAAO;GACL,IAAI,cAAc;AAChB,WAAO,WAAW,eAAe;;GAEnC,IAAI,gBAAgB;AAClB,WAAO,WAAW,eAAe;;GAEnC,IAAI,kBAAkB;AACpB,WAAO,WAAW,eAAe;;GAEnC,SAAS,OAAe;AACtB,eAAW,eAAe,OAAO,GAAG;;GAGtC,iBAAiB,QAAkB;AACjC,eAAW,eAAe,eAAe,IAAI;;GAG/C,iBAAiB,OAAe;AAC9B,eAAW,eAAe,eAAe,GAAG;;GAG9C,WAAW,OAAe;AACxB,eAAW,eAAe,SAAS,GAAG;;GAGxC,SAAS,OAAe;AACtB,eAAW,eAAe,OAAO,GAAG;;GAGtC,aAAa;AACX,eAAW,eAAe,OAAO;;GAGnC,iBAAiB,GAAW,MAAc;AACxC,eAAW,eAAe,eAAe,GAAG,EAAE;AAC9C,yBAAqB,WAAW,KAAK,eAAe,CAAC;;GAEvD,kBAAkB,GAAW,MAAc;AACzC,eAAW,eAAe,gBAAgB,GAAG,EAAE;AAC/C,yBAAqB,WAAW,KAAK,eAAe,CAAC;;GAEvD,eACE,SACA,mBACG;IACH,MAAM,KAAK,WAAW,WAAW;AACjC,QAAI,GACF,YAAW,eAAe,aAAa,IAAI,kBAAkB,MAAM;;GAIvE,cAAc,QAAkB;AAE9B,WADgB,WAAW,eAAe,YAAY,IAAI;;GAG5D,UAAU,YAAoB;AAC5B,eAAW,eAAe,QAAQ,QAAQ;;GAE5C,cAAc,YAAoB;AAChC,eAAW,eAAe,YAAY,QAAQ;;GAGhD,aAAa,cAAsB;AACjC,WAAO,WAAW,eAAe,WAAW,UAAU;;GAExD,mBAAmB,YAAoB;AACrC,WAAO,WAAW,eAAe,iBAAiB,QAAQ;;GAE5D,mBACE,MACA,aACG;AACH,eAAW,eAAe,iBAAiB,MAAM,SAA0B;;GAE7E,sBACE,MACA,aACG;AACH,eAAW,eAAe,oBAAoB,MAAM,SAA0B;;GAEjF"}
1
+ {"version":3,"file":"SelectionController.js","names":[],"sources":["../../../src/canvas/selection/SelectionController.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { SelectionModel } from \"./SelectionModel.js\";\nimport type { SelectionContext } from \"./selectionContext.js\";\n\n/**\n * Reactive controller that bridges SelectionModel and Lit element lifecycle.\n * Provides selection context to child elements.\n */\nexport class SelectionController implements ReactiveController {\n private host: ReactiveControllerHost;\n private selectionModel: SelectionModel;\n private hitTestFn: ((bounds: DOMRect) => string[]) | null = null;\n selectionContext: SelectionContext;\n\n constructor(host: ReactiveControllerHost) {\n this.host = host;\n this.selectionModel = new SelectionModel();\n this.selectionContext = this.createContextProxy();\n host.addController(this);\n\n // Listen to selection change events from the model\n // Use queueMicrotask to defer the update and avoid Lit warning about\n // scheduling updates after update completed (change-in-update)\n this.selectionModel.addEventListener(\"selectionchange\", () => {\n queueMicrotask(() => this.host.requestUpdate());\n });\n }\n\n hostConnected(): void {\n // Context is provided via @provide decorator\n }\n\n hostDisconnected(): void {\n // Cleanup if needed\n }\n\n /**\n * Set the hit test function for box selection.\n */\n setHitTest(fn: (bounds: DOMRect) => string[]): void {\n this.hitTestFn = fn;\n }\n\n /**\n * Get the underlying selection model.\n */\n getModel(): SelectionModel {\n return this.selectionModel;\n }\n\n /**\n * Create a proxy context that delegates to the selection model.\n */\n private createContextProxy(): SelectionContext {\n const controller = this;\n return new Proxy({\n select: (id: string) => {\n controller.selectionModel.select(id);\n // Event will trigger requestUpdate via event listener\n },\n selectMultiple: (ids: string[]) => {\n controller.selectionModel.selectMultiple(ids);\n // Event will trigger requestUpdate via event listener\n },\n addToSelection: (id: string) => {\n controller.selectionModel.addToSelection(id);\n // Event will trigger requestUpdate via event listener\n },\n deselect: (id: string) => {\n controller.selectionModel.deselect(id);\n // Event will trigger requestUpdate via event listener\n },\n toggle: (id: string) => {\n controller.selectionModel.toggle(id);\n // Event will trigger requestUpdate via event listener\n },\n clear: () => {\n controller.selectionModel.clear();\n // Event will trigger requestUpdate via event listener\n },\n startBoxSelect: (x: number, y: number) => {\n controller.selectionModel.startBoxSelect(x, y);\n queueMicrotask(() => controller.host.requestUpdate());\n },\n updateBoxSelect: (x: number, y: number) => {\n controller.selectionModel.updateBoxSelect(x, y);\n queueMicrotask(() => controller.host.requestUpdate());\n },\n endBoxSelect: (\n hitTest: (bounds: DOMRect) => string[],\n addToSelection?: boolean,\n ) => {\n const fn = hitTest || controller.hitTestFn;\n if (fn) {\n controller.selectionModel.endBoxSelect(fn, addToSelection ?? false);\n }\n // Event will trigger requestUpdate via event listener\n },\n createGroup: (ids: string[]) => {\n const groupId = controller.selectionModel.createGroup(ids);\n return groupId;\n },\n ungroup: (groupId: string) => {\n controller.selectionModel.ungroup(groupId);\n },\n selectGroup: (groupId: string) => {\n controller.selectionModel.selectGroup(groupId);\n // Event will trigger requestUpdate via event listener\n },\n getGroupId: (elementId: string) => {\n return controller.selectionModel.getGroupId(elementId);\n },\n getGroupElements: (groupId: string) => {\n return controller.selectionModel.getGroupElements(groupId);\n },\n addEventListener: (\n type: \"selectionchange\",\n listener: (event: CustomEvent) => void,\n ) => {\n controller.selectionModel.addEventListener(type, listener as EventListener);\n },\n removeEventListener: (\n type: \"selectionchange\",\n listener: (event: CustomEvent) => void,\n ) => {\n controller.selectionModel.removeEventListener(type, listener as EventListener);\n },\n } as Omit<SelectionContext, 'selectedIds' | 'selectionMode' | 'boxSelectBounds'>, {\n get(target, prop) {\n if (prop === 'selectedIds') {\n return controller.selectionModel.selectedIds;\n }\n if (prop === 'selectionMode') {\n return controller.selectionModel.selectionMode;\n }\n if (prop === 'boxSelectBounds') {\n return controller.selectionModel.boxSelectBounds;\n }\n return (target as any)[prop];\n },\n }) as SelectionContext;\n }\n}\n"],"mappings":";;;;;;;AAQA,IAAa,sBAAb,MAA+D;CAM7D,YAAY,MAA8B;mBAHkB;AAI1D,OAAK,OAAO;AACZ,OAAK,iBAAiB,IAAI,gBAAgB;AAC1C,OAAK,mBAAmB,KAAK,oBAAoB;AACjD,OAAK,cAAc,KAAK;AAKxB,OAAK,eAAe,iBAAiB,yBAAyB;AAC5D,wBAAqB,KAAK,KAAK,eAAe,CAAC;IAC/C;;CAGJ,gBAAsB;CAItB,mBAAyB;;;;CAOzB,WAAW,IAAyC;AAClD,OAAK,YAAY;;;;;CAMnB,WAA2B;AACzB,SAAO,KAAK;;;;;CAMd,AAAQ,qBAAuC;EAC7C,MAAM,aAAa;AACnB,SAAO,IAAI,MAAM;GACf,SAAS,OAAe;AACtB,eAAW,eAAe,OAAO,GAAG;;GAGtC,iBAAiB,QAAkB;AACjC,eAAW,eAAe,eAAe,IAAI;;GAG/C,iBAAiB,OAAe;AAC9B,eAAW,eAAe,eAAe,GAAG;;GAG9C,WAAW,OAAe;AACxB,eAAW,eAAe,SAAS,GAAG;;GAGxC,SAAS,OAAe;AACtB,eAAW,eAAe,OAAO,GAAG;;GAGtC,aAAa;AACX,eAAW,eAAe,OAAO;;GAGnC,iBAAiB,GAAW,MAAc;AACxC,eAAW,eAAe,eAAe,GAAG,EAAE;AAC9C,yBAAqB,WAAW,KAAK,eAAe,CAAC;;GAEvD,kBAAkB,GAAW,MAAc;AACzC,eAAW,eAAe,gBAAgB,GAAG,EAAE;AAC/C,yBAAqB,WAAW,KAAK,eAAe,CAAC;;GAEvD,eACE,SACA,mBACG;IACH,MAAM,KAAK,WAAW,WAAW;AACjC,QAAI,GACF,YAAW,eAAe,aAAa,IAAI,kBAAkB,MAAM;;GAIvE,cAAc,QAAkB;AAE9B,WADgB,WAAW,eAAe,YAAY,IAAI;;GAG5D,UAAU,YAAoB;AAC5B,eAAW,eAAe,QAAQ,QAAQ;;GAE5C,cAAc,YAAoB;AAChC,eAAW,eAAe,YAAY,QAAQ;;GAGhD,aAAa,cAAsB;AACjC,WAAO,WAAW,eAAe,WAAW,UAAU;;GAExD,mBAAmB,YAAoB;AACrC,WAAO,WAAW,eAAe,iBAAiB,QAAQ;;GAE5D,mBACE,MACA,aACG;AACH,eAAW,eAAe,iBAAiB,MAAM,SAA0B;;GAE7E,sBACE,MACA,aACG;AACH,eAAW,eAAe,oBAAoB,MAAM,SAA0B;;GAEjF,EAAiF,EAChF,IAAI,QAAQ,MAAM;AAChB,OAAI,SAAS,cACX,QAAO,WAAW,eAAe;AAEnC,OAAI,SAAS,gBACX,QAAO,WAAW,eAAe;AAEnC,OAAI,SAAS,kBACX,QAAO,WAAW,eAAe;AAEnC,UAAQ,OAAe;KAE1B,CAAC"}
@@ -1,13 +1,12 @@
1
- import { MediaEngine } from "../transcoding/types/index.js";
1
+ import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
2
2
  import { EFMedia } from "./EFMedia.js";
3
- import * as _lit_task8 from "@lit/task";
4
- import { Task } from "@lit/task";
5
3
  import * as lit_html1 from "lit-html";
6
4
  import * as lit_html_directives_ref_js1 from "lit-html/directives/ref.js";
7
5
 
8
6
  //#region src/elements/EFAudio.d.ts
9
7
  declare const EFAudio_base: typeof EFMedia;
10
- declare class EFAudio extends EFAudio_base {
8
+ declare class EFAudio extends EFAudio_base implements FrameRenderable {
9
+ #private;
11
10
  /**
12
11
  * EFAudio only requires audio tracks - skip video track validation
13
12
  * to avoid unnecessary network requests to transcoding service.
@@ -21,12 +20,31 @@ declare class EFAudio extends EFAudio_base {
21
20
  audioElementRef: lit_html_directives_ref_js1.Ref<HTMLAudioElement>;
22
21
  protected updated(changedProperties: Map<PropertyKey, unknown>): void;
23
22
  render(): lit_html1.TemplateResult<1>;
24
- frameTask: Task<readonly [_lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus], void>;
25
23
  /**
26
- * Legacy getter for fragment index task (maps to audioSegmentIdTask)
24
+ * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
25
+ * This is a compatibility wrapper that delegates to the new system.
26
+ */
27
+ frameTask: FrameTask;
28
+ /**
29
+ * Query readiness state for a given time.
30
+ * @implements FrameRenderable
31
+ */
32
+ getFrameState(_timeMs: number): FrameState;
33
+ /**
34
+ * Async preparation - waits for media engine to load.
35
+ * @implements FrameRenderable
36
+ */
37
+ prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void>;
38
+ /**
39
+ * Synchronous render - audio plays via HTMLAudioElement, no explicit render needed.
40
+ * @implements FrameRenderable
41
+ */
42
+ renderFrame(_timeMs: number): void;
43
+ /**
44
+ * Legacy getter for fragment index task (maps to frameTask)
27
45
  * Still used by EFCaptions
28
46
  */
29
- get fragmentIndexTask(): Task<readonly [MediaEngine | undefined, number], number | undefined>;
47
+ get fragmentIndexTask(): FrameTask;
30
48
  }
31
49
  declare global {
32
50
  interface HTMLElementTagNameMap {
@@ -1,8 +1,7 @@
1
- import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
2
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
3
2
  import { TWMixin } from "../gui/TWMixin2.js";
3
+ import { PRIORITY_AUDIO, createFrameTaskWrapper } from "../preview/FrameController.js";
4
4
  import { EFMedia } from "./EFMedia.js";
5
- import { Task } from "@lit/task";
6
5
  import { html } from "lit";
7
6
  import { customElement, property } from "lit/decorators.js";
8
7
  import { createRef, ref } from "lit/directives/ref.js";
@@ -13,62 +12,7 @@ let EFAudio = class EFAudio$1 extends TWMixin(EFMedia) {
13
12
  super(..._args);
14
13
  this.volume = 1;
15
14
  this.audioElementRef = createRef();
16
- this.frameTask = new Task(this, {
17
- autoRun: EF_INTERACTIVE,
18
- args: () => [
19
- this.audioBufferTask.status,
20
- this.audioSeekTask.status,
21
- this.audioSegmentFetchTask.status,
22
- this.mediaEngineTask.status
23
- ],
24
- onError: (error) => {
25
- this.frameTask.taskComplete.catch(() => {});
26
- 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;
27
- console.error("EFAudio frameTask error", error);
28
- },
29
- task: async ([_audioBufferStatus, _audioSeekStatus, _audioSegmentFetchStatus, _mediaEngineStatus], { signal }) => {
30
- try {
31
- await this.mediaEngineTask.taskComplete;
32
- } catch (error) {
33
- if (error instanceof DOMException && error.name === "AbortError") {
34
- signal?.throwIfAborted();
35
- return;
36
- }
37
- throw error;
38
- }
39
- signal?.throwIfAborted();
40
- try {
41
- await this.audioSegmentFetchTask.taskComplete;
42
- } catch (error) {
43
- if (error instanceof DOMException && error.name === "AbortError") {
44
- signal?.throwIfAborted();
45
- return;
46
- }
47
- throw error;
48
- }
49
- signal?.throwIfAborted();
50
- try {
51
- await this.audioSeekTask.taskComplete;
52
- } catch (error) {
53
- if (error instanceof DOMException && error.name === "AbortError") {
54
- signal?.throwIfAborted();
55
- return;
56
- }
57
- throw error;
58
- }
59
- signal?.throwIfAborted();
60
- try {
61
- await this.audioBufferTask.taskComplete;
62
- } catch (error) {
63
- if (error instanceof DOMException && error.name === "AbortError") {
64
- signal?.throwIfAborted();
65
- return;
66
- }
67
- throw error;
68
- }
69
- signal?.throwIfAborted();
70
- }
71
- });
15
+ this.frameTask = createFrameTaskWrapper(this, { getTimeMs: () => this.desiredSeekTimeMs });
72
16
  }
73
17
  /**
74
18
  * EFAudio only requires audio tracks - skip video track validation
@@ -77,6 +21,7 @@ let EFAudio = class EFAudio$1 extends TWMixin(EFMedia) {
77
21
  get requiredTracks() {
78
22
  return "audio";
79
23
  }
24
+ #mediaEngineLoaded = false;
80
25
  updated(changedProperties) {
81
26
  super.updated(changedProperties);
82
27
  if (this.audioElementRef.value) {
@@ -87,11 +32,36 @@ let EFAudio = class EFAudio$1 extends TWMixin(EFMedia) {
87
32
  return html`<audio ${ref(this.audioElementRef)}></audio>`;
88
33
  }
89
34
  /**
90
- * Legacy getter for fragment index task (maps to audioSegmentIdTask)
35
+ * Query readiness state for a given time.
36
+ * @implements FrameRenderable
37
+ */
38
+ getFrameState(_timeMs) {
39
+ const isReady = this.#mediaEngineLoaded && this.mediaEngineTask.status === 2;
40
+ return {
41
+ needsPreparation: !isReady,
42
+ isReady,
43
+ priority: PRIORITY_AUDIO
44
+ };
45
+ }
46
+ /**
47
+ * Async preparation - waits for media engine to load.
48
+ * @implements FrameRenderable
49
+ */
50
+ async prepareFrame(_timeMs, signal) {
51
+ this.#mediaEngineLoaded = !!await this.getMediaEngine(signal);
52
+ signal.throwIfAborted();
53
+ }
54
+ /**
55
+ * Synchronous render - audio plays via HTMLAudioElement, no explicit render needed.
56
+ * @implements FrameRenderable
57
+ */
58
+ renderFrame(_timeMs) {}
59
+ /**
60
+ * Legacy getter for fragment index task (maps to frameTask)
91
61
  * Still used by EFCaptions
92
62
  */
93
63
  get fragmentIndexTask() {
94
- return this.audioSegmentIdTask;
64
+ return this.frameTask;
95
65
  }
96
66
  };
97
67
  __decorate([property({
@@ -1 +1 @@
1
- {"version":3,"file":"EFAudio.js","names":["EFAudio"],"sources":["../../src/elements/EFAudio.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { EFMedia } from \"./EFMedia.js\";\n\n@customElement(\"ef-audio\")\nexport class EFAudio extends TWMixin(EFMedia) {\n /**\n * EFAudio only requires audio tracks - skip video track validation\n * to avoid unnecessary network requests to transcoding service.\n */\n override get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"audio\";\n }\n\n /**\n * Audio volume level (0.0 to 1.0)\n * @domAttribute \"volume\"\n */\n @property({ type: Number, attribute: \"volume\", reflect: true })\n volume = 1.0;\n\n audioElementRef = createRef<HTMLAudioElement>();\n\n protected updated(\n changedProperties: Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n \n // Sync volume property to HTMLAudioElement whenever it changes or element is first rendered\n if (this.audioElementRef.value) {\n if (changedProperties.has(\"volume\") || changedProperties.size === 0) {\n this.audioElementRef.value.volume = this.volume;\n }\n }\n }\n\n render() {\n return html`<audio ${ref(this.audioElementRef)}></audio>`;\n }\n\n frameTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.audioBufferTask.status,\n this.audioSeekTask.status,\n this.audioSegmentFetchTask.status,\n this.mediaEngineTask.status,\n ] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n this.frameTask.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\n const isAbortError = \n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ));\n \n if (isAbortError) {\n return;\n }\n \n console.error(\"EFAudio frameTask error\", error);\n },\n task: async ([_audioBufferStatus, _audioSeekStatus, _audioSegmentFetchStatus, _mediaEngineStatus], { signal }) => {\n // Wrap all taskComplete awaits in try/catch to handle AbortErrors\n try {\n await this.mediaEngineTask.taskComplete;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n \n try {\n await this.audioSegmentFetchTask.taskComplete;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n \n try {\n await this.audioSeekTask.taskComplete;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n \n try {\n await this.audioBufferTask.taskComplete;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n // REMOVED: this.rootTimegroup?.requestUpdate() was causing infinite update loops.\n // When EFAudio's frameTask ran, it would trigger root to update, which triggered\n // OwnCurrentTimeController.hostUpdated on all children, which triggered more\n // frameTask runs, creating an infinite cycle.\n // The root timegroup already updates when currentTime changes - no need to force it here.\n },\n });\n\n /**\n * Legacy getter for fragment index task (maps to audioSegmentIdTask)\n * Still used by EFCaptions\n */\n get fragmentIndexTask() {\n return this.audioSegmentIdTask;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-audio\": EFAudio;\n }\n}\n"],"mappings":";;;;;;;;;;AASO,oBAAMA,kBAAgB,QAAQ,QAAQ,CAAC;;;gBAcnC;yBAES,WAA6B;mBAmBnC,IAAI,KAAK,MAAM;GACzB,SAAS;GACT,YACE;IACE,KAAK,gBAAgB;IACrB,KAAK,cAAc;IACnB,KAAK,sBAAsB;IAC3B,KAAK,gBAAgB;IACtB;GACH,UAAU,UAAU;AAGlB,SAAK,UAAU,aAAa,YAAY,GAAG;AAW3C,QAPG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UAChB,MAAM,SAAS,gBACf,MAAM,SAAS,SAAS,oBAAoB,IAC5C,MAAM,SAAS,SAAS,6BAA6B,EAIvD;AAGF,YAAQ,MAAM,2BAA2B,MAAM;;GAEjD,MAAM,OAAO,CAAC,oBAAoB,kBAAkB,0BAA0B,qBAAqB,EAAE,aAAa;AAEhH,QAAI;AACF,WAAM,KAAK,gBAAgB;aACpB,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;AAER,YAAQ,gBAAgB;AAExB,QAAI;AACF,WAAM,KAAK,sBAAsB;aAC1B,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;AAER,YAAQ,gBAAgB;AAExB,QAAI;AACF,WAAM,KAAK,cAAc;aAClB,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;AAER,YAAQ,gBAAgB;AAExB,QAAI;AACF,WAAM,KAAK,gBAAgB;aACpB,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;AAER,YAAQ,gBAAgB;;GAO3B,CAAC;;;;;;CA9GF,IAAa,iBAA6C;AACxD,SAAO;;CAYT,AAAU,QACR,mBACM;AACN,QAAM,QAAQ,kBAAkB;AAGhC,MAAI,KAAK,gBAAgB,OACvB;OAAI,kBAAkB,IAAI,SAAS,IAAI,kBAAkB,SAAS,EAChE,MAAK,gBAAgB,MAAM,SAAS,KAAK;;;CAK/C,SAAS;AACP,SAAO,IAAI,UAAU,IAAI,KAAK,gBAAgB,CAAC;;;;;;CAyFjD,IAAI,oBAAoB;AACtB,SAAO,KAAK;;;YA7Gb,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAU,SAAS;CAAM,CAAC;sBAdhE,cAAc,WAAW"}
1
+ {"version":3,"file":"EFAudio.js","names":["EFAudio","#mediaEngineLoaded"],"sources":["../../src/elements/EFAudio.ts"],"sourcesContent":["import { html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n createFrameTaskWrapper,\n PRIORITY_AUDIO,\n} from \"../preview/FrameController.js\";\nimport { EFMedia } from \"./EFMedia.js\";\n\n@customElement(\"ef-audio\")\nexport class EFAudio extends TWMixin(EFMedia) implements FrameRenderable {\n /**\n * EFAudio only requires audio tracks - skip video track validation\n * to avoid unnecessary network requests to transcoding service.\n */\n override get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"audio\";\n }\n\n /**\n * Audio volume level (0.0 to 1.0)\n * @domAttribute \"volume\"\n */\n @property({ type: Number, attribute: \"volume\", reflect: true })\n volume = 1.0;\n\n audioElementRef = createRef<HTMLAudioElement>();\n\n #mediaEngineLoaded = false;\n\n protected updated(\n changedProperties: Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n \n // Sync volume property to HTMLAudioElement whenever it changes or element is first rendered\n if (this.audioElementRef.value) {\n if (changedProperties.has(\"volume\") || changedProperties.size === 0) {\n this.audioElementRef.value.volume = this.volume;\n }\n }\n }\n\n render() {\n return html`<audio ${ref(this.audioElementRef)}></audio>`;\n }\n\n /**\n * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.\n * This is a compatibility wrapper that delegates to the new system.\n */\n frameTask = createFrameTaskWrapper(this, {\n getTimeMs: () => this.desiredSeekTimeMs,\n });\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - no Lit Tasks\n // ============================================================================\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n */\n getFrameState(_timeMs: number): FrameState {\n // Check if media engine is loaded\n const isReady = this.#mediaEngineLoaded && this.mediaEngineTask.status === 2; // COMPLETE\n\n return {\n needsPreparation: !isReady,\n isReady,\n priority: PRIORITY_AUDIO,\n };\n }\n\n /**\n * Async preparation - waits for media engine to load.\n * @implements FrameRenderable\n */\n async prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void> {\n // Just ensure media engine is loaded\n const mediaEngine = await this.getMediaEngine(signal);\n this.#mediaEngineLoaded = !!mediaEngine;\n signal.throwIfAborted();\n }\n\n /**\n * Synchronous render - audio plays via HTMLAudioElement, no explicit render needed.\n * @implements FrameRenderable\n */\n renderFrame(_timeMs: number): void {\n // Audio playback is handled by the browser's HTMLAudioElement\n // No explicit rendering action needed\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n /**\n * Legacy getter for fragment index task (maps to frameTask)\n * Still used by EFCaptions\n */\n get fragmentIndexTask() {\n return this.frameTask;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-audio\": EFAudio;\n }\n}\n"],"mappings":";;;;;;;;;AAaO,oBAAMA,kBAAgB,QAAQ,QAAQ,CAA4B;;;gBAc9D;yBAES,WAA6B;mBAyBnC,uBAAuB,MAAM,EACvC,iBAAiB,KAAK,mBACvB,CAAC;;;;;;CAtCF,IAAa,iBAA6C;AACxD,SAAO;;CAYT,qBAAqB;CAErB,AAAU,QACR,mBACM;AACN,QAAM,QAAQ,kBAAkB;AAGhC,MAAI,KAAK,gBAAgB,OACvB;OAAI,kBAAkB,IAAI,SAAS,IAAI,kBAAkB,SAAS,EAChE,MAAK,gBAAgB,MAAM,SAAS,KAAK;;;CAK/C,SAAS;AACP,SAAO,IAAI,UAAU,IAAI,KAAK,gBAAgB,CAAC;;;;;;CAoBjD,cAAc,SAA6B;EAEzC,MAAM,UAAU,MAAKC,qBAAsB,KAAK,gBAAgB,WAAW;AAE3E,SAAO;GACL,kBAAkB,CAAC;GACnB;GACA,UAAU;GACX;;;;;;CAOH,MAAM,aAAa,SAAiB,QAAoC;AAGtE,QAAKA,oBAAqB,CAAC,CADP,MAAM,KAAK,eAAe,OAAO;AAErD,SAAO,gBAAgB;;;;;;CAOzB,YAAY,SAAuB;;;;;CAanC,IAAI,oBAAoB;AACtB,SAAO,KAAK;;;YAjFb,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAU,SAAS;CAAM,CAAC;sBAdhE,cAAc,WAAW"}