@editframe/elements 0.30.1-beta.0 → 0.31.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.d.ts +5 -0
- package/dist/EF_FRAMEGEN.js +20 -4
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_INTERACTIVE.js.map +1 -1
- package/dist/_virtual/rolldown_runtime.js +27 -0
- package/dist/canvas/EFCanvas.d.ts +311 -0
- package/dist/canvas/EFCanvas.js +1089 -0
- package/dist/canvas/EFCanvas.js.map +1 -0
- package/dist/canvas/EFCanvasItem.d.ts +55 -0
- package/dist/canvas/EFCanvasItem.js +72 -0
- package/dist/canvas/EFCanvasItem.js.map +1 -0
- package/dist/canvas/api/CanvasAPI.d.ts +115 -0
- package/dist/canvas/api/CanvasAPI.js +182 -0
- package/dist/canvas/api/CanvasAPI.js.map +1 -0
- package/dist/canvas/api/types.d.ts +42 -0
- package/dist/canvas/coordinateTransform.js +90 -0
- package/dist/canvas/coordinateTransform.js.map +1 -0
- package/dist/canvas/getElementBounds.js +40 -0
- package/dist/canvas/getElementBounds.js.map +1 -0
- package/dist/canvas/overlays/SelectionOverlay.js +265 -0
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
- package/dist/canvas/overlays/overlayState.js +153 -0
- package/dist/canvas/overlays/overlayState.js.map +1 -0
- package/dist/canvas/selection/SelectionController.js +105 -0
- package/dist/canvas/selection/SelectionController.js.map +1 -0
- package/dist/canvas/selection/SelectionModel.d.ts +98 -0
- package/dist/canvas/selection/SelectionModel.js +229 -0
- package/dist/canvas/selection/SelectionModel.js.map +1 -0
- package/dist/canvas/selection/selectionContext.d.ts +31 -0
- package/dist/canvas/selection/selectionContext.js +12 -0
- package/dist/canvas/selection/selectionContext.js.map +1 -0
- package/dist/elements/ContainerInfo.d.ts +29 -0
- package/dist/elements/ContainerInfo.js +30 -0
- package/dist/elements/ContainerInfo.js.map +1 -0
- package/dist/elements/EFAudio.d.ts +13 -3
- package/dist/elements/EFAudio.js +64 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +18 -16
- package/dist/elements/EFCaptions.js +110 -19
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +16 -6
- package/dist/elements/EFImage.js +79 -9
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +18 -7
- package/dist/elements/EFMedia.js +23 -3
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +96 -0
- package/dist/elements/EFPanZoom.js +290 -0
- package/dist/elements/EFPanZoom.js.map +1 -0
- package/dist/elements/EFSourceMixin.js +7 -6
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +6 -6
- package/dist/elements/EFSurface.js +7 -2
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +192 -71
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +102 -13
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +32 -6
- package/dist/elements/EFTextSegment.js +53 -15
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +118 -56
- package/dist/elements/EFThumbnailStrip.js +522 -358
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +223 -27
- package/dist/elements/EFTimegroup.js +851 -148
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +42 -5
- package/dist/elements/EFVideo.js +165 -11
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +6 -6
- package/dist/elements/EFWaveform.js +2 -1
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.d.ts +35 -0
- package/dist/elements/ElementPositionInfo.js +49 -0
- package/dist/elements/ElementPositionInfo.js.map +1 -0
- package/dist/elements/FetchMixin.js +16 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SessionThumbnailCache.js +152 -0
- package/dist/elements/SessionThumbnailCache.js.map +1 -0
- package/dist/elements/TargetController.js +3 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js +9 -3
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/findRootTemporal.js +30 -0
- package/dist/elements/findRootTemporal.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +18 -5
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/updateAnimations.js +492 -109
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +4 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +74 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
- package/dist/gui/EFActiveRootTemporal.js +94 -0
- package/dist/gui/EFActiveRootTemporal.js.map +1 -0
- package/dist/gui/EFConfiguration.d.ts +11 -5
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +109 -13
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +11 -214
- package/dist/gui/EFFilmstrip.js +53 -1152
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +3 -3
- package/dist/gui/EFFitScale.js +39 -12
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +48 -0
- package/dist/gui/EFOverlayItem.js +97 -0
- package/dist/gui/EFOverlayItem.js.map +1 -0
- package/dist/gui/EFOverlayLayer.d.ts +70 -0
- package/dist/gui/EFOverlayLayer.js +104 -0
- package/dist/gui/EFOverlayLayer.js.map +1 -0
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +12 -16
- package/dist/gui/EFResizableBox.js +109 -451
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +30 -5
- package/dist/gui/EFScrubber.js +224 -31
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +4 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +71 -0
- package/dist/gui/EFTimelineRuler.js +320 -0
- package/dist/gui/EFTimelineRuler.js.map +1 -0
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +91 -0
- package/dist/gui/EFTransformHandles.js +393 -0
- package/dist/gui/EFTransformHandles.js.map +1 -0
- package/dist/gui/EFWorkbench.d.ts +182 -4
- package/dist/gui/EFWorkbench.js +2067 -22
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.d.ts +31 -0
- package/dist/gui/FitScaleHelpers.js +41 -0
- package/dist/gui/FitScaleHelpers.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +2 -1
- package/dist/gui/PlaybackController.js +46 -15
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
- package/dist/gui/hierarchy/EFHierarchy.js +338 -0
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
- package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
- package/dist/gui/hierarchy/hierarchyContext.js +8 -0
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
- package/dist/gui/icons.js +34 -0
- package/dist/gui/icons.js.map +1 -0
- package/dist/gui/panZoomTransformContext.js +12 -0
- package/dist/gui/panZoomTransformContext.js.map +1 -0
- package/dist/gui/previewSettingsContext.js +12 -0
- package/dist/gui/previewSettingsContext.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +270 -0
- package/dist/gui/timeline/EFTimeline.js +1369 -0
- package/dist/gui/timeline/EFTimeline.js.map +1 -0
- package/dist/gui/timeline/EFTimelineRow.js +374 -0
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
- package/dist/gui/timeline/TrimHandles.d.ts +36 -0
- package/dist/gui/timeline/TrimHandles.js +204 -0
- package/dist/gui/timeline/TrimHandles.js.map +1 -0
- package/dist/gui/timeline/flattenHierarchy.js +31 -0
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
- package/dist/gui/timeline/timelineStateContext.js +42 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TextTrack.js +250 -0
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TrackItem.js +269 -0
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
- package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
- package/dist/gui/transformCalculations.js +217 -0
- package/dist/gui/transformCalculations.js.map +1 -0
- package/dist/gui/transformUtils.d.ts +37 -0
- package/dist/gui/transformUtils.js +77 -0
- package/dist/gui/transformUtils.js.map +1 -0
- package/dist/gui/tree/EFTree.d.ts +59 -0
- package/dist/gui/tree/EFTree.js +174 -0
- package/dist/gui/tree/EFTree.js.map +1 -0
- package/dist/gui/tree/EFTreeItem.d.ts +38 -0
- package/dist/gui/tree/EFTreeItem.js +146 -0
- package/dist/gui/tree/EFTreeItem.js.map +1 -0
- package/dist/gui/tree/treeContext.d.ts +60 -0
- package/dist/gui/tree/treeContext.js +23 -0
- package/dist/gui/tree/treeContext.js.map +1 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +30 -6
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react.development.js +1521 -0
- package/dist/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/react/index.js +13 -0
- package/dist/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +13 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/preview/AdaptiveResolutionTracker.js +228 -0
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
- package/dist/preview/RenderProfiler.js +135 -0
- package/dist/preview/RenderProfiler.js.map +1 -0
- package/dist/preview/previewSettings.js +131 -0
- package/dist/preview/previewSettings.js.map +1 -0
- package/dist/preview/previewTypes.js +64 -0
- package/dist/preview/previewTypes.js.map +1 -0
- package/dist/preview/renderTimegroupPreview.js +656 -0
- package/dist/preview/renderTimegroupPreview.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
- package/dist/preview/renderTimegroupToCanvas.js +840 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
- package/dist/preview/renderTimegroupToVideo.js +274 -0
- package/dist/preview/renderTimegroupToVideo.js.map +1 -0
- package/dist/preview/renderers.js +16 -0
- package/dist/preview/renderers.js.map +1 -0
- package/dist/preview/statsTrackingStrategy.js +201 -0
- package/dist/preview/statsTrackingStrategy.js.map +1 -0
- package/dist/preview/thumbnailCacheSettings.js +52 -0
- package/dist/preview/thumbnailCacheSettings.js.map +1 -0
- package/dist/preview/workers/WorkerPool.js +178 -0
- package/dist/preview/workers/WorkerPool.js.map +1 -0
- package/dist/sandbox/PlaybackControls.js +10 -0
- package/dist/sandbox/PlaybackControls.js.map +1 -0
- package/dist/sandbox/ScenarioRunner.js +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/style.css +66 -69
- package/dist/transcoding/types/index.d.ts +2 -1
- package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
- package/dist/transcoding/utils/UrlGenerator.js +12 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -375
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +14 -0
- package/dist/utils/frameTime.js.map +1 -0
- package/package.json +3 -3
- package/test/profilingPlugin.ts +223 -0
- package/test/recordReplayProxyPlugin.js +22 -27
- package/test/thumbnail-performance-test.html +116 -0
- package/test/visualRegressionUtils.ts +286 -0
- package/types.json +1 -1
- package/dist/elements/TimegroupController.d.ts +0 -18
- package/dist/msToTimeCode.js +0 -17
- package/dist/msToTimeCode.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTimegroup.js","names":["sequenceDurationCache: WeakMap<EFTimegroup, number>","EFTimegroup","#executeCustomFrameTasks","#pendingSeekTime","#currentTime","#seekInProgress","#processingPendingSeek","#customFrameTasks","#handleSlotChange","#previousDurationMs","#resizeObserver","#waitForMediaDurations","loaderTasks: Promise<any>[]"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { Task, TaskStatus } from \"@lit/task\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport {\n evaluateTemporalStateForAnimation,\n updateAnimations,\n} from \"./updateAnimations.ts\";\n\ndeclare global {\n var EF_DEV_WORKBENCH: boolean | undefined;\n}\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n// Cache for sequence mode duration calculations to avoid O(n) recalculation\nlet sequenceDurationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushSequenceDurationCache = () => {\n sequenceDurationCache = new WeakMap();\n};\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {\n static get observedAttributes(): string[] {\n // biome-ignore lint/complexity/noThisInStatic: It's okay to use this here\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n @provide({ context: efContext })\n efContext = this;\n\n mode: \"fit\" | \"fixed\" | \"sequence\" | \"contain\" = \"contain\";\n overlapMs = 0;\n\n @property({ type: Number })\n fps = 30;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n #currentTime: number | undefined = undefined;\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n /**\n * Quantize a time value to the nearest frame boundary based on effectiveFps.\n * @param timeSeconds - Time in seconds\n * @returns Time quantized to frame boundaries in seconds\n */\n private quantizeToFrameTime(timeSeconds: number): number {\n const fps = this.effectiveFps;\n if (!fps || fps <= 0) return timeSeconds;\n const frameDurationS = 1 / fps;\n return Math.round(timeSeconds / frameDurationS) * frameDurationS;\n }\n\n private async runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Quantize time to frame boundaries based on fps\n // Do this BEFORE delegating to playbackController to ensure consistency\n time = this.quantizeToFrameTime(time);\n\n if (this.playbackController) {\n this.playbackController.currentTime = time;\n return;\n }\n\n time = Math.max(0, Math.min(this.durationMs / 1000, time));\n if (!this.isRootTimegroup) {\n return;\n }\n if (Number.isNaN(time)) {\n return;\n }\n if (time === this.#currentTime && !this.#processingPendingSeek) {\n return;\n }\n if (this.#pendingSeekTime === time) {\n return;\n }\n\n if (this.#seekInProgress) {\n this.#pendingSeekTime = time;\n this.#currentTime = time;\n return;\n }\n\n this.#currentTime = time;\n this.#seekInProgress = true;\n\n this.seekTask.run().finally(() => {\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== time\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n */\n async seek(timeMs: number): Promise<void> {\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const temporalElements = deepGetElementsWithFrameTasks(this);\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateTemporalStateForAnimation(element);\n return animationState.isVisible;\n });\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n return Number.parseFloat(storedValue);\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n if (!this.playbackController) {\n this.waitForMediaDurations().then(async () => {\n let didLoadFromStorage = false;\n if (this.id) {\n const maybeLoadedTime = this.loadTimeFromLocalStorage();\n if (maybeLoadedTime !== undefined) {\n this.currentTime = maybeLoadedTime;\n didLoadFromStorage = true;\n }\n }\n if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {\n this.seekTask.run();\n } else if (didLoadFromStorage) {\n await this.seekTask.run();\n }\n });\n }\n\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n sequenceDurationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n }\n\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return (\n this.mode === \"contain\" ||\n this.mode === \"sequence\" ||\n (this.mode === \"fixed\" && this.hasExplicitDuration)\n );\n }\n\n get durationMs(): number {\n switch (this.mode) {\n case \"fit\": {\n if (!this.parentTimegroup) {\n return 0;\n }\n return this.parentTimegroup.durationMs;\n }\n case \"fixed\":\n return super.durationMs;\n case \"sequence\": {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = sequenceDurationCache.get(this);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n this.childTemporals.forEach((child, index) => {\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return;\n }\n if (index > 0) {\n duration -= this.overlapMs;\n }\n duration += child.durationMs;\n });\n\n // Cache the calculated duration\n sequenceDurationCache.set(this, duration);\n return duration;\n }\n case \"contain\": {\n let maxDuration = 0;\n for (const child of this.childTemporals) {\n // fit timegroups look \"up\" to their parent timegroup for their duration\n // so we need to skip them to avoid an infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n continue;\n }\n if (!child.hasOwnDuration) {\n continue;\n }\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n return maxDuration;\n }\n default:\n throw new Error(`Invalid time mode: ${this.mode}`);\n }\n }\n\n async getPendingFrameTasks(signal?: AbortSignal) {\n await this.waitForNestedUpdates(signal);\n signal?.throwIfAborted();\n const temporals = deepGetElementsWithFrameTasks(this);\n\n // Filter to only include temporally visible elements for frame processing\n // (but keep all elements for duration calculations)\n // Use the target timeline time if we're in the middle of seeking\n const timelineTimeMs =\n (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1000;\n const activeTemporals = temporals.filter((temporal) => {\n // Skip timeline filtering if temporal doesn't have timeline position info\n if (!(\"startTimeMs\" in temporal) || !(\"endTimeMs\" in temporal)) {\n return true; // Keep non-temporal elements\n }\n\n // Only process frame tasks for elements that overlap the current timeline\n // Use same epsilon logic as seek task for consistency\n const epsilon = 0.001; // 1µs offset to break ties at boundaries\n const startTimeMs = (temporal as any).startTimeMs as number;\n const endTimeMs = (temporal as any).endTimeMs as number;\n const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;\n // Root timegroups should remain visible at exact end time, but other elements use exclusive end for clean transitions\n const isRootTimegroup =\n temporal.tagName.toLowerCase() === \"ef-timegroup\" &&\n !(temporal as any).parentTimegroup;\n const useInclusiveEnd = isRootTimegroup;\n const elementEndsAfterStart = useInclusiveEnd\n ? endTimeMs >= timelineTimeMs\n : endTimeMs > timelineTimeMs;\n return elementStartsBeforeEnd && elementEndsAfterStart;\n });\n\n const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);\n frameTasks.forEach((task) => {\n task.run();\n });\n\n return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);\n }\n\n async waitForNestedUpdates(signal?: AbortSignal) {\n const limit = 10;\n let steps = 0;\n let isComplete = true;\n while (true) {\n steps++;\n if (steps > limit) {\n throw new Error(\"Reached update depth limit.\");\n }\n isComplete = await this.updateComplete;\n signal?.throwIfAborted();\n if (isComplete) {\n break;\n }\n }\n }\n\n async waitForFrameTasks() {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Filter to only include temporally visible elements for frame processing\n // Use animation-friendly visibility to prevent animation jumps at exact boundaries\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateTemporalStateForAnimation(element);\n return animationState.isVisible;\n });\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n await Promise.all(\n visibleElements.map((element) => element.frameTask.run()),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n async waitForMediaDurations() {\n if (!this.mediaDurationsPromise) {\n this.mediaDurationsPromise = this.#waitForMediaDurations();\n }\n return this.mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations() {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // We must await updateComplete to ensure all media elements inside this are connected\n // and will match deepGetMediaElements\n await this.updateComplete;\n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n await Promise.all(\n mediaElements.map((m) =>\n m.mediaEngineTask.value\n ? Promise.resolve()\n : m.mediaEngineTask.run(),\n ),\n );\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n this.requestUpdate(\"currentTime\");\n // Finally, we must await updateComplete to ensure all temporal elements have their\n // currentTime updated and all animations have run.\n\n await this.updateComplete;\n },\n );\n }\n\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - It's in interactive mode (EF_INTERACTIVE) with the dev workbench flag set\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n */\n shouldWrapWithWorkbench() {\n const isRendering = EF_RENDERING?.() === true;\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n if (isRendering) {\n return (\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n // During interactive mode, respect the dev workbench flag\n if (!globalThis.EF_DEV_WORKBENCH) {\n return false;\n }\n\n return (\n EF_INTERACTIVE &&\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n this.parentElement?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-this\");\n }\n this.setAttribute(\"slot\", \"canvas\");\n workbench.append(this as unknown as Element);\n\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n get efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async loadMd5Sums() {\n const efElements = this.efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n if (md5SumLoader instanceof Task) {\n md5SumLoader.run();\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n frameTask = new Task(this, {\n // autoRun: EF_INTERACTIVE,\n autoRun: false,\n args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,\n task: async ([ownCurrentTimeMs, currentTimeMs]) => {\n if (this.isRootTimegroup) {\n await withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs,\n currentTimeMs,\n },\n undefined,\n async () => {\n await this.waitForFrameTasks();\n await this.#executeCustomFrameTasks();\n updateAnimations(this);\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n await this.#executeCustomFrameTasks();\n }\n },\n });\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n seekTask = new Task(this, {\n autoRun: false,\n args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,\n onComplete: () => {},\n task: async ([targetTime]) => {\n if (this.playbackController) {\n await this.playbackController.seekTask.taskComplete;\n return this.currentTime;\n }\n\n if (!this.isRootTimegroup) {\n return;\n }\n return withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n await this.waitForMediaDurations();\n const newTime = Math.max(\n 0,\n Math.min(targetTime ?? 0, this.durationMs / 1000),\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n // Apply the clamped time back to currentTime\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n await this.runThrottledFrameTask();\n this.saveTimeToLocalStorage(this.#currentTime);\n this.#seekInProgress = false;\n return newTime;\n },\n );\n },\n });\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFTimegroup.js","names":["durationCache: WeakMap<EFTimegroup, number>","ancestor: Node | null","EFTimegroup","#timegroupFrameTaskLastReset","#timegroupFrameTaskCount","#executeCustomFrameTasks","#pendingSeekTime","#currentTime","#runThrottledFrameTask","#restoringFromLocalStorage","#seekInProgress","#userTimeMs","#processingPendingSeek","#evaluateVisibleElementsForFrame","#getAllLitElementDescendants","result: LitElement[]","#onFrameCallback","#onFrameCleanup","#customFrameTasks","#handleSlotChange","#setupPlaybackListener","#playbackListener","#previousDurationMs","#resizeObserver","#removePlaybackListener","canvases: HTMLCanvasElement[]","result: unknown","#waitForCaptionsData","waitPromises: Promise<unknown>[]","#copyCaptionsData","#copyTextContent","#copyTextSegmentData","#runInitializer","container","#mediaDurationsPromise","#waitForMediaDurations","rafId1: number","rafId2: number","timeoutId: ReturnType<typeof setTimeout>","#contextProvider","#efElements","#testPlayAudio","#loadMd5Sums","loaderTasks: Promise<any>[]"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { Task, TaskStatus } from \"@lit/task\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { quantizeToFrameTimeS } from \"../utils/frameTime.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n type TemporalMixinInterface,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport {\n evaluateAnimationVisibilityState,\n updateAnimations,\n} from \"./updateAnimations.js\";\nimport {\n type ContainerInfo,\n getContainerInfoFromElement,\n} from \"./ContainerInfo.js\";\nimport {\n type ElementPositionInfo,\n getPositionInfoFromElement,\n} from \"./ElementPositionInfo.js\";\nimport {\n captureTimegroupAtTime,\n captureFromClone,\n type CaptureOptions,\n type CaptureBatchOptions,\n} from \"../preview/renderTimegroupToCanvas.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n} from \"../preview/renderTimegroupToVideo.js\";\nimport type { PlaybackControllerUpdateEvent } from \"../gui/PlaybackController.js\";\n\n// Side-effect imports for workbench wrapping\nimport \"../canvas/EFCanvas.js\";\nimport \"../gui/hierarchy/EFHierarchy.js\";\nimport \"../gui/EFFilmstrip.js\";\nimport \"../gui/EFWorkbench.js\";\nimport \"../gui/EFFitScale.js\";\nimport \"./EFPanZoom.js\";\n\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n/**\n * Result of createRenderClone() - contains the clone, its container, and cleanup function.\n */\nexport interface RenderCloneResult {\n /** The cloned timegroup, fully functional with its own time state */\n clone: EFTimegroup;\n /** The offscreen container holding the clone */\n container: HTMLElement;\n /** Call this to remove the clone from DOM and clean up */\n cleanup: () => void;\n}\n\n/**\n * Initializer function type for setting up JavaScript behavior on timegroup instances.\n * This function is called on both the prime timeline and each render clone.\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error) or <10ms (warning)\n * - Should only register callbacks and set up behavior, not do expensive work\n */\nexport type TimegroupInitializer = (timegroup: EFTimegroup) => void;\n\n// Constants for initializer time budget enforcement\nconst INITIALIZER_ERROR_THRESHOLD_MS = 100;\nconst INITIALIZER_WARN_THRESHOLD_MS = 10;\n\n// ============================================================================\n// Purpose 1: Composition Rules - How Duration is Determined\n// ============================================================================\n//\n// A timegroup's duration is determined by its mode:\n// - \"fixed\": Uses explicit duration attribute (base case)\n// - \"sequence\": Sum of child durations minus overlaps\n// - \"contain\": Maximum of child durations\n// - \"fit\": Inherits duration from parent timegroup\n//\n// Core invariant: Every timegroup has exactly one duration value at any moment,\n// computed from either explicit specification (fixed mode) or child relationships\n// (sequence/contain/fit modes).\n//\n// ============================================================================\n\n/**\n * The four timegroup modes define how duration is calculated:\n * - \"fit\": Inherits duration from parent timegroup\n * - \"fixed\": Uses explicit duration attribute\n * - \"sequence\": Sum of child durations minus overlaps\n * - \"contain\": Maximum of child durations\n */\nexport type TimeMode = \"fit\" | \"fixed\" | \"sequence\" | \"contain\";\n\n// Cache for duration calculations to avoid O(n) recalculation on every access\n// Used by all modes (sequence, contain) to avoid repeated iteration through children\nlet durationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushDurationCache = () => {\n durationCache = new WeakMap();\n};\n\n// Keep alias for backwards compatibility\nexport const flushSequenceDurationCache = flushDurationCache;\n\n// Track timegroups currently calculating duration to prevent infinite loops\nconst durationCalculationInProgress = new WeakSet<EFTimegroup>();\n\n// Export function to check if a timegroup is currently calculating duration\n// This is used by EFTemporal to prevent calling parent.durationMs during calculation\nexport const isTimegroupCalculatingDuration = (\n timegroup: EFTimegroup | undefined,\n): boolean => {\n return (\n timegroup !== undefined && durationCalculationInProgress.has(timegroup)\n );\n};\n\n// Register this function with EFTemporal to break circular dependency\n// EFTemporal needs this function but can't import it directly due to circular dependency\nimport { registerIsTimegroupCalculatingDuration } from \"./EFTemporal.js\";\nregisterIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);\n\n/**\n * Determines if a timegroup has its own duration based on its mode.\n * This is the semantic rule: which modes produce independent durations.\n */\nfunction hasOwnDurationForMode(\n mode: TimeMode,\n hasExplicitDuration: boolean,\n): boolean {\n return (\n mode === \"contain\" ||\n mode === \"sequence\" ||\n (mode === \"fixed\" && hasExplicitDuration)\n );\n}\n\n/**\n * Determines if a child temporal element should participate in parent duration calculation.\n *\n * Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's\n * duration calculation (to avoid circular dependencies). Children without own duration\n * also don't contribute.\n */\nfunction shouldParticipateInDurationCalculation(\n child: TemporalMixinInterface & HTMLElement,\n): boolean {\n // Fit timegroups look \"up\" to their parent for duration, so skip to avoid infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return false;\n }\n // Only children with their own duration contribute\n if (!child.hasOwnDuration) {\n return false;\n }\n return true;\n}\n\n/**\n * Evaluates duration for \"fit\" mode: inherits from parent.\n * Semantic rule: fit mode always matches parent duration, or 0 if no parent.\n */\nfunction evaluateFitDuration(parentTimegroup: EFTimegroup | undefined): number {\n if (!parentTimegroup) {\n return 0;\n }\n return parentTimegroup.durationMs;\n}\n\n/**\n * Evaluates duration for \"sequence\" mode: sum of children minus overlaps.\n * Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.\n * Fit-mode children are excluded to avoid circular dependencies.\n */\nfunction evaluateSequenceDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n overlapMs: number,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n let participatingIndex = 0;\n childTemporals.forEach((child) => {\n if (!shouldParticipateInDurationCalculation(child)) {\n return;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n return;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n return;\n }\n }\n\n // Subtract overlap for all items after the first\n if (participatingIndex > 0) {\n duration -= overlapMs;\n }\n duration += child.durationMs;\n participatingIndex++;\n });\n\n // Ensure non-negative duration (invariant)\n duration = Math.max(0, duration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration for \"contain\" mode: maximum of children.\n * Semantic rule: contain mode takes the maximum child duration.\n * Fit-mode children and children without own duration are excluded.\n */\nfunction evaluateContainDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let maxDuration = 0;\n for (const child of childTemporals) {\n if (!shouldParticipateInDurationCalculation(child)) {\n continue;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n // This check applies to all timegroup children, not just contain mode, because\n // a sequence-mode child could contain a contain-mode grandchild that\n // eventually references back to the parent through the parent chain\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n continue;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n continue;\n }\n }\n\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n // Ensure non-negative duration (invariant)\n const duration = Math.max(0, maxDuration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration based on timegroup mode.\n * This is the semantic evaluation function - it determines what duration should be.\n *\n * Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs\n * which requires the class context. The other modes are extracted for clarity.\n */\nfunction evaluateDurationForMode(\n timegroup: EFTimegroup,\n mode: TimeMode,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n switch (mode) {\n case \"fit\":\n return evaluateFitDuration(timegroup.parentTimegroup);\n case \"sequence\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateSequenceDuration(\n timegroup,\n childTemporals,\n timegroup.overlapMs,\n );\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n case \"contain\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateContainDuration(timegroup, childTemporals);\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n default:\n throw new Error(`Invalid time mode: ${mode}`);\n }\n}\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n// ============================================================================\n// Purpose 2: Time Propagation - How currentTime Flows Root to Children\n// ============================================================================\n//\n// Time propagation determines how the root timegroup's currentTime flows to child\n// temporal elements, computing each child's ownCurrentTime based on:\n// - The root's currentTime (global coordinate)\n// - The child's startTimeMs (determined by parent's composition mode)\n// - The parent's mode (sequence/contain/fit/fixed)\n//\n// Propagation rules by mode:\n// - Sequence: Each child's ownCurrentTime progresses within its time-shifted window\n// - Contain: All children share the same ownCurrentTime as parent\n// - Fit: Child ownCurrentTime = parent ownCurrentTime (identity mapping)\n//\n// Core invariant: Only root timegroup's currentTime should be written.\n// Child times are computed from parent state via ownCurrentTimeMs.\n//\n// Note: Time propagation logic is primarily implemented in EFTemporal.ts\n// (ownCurrentTimeMs getter and startTimeMs calculation). The timegroup's\n// currentTime setter triggers propagation by updating root time.\n//\n// ============================================================================\n\n// ============================================================================\n// Purpose 3: Seeking - Moving to a Specific Time\n// ============================================================================\n//\n// Seeking moves the timeline to a specific time position. This involves:\n// 1. Quantizing the requested time to frame boundaries (based on fps)\n// 2. Clamping to valid range [0, duration]\n// 3. Updating root timegroup's currentTime (which triggers time propagation)\n// 4. Waiting for all media and frame tasks to complete\n//\n// Core invariant: All time values snap to frame boundaries when FPS is set.\n// This ensures consistent seek/render behavior.\n//\n// ============================================================================\n\n/**\n * Evaluates the target time for a seek operation.\n * Applies quantization and clamping to determine the valid seek target.\n */\nfunction evaluateSeekTarget(\n requestedTime: number,\n durationMs: number,\n fps: number,\n): number {\n // Quantize to frame boundaries\n const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);\n // Clamp to valid range [0, duration]\n return Math.max(0, Math.min(quantizedTime, durationMs / 1000));\n}\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {\n static get observedAttributes(): string[] {\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n \"auto-init\",\n \"workbench\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n /** @internal */\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n /** @internal */\n @provide({ context: efContext })\n efContext = this;\n\n /** @public */\n mode: TimeMode = \"contain\";\n /** @public */\n overlapMs = 0;\n\n /**\n * Initializer function for setting up JavaScript behavior on this timegroup.\n * This function is called on both the prime timeline and each render clone.\n * \n * REQUIRED for render operations (captureBatch, renderToVideo, createRenderClone).\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error thrown) or <10ms (warning logged)\n * - Should only register callbacks and set up behavior, not do expensive work\n * \n * @example\n * ```javascript\n * const tg = document.querySelector('ef-timegroup');\n * tg.initializer = (instance) => {\n * instance.addFrameCallback((time) => {\n * // Update content based on time\n * });\n * };\n * ```\n * @public\n */\n initializer?: TimegroupInitializer;\n\n /** @public */\n @property({ type: Number })\n fps = 30;\n\n /**\n * When true, automatically seeks to frame 0 after media durations are loaded.\n * Only applies to root timegroups (timegroups that are not nested inside another timegroup).\n * This ensures the first frame is rendered immediately on initialization.\n */\n @property({ type: Boolean, attribute: \"auto-init\" })\n autoInit = false;\n\n /**\n * When true, automatically wraps this root timegroup with an ef-workbench element.\n * The workbench provides development UI including hierarchy panel, timeline, and playback controls.\n * Only applies to root timegroups.\n * @public\n */\n @property({ type: Boolean, reflect: true })\n workbench = false;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"auto-init\") {\n this.autoInit = value !== null;\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n if (name === \"workbench\") {\n this.workbench = value !== null;\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n /** @public */\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n #currentTime: number | undefined = undefined;\n #userTimeMs: number = 0; // What the user last requested (for preview display)\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #restoringFromLocalStorage = false; // Guard to prevent recursive seeks during localStorage restoration\n \n /** @internal */\n isRestoringFromLocalStorage(): boolean {\n return this.#restoringFromLocalStorage;\n }\n \n /** @internal - Used by PlaybackController to set restoration state */\n setRestoringFromLocalStorage(value: boolean): void {\n this.#restoringFromLocalStorage = value;\n }\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n #onFrameCallback: FrameTaskCallback | null = null;\n #onFrameCleanup: (() => void) | null = null;\n #playbackListener: ((event: PlaybackControllerUpdateEvent) => void) | null = null;\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n * @public\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n async #runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n // ============================================================================\n // Purpose 3: Seeking Implementation\n // ============================================================================\n\n /** @public */\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Evaluate seek target (quantization and clamping)\n const seekTarget = evaluateSeekTarget(\n time,\n this.durationMs,\n this.effectiveFps,\n );\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n this.playbackController.currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Only root timegroups can have their currentTime set directly\n if (!this.isRootTimegroup) {\n return;\n }\n\n // Validate seek target\n if (Number.isNaN(seekTarget)) {\n return;\n }\n\n // Skip if already at target time (unless processing pending seek or restoring from localStorage)\n if (seekTarget === this.#currentTime && !this.#processingPendingSeek && !this.#restoringFromLocalStorage) {\n return;\n }\n\n // Skip if this is the same as pending seek\n if (this.#pendingSeekTime === seekTarget) {\n return;\n }\n \n // Prevent recursive seeks during localStorage restoration\n if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {\n // Allow the restoration seek to proceed, but prevent subsequent seeks\n // The flag will be cleared after the seek completes\n }\n\n // Handle concurrent seeks by queuing pending seek\n // This ensures we only have ONE seek in flight at a time, avoiding wasted work.\n // When scrubbing quickly, intermediate positions are skipped entirely - we don't\n // start work we know will be thrown away.\n if (this.#seekInProgress) {\n this.#pendingSeekTime = seekTarget;\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Execute seek - update both source time and user time\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n this.#seekInProgress = true;\n\n // Attach .catch() to prevent unhandled rejection warning - errors are handled by seekTask.onError\n this.seekTask.run().catch(() => {}).finally(() => {\n this.#seekInProgress = false;\n \n // Process pending seek if it differs from completed seek\n // This jumps directly to wherever the user ended up, skipping intermediates\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== seekTarget\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n /** @public */\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n /** @public */\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n /** @public */\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * The time the user last requested via seek/scrub.\n * Preview systems should use this instead of currentTimeMs to avoid\n * seeing intermediate times during batch operations (thumbnails, export).\n * @public\n */\n get userTimeMs(): number {\n return this.#userTimeMs;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure\n * all visible elements are ready after the seek completes.\n * \n * Updates both the source time AND userTimeMs (what the preview displays).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n * @public\n */\n async seek(timeMs: number): Promise<void> {\n // Update user time - this is what the preview should display\n this.#userTimeMs = timeMs;\n \n // Execute seek (Purpose 3)\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n // Wait for frame rendering (Purpose 4)\n await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Optimized seek for render loops.\n * Unlike `seek()`, this:\n * - Skips waitForMediaDurations (already loaded at render setup)\n * - Skips localStorage persistence\n * - Consolidates awaits to reduce event loop yields\n * \n * Still waits for all content to be ready (Lit updates, frame tasks, video frames).\n * \n * @param timeMs - Time in milliseconds to seek to\n * @internal\n */\n async seekForRender(timeMs: number): Promise<void> {\n // Set time directly (skip seekTask overhead)\n const newTime = timeMs / 1000;\n this.#userTimeMs = timeMs;\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n // First await: let Lit propagate time to children\n await this.updateComplete;\n \n // Collect all LitElement descendants (not just those with frameTask)\n // This ensures ef-text, ef-captions, and other reactive elements update\n const allLitElements = this.#getAllLitElementDescendants();\n \n // Wait for ALL LitElement descendants to complete their reactive updates\n // This is critical for elements like ef-text and ef-captions that don't have frameTask\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n \n // Wait for ef-text elements to have their segments ready\n // ef-text creates segments asynchronously via requestAnimationFrame\n const textElements = allLitElements.filter((el) => el.tagName === \"EF-TEXT\");\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\"whenSegmentsReady\" in el && typeof el.whenSegmentsReady === \"function\") {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n }\n \n // Wait one frame to ensure animations and styles have settled\n await new Promise((resolve) => requestAnimationFrame(resolve));\n \n // Now collect elements with frame tasks (media elements that need special handling)\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n \n // Wait for frame tasks and frame-ready elements\n await Promise.all([\n this.frameTask.run(),\n ...visibleElements.map((element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n return (element as any).waitForFrameReady();\n } else {\n return element.updateComplete;\n }\n }),\n ]);\n \n // CRITICAL: Force style recalculation after updateAnimations sets animation.currentTime\n // Without this, getComputedStyle may return stale values (e.g., opacity: 0 instead of 1)\n // Accessing offsetWidth triggers synchronous style recalc\n void this.offsetWidth;\n }\n \n /**\n * Collects all LitElement descendants recursively.\n * Used by seekForRender to ensure all reactive elements have updated.\n */\n #getAllLitElementDescendants(): LitElement[] {\n const result: LitElement[] = [];\n const walk = (el: Element) => {\n for (const child of el.children) {\n if (child instanceof LitElement) {\n result.push(child);\n }\n walk(child);\n }\n };\n walk(this);\n return result;\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n * @public\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Property-based frame task callback for React integration.\n * When set, automatically registers the callback as a frame task.\n * Setting a new value automatically cleans up the previous callback.\n * Set to null or undefined to remove the callback.\n *\n * @example\n * // React usage:\n * <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {\n * // Per-frame updates\n * }} />\n *\n * @public\n */\n get onFrame(): FrameTaskCallback | null {\n return this.#onFrameCallback;\n }\n\n set onFrame(callback: FrameTaskCallback | null | undefined) {\n // Clean up previous callback if exists\n if (this.#onFrameCleanup) {\n this.#onFrameCleanup();\n this.#onFrameCleanup = null;\n }\n this.#onFrameCallback = callback ?? null;\n\n // Register new callback if provided\n if (callback) {\n this.#onFrameCleanup = this.addFrameTask(callback);\n }\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n * @public\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n * @public\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n /** @internal */\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\n /** @internal */\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n const parsedValue = Number.parseFloat(storedValue);\n // Guard against NaN and Infinity which could cause issues\n if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) {\n return undefined;\n }\n return parsedValue;\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n \n // CRITICAL: super.connectedCallback() MUST be synchronous for Lit lifecycle to work correctly.\n // Deferring it breaks render clones because updateComplete resolves before Lit initializes.\n // \n // EFTemporal.connectedCallback() handles root detection after Lit Context propagates:\n // - Schedules updateComplete.then(didBecomeRoot check)\n // - Only true roots (no parent after context) create PlaybackController\n // \n // PlaybackController.hostConnected() owns ALL root initialization:\n // - waitForMediaDurations\n // - localStorage time restoration \n // - initial seek\n //\n // This avoids the previous race conditions where both EFTimegroup.connectedCallback\n // and PlaybackController.hostConnected tried to initialize, causing concurrent seeks.\n super.connectedCallback();\n\n // Defer TimegroupController creation and workbench wrapping to next frame\n // These operations involve DOM queries (closest, getBoundingClientRect) which\n // can be expensive when many elements initialize simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n });\n });\n }\n \n /**\n * Called when this timegroup becomes a root (no parent timegroup).\n * Sets up the playback listener after PlaybackController is created.\n * @internal\n */\n didBecomeRoot() {\n super.didBecomeRoot();\n this.#setupPlaybackListener();\n }\n\n /**\n * Setup listener on playbackController to sync userTimeMs during playback.\n */\n #setupPlaybackListener(): void {\n // Already setup or no controller\n if (this.#playbackListener || !this.playbackController) return;\n \n this.#playbackListener = (event: PlaybackControllerUpdateEvent) => {\n // Only update userTimeMs during playback time changes\n // Clone-timeline: captures use separate clones, so Prime-timeline updates freely\n if (event.property === \"currentTimeMs\" && typeof event.value === \"number\") {\n if (this.playing) {\n this.#userTimeMs = event.value;\n }\n }\n };\n \n this.playbackController.addListener(this.#playbackListener);\n }\n \n /**\n * Remove playback listener on disconnect.\n */\n #removePlaybackListener(): void {\n if (this.#playbackListener && this.playbackController) {\n this.playbackController.removeListener(this.#playbackListener);\n }\n this.#playbackListener = null;\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n durationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.#runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n this.#removePlaybackListener();\n }\n\n /**\n * Capture the timegroup at a specific timestamp as a canvas.\n * Does NOT modify currentTimeMs - captures are rendered independently.\n * \n * @param options - Capture options including timeMs, scale, contentReadyMode\n * @returns Promise resolving to an HTMLCanvasElement with the captured frame\n * @public\n */\n async captureAtTime(options: CaptureOptions): Promise<HTMLCanvasElement> {\n return captureTimegroupAtTime(this, options);\n }\n\n /**\n * Capture multiple timestamps as canvas thumbnails in a single batch.\n * \n * CLONE-TIMELINE ARCHITECTURE:\n * Creates a single render clone and reuses it across all captures.\n * Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.\n * \n * @param timestamps - Array of timestamps (in milliseconds) to capture\n * @param options - Capture options (scale, contentReadyMode, blockingTimeoutMs)\n * @returns Promise resolving to array of HTMLCanvasElements\n * @public\n */\n async captureBatch(\n timestamps: number[],\n options: CaptureBatchOptions = {},\n ): Promise<HTMLCanvasElement[]> {\n if (timestamps.length === 0) return [];\n\n const {\n scale = 0.25,\n contentReadyMode = \"immediate\",\n blockingTimeoutMs = 5000,\n } = options;\n\n const batchStartTime = performance.now();\n\n // CLONE-TIMELINE: Create ONE clone and reuse across all captures\n const cloneStartTime = performance.now();\n const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } =\n await this.createRenderClone();\n const cloneTime = performance.now() - cloneStartTime;\n\n // Pre-fetch scrub segments for all video elements to ensure fast seeks\n const prefetchStartTime = performance.now();\n const videoElements = renderClone.querySelectorAll(\"ef-video\");\n if (videoElements.length > 0) {\n await Promise.all(\n Array.from(videoElements).map((video) =>\n (video as import(\"./EFVideo.js\").EFVideo).prefetchScrubSegments(timestamps),\n ),\n );\n }\n const prefetchTime = performance.now() - prefetchStartTime;\n\n const canvases: HTMLCanvasElement[] = [];\n let totalSeekTime = 0;\n let totalCaptureTime = 0;\n\n try {\n for (let i = 0; i < timestamps.length; i++) {\n const timeMs = timestamps[i]!\n \n // Seek clone to target time using optimized seekForRender\n // (skips waitForMediaDurations, localStorage, consolidates awaits)\n const seekStart = performance.now();\n await renderClone.seekForRender(timeMs);\n totalSeekTime += performance.now() - seekStart;\n \n // Capture from the seeked clone\n const captureStart = performance.now();\n const canvas = await captureFromClone(renderClone, renderContainer, {\n scale,\n contentReadyMode,\n blockingTimeoutMs,\n originalTimegroup: this,\n });\n totalCaptureTime += performance.now() - captureStart;\n canvases.push(canvas);\n }\n \n return canvases;\n } finally {\n // Log timing and clean up the render clone\n const totalTime = performance.now() - batchStartTime;\n console.log(`[captureBatch] ${timestamps.length} frames: clone=${cloneTime.toFixed(0)}ms, prefetch=${prefetchTime.toFixed(0)}ms, seek=${totalSeekTime.toFixed(0)}ms, capture=${totalCaptureTime.toFixed(0)}ms, total=${totalTime.toFixed(0)}ms`);\n cleanupRenderClone();\n }\n }\n\n /**\n * Render the timegroup to an MP4 video file and trigger download.\n * Captures each frame at the specified fps, encodes using WebCodecs via\n * MediaBunny, and downloads the resulting video.\n * \n * @param options - Rendering options (fps, codec, bitrate, filename, etc.)\n * @returns Promise that resolves when video is downloaded\n * @public\n */\n async renderToVideo(options?: RenderToVideoOptions): Promise<Uint8Array | undefined> {\n return renderTimegroupToVideo(this, options);\n }\n\n /**\n * Runs the initializer function with validation for synchronous execution and time budget.\n * @throws Error if no initializer is set\n * @throws Error if initializer returns a Promise (async not allowed)\n * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS\n * @internal\n */\n #runInitializer(cloneEl: EFTimegroup): void {\n if (!this.initializer) {\n return;\n }\n \n const startTime = performance.now();\n const result: unknown = this.initializer(cloneEl);\n const elapsed = performance.now() - startTime;\n \n // Check for async (Promise return) - initializers MUST be synchronous\n if (result !== undefined && result !== null && typeof (result as any).then === 'function') {\n throw new Error(\n 'Timeline initializer must be synchronous. ' +\n 'Do not return a Promise from the initializer function.'\n );\n }\n \n // Time budget enforcement - initializers run for EVERY clone\n if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) {\n throw new Error(\n `Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. ` +\n 'Initializers must be fast - move expensive work outside the initializer.'\n );\n }\n \n if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) {\n console.warn(\n `[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. ` +\n 'Consider optimizing for better render performance.'\n );\n }\n }\n\n /**\n * Copy captionsData property from original to clone.\n * cloneNode() only copies attributes, not JavaScript properties.\n * captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),\n * so we must manually copy it to the cloned elements.\n * @internal\n */\n #copyCaptionsData(original: Element, clone: Element): void {\n // Find matching caption elements by position (querySelectorAll returns in document order)\n const originalCaptions = original.querySelectorAll('ef-captions');\n const cloneCaptions = clone.querySelectorAll('ef-captions');\n \n for (let i = 0; i < originalCaptions.length && i < cloneCaptions.length; i++) {\n const origCap = originalCaptions[i] as any;\n const cloneCap = cloneCaptions[i] as any;\n \n // Copy captionsData if set via JS property\n if (origCap.captionsData) {\n cloneCap.captionsData = origCap.captionsData;\n }\n }\n }\n \n /**\n * Copy ef-text _textContent property from original to cloned elements.\n * This MUST be called BEFORE elements upgrade (before updateComplete)\n * because splitText() runs in connectedCallback and will clear segments\n * if _textContent is null/empty.\n * @internal\n */\n #copyTextContent(original: Element, clone: Element): void {\n const originalTexts = original.querySelectorAll('ef-text');\n const cloneTexts = clone.querySelectorAll('ef-text');\n \n for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {\n const origText = originalTexts[i] as any;\n const cloneText = cloneTexts[i] as any;\n \n // Copy _textContent if it exists\n // This is a private property, so we access it via any\n if (origText._textContent !== undefined) {\n cloneText._textContent = origText._textContent;\n }\n // Also copy the segments getter to ensure we can read them\n if (origText._templateElement !== undefined) {\n cloneText._templateElement = origText._templateElement;\n }\n }\n }\n \n /**\n * Copy ef-text-segment properties from original to cloned elements.\n * segmentText and other properties are set via JS, not attributes,\n * so we must manually copy them to the cloned elements.\n * @internal\n */\n #copyTextSegmentData(original: Element, clone: Element): void {\n // Find matching text segment elements by position\n const originalSegments = original.querySelectorAll('ef-text-segment');\n const cloneSegments = clone.querySelectorAll('ef-text-segment');\n \n for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {\n const origSeg = originalSegments[i] as any;\n const cloneSeg = cloneSegments[i] as any;\n \n // Copy all segment properties\n if (origSeg.segmentText !== undefined) {\n cloneSeg.segmentText = origSeg.segmentText;\n }\n if (origSeg.segmentIndex !== undefined) {\n cloneSeg.segmentIndex = origSeg.segmentIndex;\n }\n if (origSeg.staggerOffsetMs !== undefined) {\n cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;\n }\n if (origSeg.segmentStartMs !== undefined) {\n cloneSeg.segmentStartMs = origSeg.segmentStartMs;\n }\n if (origSeg.segmentEndMs !== undefined) {\n cloneSeg.segmentEndMs = origSeg.segmentEndMs;\n }\n }\n }\n\n /**\n * Wait for all ef-captions elements to have their data loaded.\n * This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.\n * Used by createRenderClone to ensure captions are ready before rendering.\n * @internal\n */\n async #waitForCaptionsData(root: Element): Promise<void> {\n // Find all ef-captions elements (including nested in timegroups)\n const captionsElements = root.querySelectorAll('ef-captions');\n if (captionsElements.length === 0) return;\n \n // Wait for each caption element's data to load\n // Use duck-typing to check for unifiedCaptionsDataTask without importing EFCaptions\n const waitPromises: Promise<unknown>[] = [];\n for (const el of captionsElements) {\n const captions = el as any;\n const task = captions.unifiedCaptionsDataTask;\n if (!task) continue;\n \n // Only wait if task is PENDING or needs to be run\n // TaskStatus: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n if (task.status === TaskStatus.COMPLETE || task.status === TaskStatus.ERROR) {\n // Already complete or errored, no need to wait\n continue;\n }\n \n if (task.status === TaskStatus.INITIAL) {\n // Need to run the task first\n // Attach .catch() to prevent unhandled rejection warning - errors handled via taskComplete\n task.run().catch(() => {});\n }\n \n // Task is now PENDING, wait for it\n if (task.taskComplete) {\n waitPromises.push(task.taskComplete);\n }\n }\n \n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Create an independent clone of this timegroup for rendering.\n * The clone is a fully functional ef-timegroup with its own animations\n * and time state, isolated from the original (Prime-timeline).\n * \n * OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`\n * to re-run JavaScript setup (frame callbacks, React components) on each clone.\n * \n * This enables:\n * - Rendering without affecting user's preview position\n * - Concurrent renders with different clones\n * - Re-running JavaScript setup on each clone (if initializer is provided)\n * \n * @returns Promise resolving to clone, container, and cleanup function\n * @throws Error if initializer is async or takes too long\n * @public\n */\n async createRenderClone(): Promise<RenderCloneResult> {\n // 1. Create offscreen container positioned off-screen but in the DOM\n // The clone needs to be in the DOM for:\n // - Custom elements to upgrade (connectedCallback)\n // - CSS to compute correctly\n // - Animations to work\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${this.offsetWidth || 1920}px;\n height: ${this.offsetHeight || 1080}px;\n pointer-events: none;\n overflow: hidden;\n `;\n \n // 2. Deep clone the DOM - this clones the entire subtree\n const cloneEl = this.cloneNode(true) as EFTimegroup;\n \n // CRITICAL: Clear the clone's id to prevent localStorage conflicts\n // The original timegroup and clone would share the same localStorage key otherwise,\n // which causes time to be loaded from storage during connectedCallback.\n cloneEl.removeAttribute(\"id\");\n \n // 2b. Copy JavaScript properties that aren't cloned by cloneNode()\n this.#copyCaptionsData(this, cloneEl);\n \n // 2c. Copy ef-text _textContent BEFORE elements upgrade\n // This is critical because splitText() runs in connectedCallback and will\n // clear segments if _textContent is empty\n this.#copyTextContent(this, cloneEl);\n \n // 3. Preserve ef-configuration context for the clone\n // Media elements use closest(\"ef-configuration\") to determine settings like media-engine.\n // Without this, clones would lose configuration and use wrong media engine types.\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n // Shallow clone the configuration element (just the element, not children)\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n configClone.appendChild(cloneEl);\n container.appendChild(configClone);\n } else {\n container.appendChild(cloneEl);\n }\n \n document.body.appendChild(container);\n \n // 3. Wait for custom elements to upgrade\n await cloneEl.updateComplete;\n \n // 3b. Copy ef-text-segment properties AFTER elements have upgraded\n // segmentText is a JS property, so we need to wait for custom elements to define it\n this.#copyTextSegmentData(this, cloneEl);\n \n // 4. Run initializer to set up JavaScript behavior on the clone\n // This re-registers frame callbacks, React components, etc.\n // MUST be synchronous and fast (enforced by #runInitializer)\n // NOTE: For React, the initializer may REPLACE cloneEl with a fresh React-rendered tree\n this.#runInitializer(cloneEl);\n \n // 5. Find the actual timegroup after initializer runs\n // React initializers replace the cloned DOM with a fresh render, so we need to find\n // the actual ef-timegroup in the container (may be different from cloneEl)\n let actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error(\n 'No ef-timegroup found after initializer. ' +\n 'Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).'\n );\n }\n \n // 6. Wait for custom elements to upgrade\n // React renders DOM synchronously via flushSync, but custom elements upgrade asynchronously.\n // We need to ensure the element has been upgraded to its class before accessing Lit properties.\n await customElements.whenDefined('ef-timegroup');\n \n // Force upgrade of all custom elements in the container (in case they haven't upgraded yet)\n customElements.upgrade(container);\n \n // Re-query in case the element reference changed during upgrade\n actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error('ef-timegroup element lost after upgrade');\n }\n \n // 7. Wait for LitElement updates and media durations\n await actualClone.updateComplete;\n \n // 7a. CRITICAL: Manually set up parent-child relationships for cloned elements\n // Lit Context doesn't automatically propagate to cloned children because the\n // context consumer decorator runs before the element is in the DOM tree.\n // We need to explicitly walk the tree and set parentTimegroup/rootTimegroup on each element.\n const setupParentChildRelationships = (parent: EFTimegroup, root: EFTimegroup) => {\n for (const child of parent.children) {\n // Handle nested timegroups\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n // Use the public setter to trigger proper initialization\n childTG.parentTimegroup = parent;\n childTG.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n (childTG as any).lockRootTimegroup();\n // Recursively process children\n setupParentChildRelationships(childTG, root);\n }\n // Handle temporal elements (videos, audio, captions, etc.)\n else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = parent;\n temporal.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n }\n // Recursively check non-timegroup containers (divs, etc.)\n else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, parent, root);\n }\n }\n };\n \n const setupParentChildRelationshipsInContainer = (container: Element, nearestParentTG: EFTimegroup, root: EFTimegroup) => {\n for (const child of container.children) {\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = nearestParentTG;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = nearestParentTG;\n temporal.rootTimegroup = root;\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, nearestParentTG, root);\n }\n }\n };\n \n // Set up the root clone itself (it's its own root, no parent)\n // Note: This must happen BEFORE lockRootTimegroup to set the correct value\n actualClone.rootTimegroup = actualClone;\n setupParentChildRelationships(actualClone, actualClone);\n \n // Wait for updates to propagate\n await actualClone.updateComplete;\n \n // CRITICAL: Lock AFTER all updates are complete to prevent further context changes\n // Also re-set rootTimegroup in case Lit Context overwrote it during updates\n actualClone.rootTimegroup = actualClone;\n (actualClone as any).lockRootTimegroup();\n const finalizeRootTimegroup = (el: Element) => {\n if ('rootTimegroup' in el && 'lockRootTimegroup' in el) {\n (el as any).rootTimegroup = actualClone;\n (el as any).lockRootTimegroup();\n }\n for (const child of el.children) {\n finalizeRootTimegroup(child);\n }\n };\n finalizeRootTimegroup(actualClone);\n \n await actualClone.waitForMediaDurations();\n \n // 7b. Wait for captions data to load (ef-captions elements need to fetch their data)\n // This is separate from waitForMediaDurations because EFCaptions is not an EFMedia.\n await this.#waitForCaptionsData(actualClone);\n \n // 8. CRITICAL: Remove PlaybackController from clone\n // Clones get a PlaybackController when they become root (in didBecomeRoot callback).\n // But render clones need direct seeking without the UI/context machinery.\n // Remove it to enable direct seekTask execution.\n if (actualClone.playbackController) {\n actualClone.playbackController.remove();\n actualClone.playbackController = undefined;\n }\n \n // 9. Initial seek to frame 0 to ensure animations are at correct state\n await actualClone.seek(0);\n \n return {\n clone: actualClone,\n container,\n cleanup: () => {\n // Remove container from DOM immediately\n container.remove();\n \n // Unmount React root if present (set by TimelineRoot component)\n // Defer to next microtask to avoid \"unmount during render\" warning\n // when cleanup is called rapidly during batch operations\n const reactRoot = (actualClone as any)._reactRoot;\n if (reactRoot) {\n queueMicrotask(() => {\n reactRoot.unmount();\n });\n }\n },\n };\n }\n\n /** @internal */\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n /** @internal */\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n /** @internal */\n get hasOwnDuration() {\n return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);\n }\n\n // ============================================================================\n // Purpose 1: Composition Rules Implementation\n // ============================================================================\n\n /** @public */\n get durationMs(): number {\n // Fixed mode delegates to parent class durationMs which handles trimming, source in/out, etc.\n if (this.mode === \"fixed\") {\n return super.durationMs;\n }\n\n // Evaluate duration semantics based on mode (Purpose 1)\n // childTemporals returns TemporalMixinInterface[], but we need HTMLElement intersection\n const childTemporalsAsElements = this.childTemporals as Array<\n TemporalMixinInterface & HTMLElement\n >;\n return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);\n }\n\n // ============================================================================\n // Purpose 4: Frame Rendering - What Happens Each Frame\n // ============================================================================\n\n /**\n * Evaluates which elements should be rendered in the current frame.\n * Filters to only include temporally visible elements for frame processing.\n * Uses animation-friendly visibility to prevent animation jumps at exact boundaries.\n */\n #evaluateVisibleElementsForFrame(): Array<\n TemporalMixinInterface & HTMLElement\n > {\n const temporalElements = deepGetElementsWithFrameTasks(this);\n return temporalElements.filter((element) => {\n const animationState = evaluateAnimationVisibilityState(element);\n return animationState.isVisible;\n });\n }\n\n /** @internal */\n async waitForFrameTasks(signal?: AbortSignal) {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Evaluate which elements should be rendered\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n // Execute frame tasks for all visible elements\n // CRITICAL: Must wait for updateComplete before running frameTask so that\n // property changes (like desiredSeekTimeMs) have propagated to the element.\n // Elements with waitForFrameReady() handle this; others need explicit waiting.\n await Promise.all(\n visibleElements.map(async (element) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n try {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n await element.frameTask.run();\n }\n } catch (error) {\n // AbortErrors are expected when elements are disconnected or tasks cancelled\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n (error as any).message?.includes(\"signal is aborted\") ||\n (error as any).message?.includes(\"The user aborted a request\")\n );\n \n if (isAbortError) {\n // Re-throw if our signal is also aborted (propagate cancellation)\n signal?.throwIfAborted();\n return; // Otherwise silently ignore - element was disconnected\n }\n throw error;\n }\n }),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n #mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n /** @internal */\n async waitForMediaDurations(signal?: AbortSignal) {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Start loading media durations in background, but don't block if already in progress\n // This prevents multiple concurrent calls from creating redundant work\n if (!this.#mediaDurationsPromise) {\n this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch(err => {\n // Re-throw AbortError to propagate cancellation\n if (err instanceof DOMException && err.name === \"AbortError\") {\n this.#mediaDurationsPromise = undefined;\n throw err;\n }\n console.error(`[EFTimegroup] waitForMediaDurations failed for ${this.id || 'unnamed'}:`, err);\n // Clear promise on error so it can be retried\n this.#mediaDurationsPromise = undefined;\n throw err;\n });\n }\n \n // If signal is provided and aborted, throw immediately\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n return this.#mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations(signal?: AbortSignal) {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Don't wait for updateComplete during initialization - it causes deadlocks with nested timegroups\n // Instead, use a short delay to let elements connect, then scan for media elements\n // If elements aren't ready yet, we'll retry or they'll be picked up on the next update cycle\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n clearTimeout(timeoutId);\n cancelAnimationFrame(rafId2);\n cancelAnimationFrame(rafId1);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener('abort', abortHandler, { once: true });\n \n let rafId1: number;\n let rafId2: number;\n let timeoutId: ReturnType<typeof setTimeout>;\n \n // Use multiple animation frames to ensure DOM is ready, but don't wait for all children\n rafId1 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n rafId2 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n // Small additional delay to let custom elements upgrade\n timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', abortHandler);\n resolve();\n }, 10);\n });\n });\n });\n \n // Check abort after delay\n signal?.throwIfAborted();\n \n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n // Use Promise.allSettled with timeout to avoid blocking if asset server is slow\n const mediaLoadStart = Date.now();\n const MEDIA_LOAD_TIMEOUT_MS = 30000; // 30 second timeout per element\n \n const loadPromises = mediaElements.map(async (m, index) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n const elementStart = Date.now();\n try {\n // Check task status to avoid aborting already-running tasks\n // TaskStatus: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n const status = m.mediaEngineTask.status;\n \n // Already complete or errored - no need to wait\n if (status === TaskStatus.COMPLETE || status === TaskStatus.ERROR) {\n return;\n }\n \n // Task is in INITIAL state - need to run it\n // (PENDING means it's already running - don't call run() again as that would abort it)\n if (status === TaskStatus.INITIAL) {\n // Attach .catch() to prevent unhandled rejection warning - AbortErrors are expected\n const runPromise = m.mediaEngineTask.run();\n runPromise.catch(() => {}); // Suppress unhandled rejection - errors handled below\n }\n \n // Now wait for the task to complete (whether we started it or it was already running)\n // Add timeout to prevent indefinite blocking\n const timeoutPromise = new Promise((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n });\n \n // Attach .catch() to taskComplete to prevent unhandled rejection\n const taskPromise = m.mediaEngineTask.taskComplete;\n taskPromise.catch(() => {});\n \n await Promise.race([taskPromise, timeoutPromise]);\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Log only if tracing is enabled to reduce console noise\n if (isTracingEnabled()) {\n const elementElapsed = Date.now() - elementStart;\n console.error(`[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`, error);\n }\n // Don't throw - continue with other elements\n }\n });\n \n const results = await Promise.allSettled(loadPromises);\n \n // Check if any were aborted\n const aborted = results.some(r => \n r.status === 'rejected' && \n r.reason instanceof DOMException && \n r.reason.name === \"AbortError\"\n );\n if (aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n // Log any failures but don't throw - we want to continue even if some media fails\n const failures = results.filter(r => r.status === 'rejected');\n if (failures.length > 0 && isTracingEnabled()) {\n const mediaLoadElapsed = Date.now() - mediaLoadStart;\n console.warn(`[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`, failures.map(r => r.status === 'rejected' ? r.reason : null));\n }\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n // Defer using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This method can be called during a task or after an update cycle completes, and using\n // setTimeout ensures we're completely outside any Lit update cycle.\n setTimeout(() => this.requestUpdate(\"currentTime\"), 0);\n // Note: We don't await updateComplete here during initialization to avoid deadlocks.\n // The update will complete asynchronously, and sequence groups will arrange correctly\n // once all timegroups have finished initializing. During normal operation (seeks, etc.),\n // the caller will wait for updateComplete explicitly if needed.\n },\n );\n }\n\n /** @internal */\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get #contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - The workbench property is set to true\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n * @internal\n */\n shouldWrapWithWorkbench() {\n // Only root timegroups should wrap with workbench\n if (!this.isRootTimegroup) {\n return false;\n }\n\n // Never wrap with workbench when inside a canvas\n // Canvas manages its own layout and coordinate system\n if (this.closest(\"ef-canvas\") !== null) {\n return false;\n }\n\n // Never wrap if already inside preview, workbench, or preview context\n if (\n this.closest(\"ef-preview\") !== null ||\n this.closest(\"ef-workbench\") !== null ||\n this.closest(\"ef-preview-context\") !== null\n ) {\n return false;\n }\n\n // Skip wrapping in test contexts\n if (this.closest(\"test-context\") !== null) {\n return false;\n }\n\n // Never wrap render clones (they're in an offscreen container for capture operations)\n if (this.closest(\".ef-render-clone-container\") !== null) {\n return false;\n }\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n const isRendering = EF_RENDERING?.() === true;\n if (isRendering) {\n return true;\n }\n\n // Respect the explicit workbench property\n return this.workbench;\n }\n\n /** @internal */\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n this.parentElement?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-timegroup\");\n }\n\n // Create pan-zoom for selection overlay support\n // Must be in light DOM so canvas can find it via closest()\n const panZoom = document.createElement(\"ef-pan-zoom\");\n panZoom.id = \"workbench-panzoom\";\n panZoom.setAttribute(\"slot\", \"canvas\");\n panZoom.setAttribute(\"auto-fit\", \"\"); // Fit content to view on first render\n panZoom.style.width = \"100%\";\n panZoom.style.height = \"100%\";\n\n // Create canvas wrapper for selection/highlighting support\n // Get dimensions from the timegroup for explicit canvas sizing\n const rect = this.getBoundingClientRect();\n const canvas = document.createElement(\"ef-canvas\");\n canvas.id = \"workbench-canvas\";\n // Use explicit dimensions (required for selection bounds calculation)\n canvas.style.width = `${Math.max(rect.width, 1920)}px`;\n canvas.style.height = `${Math.max(rect.height, 1080)}px`;\n canvas.style.display = \"block\";\n\n // Move timegroup into canvas, canvas into pan-zoom\n canvas.append(this as unknown as Element);\n panZoom.append(canvas);\n workbench.append(panZoom);\n\n // Add hierarchy panel - targets canvas for selection support\n const hierarchy = document.createElement(\"ef-hierarchy\");\n hierarchy.setAttribute(\"slot\", \"hierarchy\");\n hierarchy.setAttribute(\"target\", \"workbench-canvas\");\n hierarchy.setAttribute(\"header\", \"Scenes\");\n workbench.append(hierarchy);\n\n // Add filmstrip/timeline - targets timegroup for playback\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n get #efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n * @internal\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n * @internal\n */\n async renderAudio(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs, signal);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async #testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async #loadMd5Sums() {\n const efElements = this.#efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n if (md5SumLoader instanceof Task) {\n // Attach .catch() to prevent unhandled rejection warning - errors handled via taskComplete\n md5SumLoader.run().catch(() => {});\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n // Track frameTask execution count to detect runaway loops\n #timegroupFrameTaskCount = 0;\n #timegroupFrameTaskLastReset = Date.now();\n static readonly TIMEGROUP_FRAME_TASK_THRESHOLD = 100;\n static readonly TIMEGROUP_FRAME_TASK_RESET_MS = 1000;\n\n /** @internal */\n frameTask = new Task(this, {\n // Re-enabled with EF_INTERACTIVE guard: only auto-runs in interactive mode\n // During export/rendering, frame tasks are triggered explicitly via seekTask\n autoRun: EF_INTERACTIVE,\n args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete in onError.\n // This is called BEFORE reject(), so the handler is attached in time\n // to prevent 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 console.error(\"EFTimegroup frameTask error\", error);\n },\n task: async ([ownCurrentTimeMs, currentTimeMs], { signal }) => {\n // Check for runaway loop\n const now = Date.now();\n if (now - this.#timegroupFrameTaskLastReset > EFTimegroup.TIMEGROUP_FRAME_TASK_RESET_MS) {\n this.#timegroupFrameTaskCount = 0;\n this.#timegroupFrameTaskLastReset = now;\n }\n this.#timegroupFrameTaskCount++;\n \n if (this.#timegroupFrameTaskCount > EFTimegroup.TIMEGROUP_FRAME_TASK_THRESHOLD) {\n // Safety break to prevent infinite loops\n return;\n }\n \n // Check abort before starting work\n signal?.throwIfAborted();\n \n if (this.isRootTimegroup) {\n // Root timegroup orchestrates frame rendering for entire tree\n return withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs,\n currentTimeMs,\n },\n undefined,\n async () => {\n // Wait for all child frame tasks to complete (Purpose 4)\n await this.waitForFrameTasks(signal);\n // Check abort after async operation\n signal?.throwIfAborted();\n // Execute custom frame tasks registered on this timegroup\n await this.#executeCustomFrameTasks();\n // Check abort before final operation\n signal?.throwIfAborted();\n // Update animations based on current time\n // NOTE: This is needed even during export because it sets CSS animation\n // times which syncStyles then copies to the clone structure.\n updateAnimations(this);\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n return this.#executeCustomFrameTasks();\n }\n },\n });\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n /** @internal */\n seekTask = new Task(this, {\n autoRun: false,\n args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,\n onComplete: () => {},\n task: async ([targetTime], { signal }) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Delegate to playbackController if available\n if (this.playbackController) {\n return this.playbackController.seekTask.taskComplete.then(() => {\n signal?.throwIfAborted();\n return this.currentTime;\n });\n }\n\n // Only root timegroups execute seek tasks\n if (!this.isRootTimegroup) {\n return;\n }\n\n return withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n // Wait for media durations to be loaded (needed for accurate duration calculations)\n // But don't block indefinitely - use a timeout to prevent freezes\n try {\n await Promise.race([\n this.waitForMediaDurations(signal),\n new Promise((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error('waitForMediaDurations timeout')), 10000);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n });\n })\n ]);\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Continue with seek even if durations aren't loaded yet\n // Duration will be 0 or incorrect, but at least the page won't freeze\n }\n\n // Check abort after async operation\n signal?.throwIfAborted();\n\n // Evaluate and apply seek target\n const newTime = evaluateSeekTarget(\n targetTime ?? 0,\n this.durationMs,\n this.effectiveFps,\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n\n // Apply the seek target (triggers time propagation to children)\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n // CRITICAL: Wait for update cycle to complete so children have updated desiredSeekTimeMs\n // before we run their frame tasks. Without this, children would use stale values.\n await this.updateComplete;\n\n // Check abort before frame task\n signal?.throwIfAborted();\n\n // Trigger frame rendering for the new time position\n await this.#runThrottledFrameTask();\n\n // Check abort after frame task\n signal?.throwIfAborted();\n\n // Save to localStorage for persistence (but not during restoration to avoid loops)\n if (!this.#restoringFromLocalStorage) {\n this.saveTimeToLocalStorage(this.#currentTime);\n }\n this.#seekInProgress = false;\n // Clear restoration flag after seek completes\n if (this.#restoringFromLocalStorage) {\n this.#restoringFromLocalStorage = false;\n }\n return newTime;\n },\n );\n },\n });\n\n /**\n * Get container information for this timegroup.\n * Timegroups are always containers and can contain children.\n * Display mode is determined from computed styles.\n *\n * @public\n */\n getContainerInfo(): ContainerInfo {\n const info = getContainerInfoFromElement(this);\n // Timegroups are always containers and can contain children\n return {\n ...info,\n isContainer: true,\n canContainChildren: true,\n };\n }\n\n /**\n * Get position information for this timegroup.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { EFFramegen } from "../EF_FRAMEGEN.js";
|
|
2
2
|
import { InputTask } from "./EFMedia/shared/MediaTaskUtils.js";
|
|
3
3
|
import { MediaEngine } from "../transcoding/types/index.js";
|
|
4
|
+
import { MediaBufferState } from "./EFMedia/shared/BufferUtils.js";
|
|
4
5
|
import { EFMedia } from "./EFMedia.js";
|
|
5
6
|
import { VideoBufferState } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
|
|
6
7
|
import { Task } from "@lit/task";
|
|
7
|
-
import * as
|
|
8
|
+
import * as lit2 from "lit";
|
|
8
9
|
import { PropertyValueMap } from "lit";
|
|
9
10
|
import * as mediabunny0 from "mediabunny";
|
|
10
|
-
import * as
|
|
11
|
+
import * as lit_html2 from "lit-html";
|
|
11
12
|
import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
12
13
|
|
|
13
14
|
//#region src/elements/EFVideo.d.ts
|
|
@@ -19,9 +20,14 @@ interface LoadingState {
|
|
|
19
20
|
operation: "scrub-segment" | "video-segment" | "seeking" | "decoding" | null;
|
|
20
21
|
message: string;
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Event detail for scrub segment loading progress.
|
|
25
|
+
* Dispatched during prefetchScrubSegments to indicate network activity.
|
|
26
|
+
*/
|
|
27
|
+
|
|
22
28
|
declare const EFVideo_base: typeof EFMedia;
|
|
23
29
|
declare class EFVideo extends EFVideo_base {
|
|
24
|
-
static styles:
|
|
30
|
+
static styles: lit2.CSSResult[];
|
|
25
31
|
canvasRef: lit_html_directives_ref0.Ref<HTMLCanvasElement>;
|
|
26
32
|
/**
|
|
27
33
|
* Duration in milliseconds for video buffering ahead of current time
|
|
@@ -40,7 +46,7 @@ declare class EFVideo extends EFVideo_base {
|
|
|
40
46
|
enableVideoBuffering: boolean;
|
|
41
47
|
unifiedVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
|
|
42
48
|
videoBufferTask: Task<readonly [number], VideoBufferState>;
|
|
43
|
-
scrubVideoBufferTask: Task<readonly [
|
|
49
|
+
scrubVideoBufferTask: Task<readonly [any], MediaBufferState>;
|
|
44
50
|
scrubVideoInputTask: InputTask;
|
|
45
51
|
scrubVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
|
|
46
52
|
scrubVideoSegmentIdTask: Task<readonly [MediaEngine | undefined, number], number | undefined>;
|
|
@@ -60,7 +66,7 @@ declare class EFVideo extends EFVideo_base {
|
|
|
60
66
|
};
|
|
61
67
|
constructor();
|
|
62
68
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
63
|
-
render():
|
|
69
|
+
render(): lit_html2.TemplateResult<1>;
|
|
64
70
|
get canvasElement(): HTMLCanvasElement | undefined;
|
|
65
71
|
frameTask: Task<readonly [number], void>;
|
|
66
72
|
/**
|
|
@@ -111,12 +117,43 @@ declare class EFVideo extends EFVideo_base {
|
|
|
111
117
|
* @returns Promise that resolves when the frame is ready
|
|
112
118
|
*/
|
|
113
119
|
waitForFrameReady(): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Pre-fetch scrub segments for given timestamps.
|
|
122
|
+
* Loads 30-second segments sequentially, emitting progress events.
|
|
123
|
+
* This ensures scrub track is cached for fast thumbnail generation.
|
|
124
|
+
*
|
|
125
|
+
* @param timestamps - Array of timestamps (in ms) that will be captured
|
|
126
|
+
* @param onProgress - Optional callback for loading progress
|
|
127
|
+
* @returns Promise that resolves when all segments are cached
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
prefetchScrubSegments(timestamps: number[], onProgress?: (loaded: number, total: number, segmentTimeRange: [number, number]) => void): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Pre-fetch main video segments for given timestamps.
|
|
133
|
+
* This ensures segments are cached for fast seeking during video export.
|
|
134
|
+
*
|
|
135
|
+
* @param timestamps - Array of timestamps (in ms) that will be captured
|
|
136
|
+
* @param onProgress - Optional callback for loading progress
|
|
137
|
+
* @returns Promise that resolves when all segments are cached
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
prefetchMainVideoSegments(timestamps: number[], onProgress?: (loaded: number, total: number) => void): Promise<void>;
|
|
114
141
|
/**
|
|
115
142
|
* Clean up resources when component is disconnected
|
|
116
143
|
*/
|
|
117
144
|
disconnectedCallback(): void;
|
|
118
145
|
didBecomeRoot(): void;
|
|
119
146
|
didBecomeChild(): void;
|
|
147
|
+
/**
|
|
148
|
+
* Get the natural dimensions of the video (coded width and height).
|
|
149
|
+
* Returns null if the video hasn't loaded yet or canvas isn't available.
|
|
150
|
+
*
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
153
|
+
getNaturalDimensions(): {
|
|
154
|
+
width: number;
|
|
155
|
+
height: number;
|
|
156
|
+
} | null;
|
|
120
157
|
}
|
|
121
158
|
declare global {
|
|
122
159
|
interface HTMLElementTagNameMap {
|