@editframe/elements 0.30.2-beta.0 → 0.31.0-beta.1
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 +129 -56
- package/dist/elements/EFThumbnailStrip.js +605 -359
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +233 -25
- package/dist/elements/EFTimegroup.js +865 -144
- 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 +154 -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 +171 -28
- 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 +7 -1
- 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/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 +178 -0
- 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 +833 -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/preview/workers/encoderWorkerInline.js +103 -0
- package/dist/preview/workers/encoderWorkerInline.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 +71 -67
- 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,8 +1,9 @@
|
|
|
1
1
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
2
|
+
import { quantizeToFrameTimeS } from "../utils/frameTime.js";
|
|
2
3
|
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
3
4
|
import { parseTimeToMs } from "./parseTimeToMs.js";
|
|
4
5
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
5
|
-
import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
|
|
6
|
+
import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, registerIsTimegroupCalculatingDuration, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
|
|
6
7
|
import { efContext } from "../gui/efContext.js";
|
|
7
8
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
8
9
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
@@ -12,6 +13,16 @@ import { EFTargetable } from "./TargetController.js";
|
|
|
12
13
|
import { deepGetMediaElements } from "./EFMedia.js";
|
|
13
14
|
import { TimegroupController } from "./TimegroupController.js";
|
|
14
15
|
import { evaluateAnimationVisibilityState, updateAnimations } from "./updateAnimations.js";
|
|
16
|
+
import { getContainerInfoFromElement } from "./ContainerInfo.js";
|
|
17
|
+
import { getPositionInfoFromElement } from "./ElementPositionInfo.js";
|
|
18
|
+
import { captureFromClone, captureTimegroupAtTime } from "../preview/renderTimegroupToCanvas.js";
|
|
19
|
+
import { renderTimegroupToVideo } from "../preview/renderTimegroupToVideo.js";
|
|
20
|
+
import "../canvas/EFCanvas.js";
|
|
21
|
+
import "../gui/hierarchy/EFHierarchy.js";
|
|
22
|
+
import "../gui/EFFilmstrip.js";
|
|
23
|
+
import "../gui/EFFitScale.js";
|
|
24
|
+
import "../gui/EFWorkbench.js";
|
|
25
|
+
import "./EFPanZoom.js";
|
|
15
26
|
import { provide } from "@lit/context";
|
|
16
27
|
import { Task, TaskStatus } from "@lit/task";
|
|
17
28
|
import debug from "debug";
|
|
@@ -21,15 +32,150 @@ import { customElement, property } from "lit/decorators.js";
|
|
|
21
32
|
//#region src/elements/EFTimegroup.ts
|
|
22
33
|
var _EFTimegroup;
|
|
23
34
|
const log = debug("ef:elements:EFTimegroup");
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
35
|
+
const INITIALIZER_ERROR_THRESHOLD_MS = 100;
|
|
36
|
+
const INITIALIZER_WARN_THRESHOLD_MS = 10;
|
|
37
|
+
let durationCache = /* @__PURE__ */ new WeakMap();
|
|
38
|
+
const flushDurationCache = () => {
|
|
39
|
+
durationCache = /* @__PURE__ */ new WeakMap();
|
|
27
40
|
};
|
|
41
|
+
const flushSequenceDurationCache = flushDurationCache;
|
|
42
|
+
const durationCalculationInProgress = /* @__PURE__ */ new WeakSet();
|
|
43
|
+
const isTimegroupCalculatingDuration = (timegroup) => {
|
|
44
|
+
return timegroup !== void 0 && durationCalculationInProgress.has(timegroup);
|
|
45
|
+
};
|
|
46
|
+
registerIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);
|
|
47
|
+
/**
|
|
48
|
+
* Determines if a timegroup has its own duration based on its mode.
|
|
49
|
+
* This is the semantic rule: which modes produce independent durations.
|
|
50
|
+
*/
|
|
51
|
+
function hasOwnDurationForMode(mode, hasExplicitDuration) {
|
|
52
|
+
return mode === "contain" || mode === "sequence" || mode === "fixed" && hasExplicitDuration;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Determines if a child temporal element should participate in parent duration calculation.
|
|
56
|
+
*
|
|
57
|
+
* Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's
|
|
58
|
+
* duration calculation (to avoid circular dependencies). Children without own duration
|
|
59
|
+
* also don't contribute.
|
|
60
|
+
*/
|
|
61
|
+
function shouldParticipateInDurationCalculation(child) {
|
|
62
|
+
if (child instanceof EFTimegroup && child.mode === "fit") return false;
|
|
63
|
+
if (!child.hasOwnDuration) return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Evaluates duration for "fit" mode: inherits from parent.
|
|
68
|
+
* Semantic rule: fit mode always matches parent duration, or 0 if no parent.
|
|
69
|
+
*/
|
|
70
|
+
function evaluateFitDuration(parentTimegroup) {
|
|
71
|
+
if (!parentTimegroup) return 0;
|
|
72
|
+
return parentTimegroup.durationMs;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Evaluates duration for "sequence" mode: sum of children minus overlaps.
|
|
76
|
+
* Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.
|
|
77
|
+
* Fit-mode children are excluded to avoid circular dependencies.
|
|
78
|
+
*/
|
|
79
|
+
function evaluateSequenceDuration(timegroup, childTemporals, overlapMs) {
|
|
80
|
+
const cachedDuration = durationCache.get(timegroup);
|
|
81
|
+
if (cachedDuration !== void 0) return cachedDuration;
|
|
82
|
+
let duration = 0;
|
|
83
|
+
let participatingIndex = 0;
|
|
84
|
+
childTemporals.forEach((child) => {
|
|
85
|
+
if (!shouldParticipateInDurationCalculation(child)) return;
|
|
86
|
+
if (child instanceof EFTimegroup && durationCalculationInProgress.has(child)) return;
|
|
87
|
+
if (child instanceof EFTimegroup) {
|
|
88
|
+
let ancestor = child.parentNode;
|
|
89
|
+
let shouldSkip = false;
|
|
90
|
+
while (ancestor) {
|
|
91
|
+
if (ancestor === timegroup) break;
|
|
92
|
+
if (ancestor instanceof EFTimegroup && durationCalculationInProgress.has(ancestor)) {
|
|
93
|
+
shouldSkip = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
ancestor = ancestor.parentNode;
|
|
97
|
+
}
|
|
98
|
+
if (shouldSkip) return;
|
|
99
|
+
}
|
|
100
|
+
if (participatingIndex > 0) duration -= overlapMs;
|
|
101
|
+
duration += child.durationMs;
|
|
102
|
+
participatingIndex++;
|
|
103
|
+
});
|
|
104
|
+
duration = Math.max(0, duration);
|
|
105
|
+
durationCache.set(timegroup, duration);
|
|
106
|
+
return duration;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Evaluates duration for "contain" mode: maximum of children.
|
|
110
|
+
* Semantic rule: contain mode takes the maximum child duration.
|
|
111
|
+
* Fit-mode children and children without own duration are excluded.
|
|
112
|
+
*/
|
|
113
|
+
function evaluateContainDuration(timegroup, childTemporals) {
|
|
114
|
+
const cachedDuration = durationCache.get(timegroup);
|
|
115
|
+
if (cachedDuration !== void 0) return cachedDuration;
|
|
116
|
+
let maxDuration = 0;
|
|
117
|
+
for (const child of childTemporals) {
|
|
118
|
+
if (!shouldParticipateInDurationCalculation(child)) continue;
|
|
119
|
+
if (child instanceof EFTimegroup && durationCalculationInProgress.has(child)) continue;
|
|
120
|
+
if (child instanceof EFTimegroup) {
|
|
121
|
+
let ancestor = child.parentNode;
|
|
122
|
+
let shouldSkip = false;
|
|
123
|
+
while (ancestor) {
|
|
124
|
+
if (ancestor === timegroup) break;
|
|
125
|
+
if (ancestor instanceof EFTimegroup && durationCalculationInProgress.has(ancestor)) {
|
|
126
|
+
shouldSkip = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
ancestor = ancestor.parentNode;
|
|
130
|
+
}
|
|
131
|
+
if (shouldSkip) continue;
|
|
132
|
+
}
|
|
133
|
+
maxDuration = Math.max(maxDuration, child.durationMs);
|
|
134
|
+
}
|
|
135
|
+
const duration = Math.max(0, maxDuration);
|
|
136
|
+
durationCache.set(timegroup, duration);
|
|
137
|
+
return duration;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Evaluates duration based on timegroup mode.
|
|
141
|
+
* This is the semantic evaluation function - it determines what duration should be.
|
|
142
|
+
*
|
|
143
|
+
* Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs
|
|
144
|
+
* which requires the class context. The other modes are extracted for clarity.
|
|
145
|
+
*/
|
|
146
|
+
function evaluateDurationForMode(timegroup, mode, childTemporals) {
|
|
147
|
+
switch (mode) {
|
|
148
|
+
case "fit": return evaluateFitDuration(timegroup.parentTimegroup);
|
|
149
|
+
case "sequence":
|
|
150
|
+
durationCalculationInProgress.add(timegroup);
|
|
151
|
+
try {
|
|
152
|
+
return evaluateSequenceDuration(timegroup, childTemporals, timegroup.overlapMs);
|
|
153
|
+
} finally {
|
|
154
|
+
durationCalculationInProgress.delete(timegroup);
|
|
155
|
+
}
|
|
156
|
+
case "contain":
|
|
157
|
+
durationCalculationInProgress.add(timegroup);
|
|
158
|
+
try {
|
|
159
|
+
return evaluateContainDuration(timegroup, childTemporals);
|
|
160
|
+
} finally {
|
|
161
|
+
durationCalculationInProgress.delete(timegroup);
|
|
162
|
+
}
|
|
163
|
+
default: throw new Error(`Invalid time mode: ${mode}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
28
166
|
const shallowGetTimegroups = (element, groups = []) => {
|
|
29
167
|
for (const child of Array.from(element.children)) if (child instanceof EFTimegroup) groups.push(child);
|
|
30
168
|
else shallowGetTimegroups(child, groups);
|
|
31
169
|
return groups;
|
|
32
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Evaluates the target time for a seek operation.
|
|
173
|
+
* Applies quantization and clamping to determine the valid seek target.
|
|
174
|
+
*/
|
|
175
|
+
function evaluateSeekTarget(requestedTime, durationMs, fps) {
|
|
176
|
+
const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);
|
|
177
|
+
return Math.max(0, Math.min(quantizedTime, durationMs / 1e3));
|
|
178
|
+
}
|
|
33
179
|
let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
|
|
34
180
|
static {
|
|
35
181
|
_EFTimegroup = this;
|
|
@@ -41,47 +187,83 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
41
187
|
this.mode = "contain";
|
|
42
188
|
this.overlapMs = 0;
|
|
43
189
|
this.fps = 30;
|
|
190
|
+
this.autoInit = false;
|
|
191
|
+
this.workbench = false;
|
|
44
192
|
this.fit = "none";
|
|
45
|
-
this.mediaDurationsPromise = void 0;
|
|
46
193
|
this.frameTask = new Task(this, {
|
|
47
|
-
autoRun:
|
|
194
|
+
autoRun: EF_INTERACTIVE,
|
|
48
195
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
|
|
49
|
-
|
|
50
|
-
|
|
196
|
+
onError: (error) => {
|
|
197
|
+
this.frameTask.taskComplete.catch(() => {});
|
|
198
|
+
if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
|
|
199
|
+
console.error("EFTimegroup frameTask error", error);
|
|
200
|
+
},
|
|
201
|
+
task: async ([ownCurrentTimeMs, currentTimeMs], { signal }) => {
|
|
202
|
+
const now = Date.now();
|
|
203
|
+
if (now - this.#timegroupFrameTaskLastReset > _EFTimegroup.TIMEGROUP_FRAME_TASK_RESET_MS) {
|
|
204
|
+
this.#timegroupFrameTaskCount = 0;
|
|
205
|
+
this.#timegroupFrameTaskLastReset = now;
|
|
206
|
+
}
|
|
207
|
+
this.#timegroupFrameTaskCount++;
|
|
208
|
+
if (this.#timegroupFrameTaskCount > _EFTimegroup.TIMEGROUP_FRAME_TASK_THRESHOLD) return;
|
|
209
|
+
signal?.throwIfAborted();
|
|
210
|
+
if (this.isRootTimegroup) return withSpan("timegroup.frameTask", {
|
|
51
211
|
timegroupId: this.id || "unknown",
|
|
52
212
|
ownCurrentTimeMs,
|
|
53
213
|
currentTimeMs
|
|
54
214
|
}, void 0, async () => {
|
|
55
|
-
await this.waitForFrameTasks();
|
|
215
|
+
await this.waitForFrameTasks(signal);
|
|
216
|
+
signal?.throwIfAborted();
|
|
56
217
|
await this.#executeCustomFrameTasks();
|
|
218
|
+
signal?.throwIfAborted();
|
|
57
219
|
updateAnimations(this);
|
|
58
220
|
});
|
|
59
|
-
else
|
|
221
|
+
else return this.#executeCustomFrameTasks();
|
|
60
222
|
}
|
|
61
223
|
});
|
|
62
224
|
this.seekTask = new Task(this, {
|
|
63
225
|
autoRun: false,
|
|
64
226
|
args: () => [this.#pendingSeekTime ?? this.#currentTime],
|
|
65
227
|
onComplete: () => {},
|
|
66
|
-
task: async ([targetTime]) => {
|
|
67
|
-
|
|
68
|
-
|
|
228
|
+
task: async ([targetTime], { signal }) => {
|
|
229
|
+
signal?.throwIfAborted();
|
|
230
|
+
if (this.playbackController) return this.playbackController.seekTask.taskComplete.then(() => {
|
|
231
|
+
signal?.throwIfAborted();
|
|
69
232
|
return this.currentTime;
|
|
70
|
-
}
|
|
233
|
+
});
|
|
71
234
|
if (!this.isRootTimegroup) return;
|
|
72
235
|
return withSpan("timegroup.seekTask", {
|
|
73
236
|
timegroupId: this.id || "unknown",
|
|
74
237
|
targetTime: targetTime ?? 0,
|
|
75
238
|
durationMs: this.durationMs
|
|
76
239
|
}, void 0, async (span) => {
|
|
77
|
-
|
|
78
|
-
|
|
240
|
+
try {
|
|
241
|
+
await Promise.race([this.waitForMediaDurations(signal), new Promise((_, reject) => {
|
|
242
|
+
if (signal?.aborted) {
|
|
243
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error("waitForMediaDurations timeout")), 1e4);
|
|
247
|
+
signal?.addEventListener("abort", () => {
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
250
|
+
});
|
|
251
|
+
})]);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
254
|
+
}
|
|
255
|
+
signal?.throwIfAborted();
|
|
256
|
+
const newTime = evaluateSeekTarget(targetTime ?? 0, this.durationMs, this.effectiveFps);
|
|
79
257
|
if (isTracingEnabled()) span.setAttribute("newTime", newTime);
|
|
80
258
|
this.#currentTime = newTime;
|
|
81
259
|
this.requestUpdate("currentTime");
|
|
82
|
-
await this.
|
|
83
|
-
|
|
260
|
+
await this.updateComplete;
|
|
261
|
+
signal?.throwIfAborted();
|
|
262
|
+
await this.#runThrottledFrameTask();
|
|
263
|
+
signal?.throwIfAborted();
|
|
264
|
+
if (!this.#restoringFromLocalStorage) this.saveTimeToLocalStorage(this.#currentTime);
|
|
84
265
|
this.#seekInProgress = false;
|
|
266
|
+
if (this.#restoringFromLocalStorage) this.#restoringFromLocalStorage = false;
|
|
85
267
|
return newTime;
|
|
86
268
|
});
|
|
87
269
|
}
|
|
@@ -94,7 +276,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
94
276
|
"overlap",
|
|
95
277
|
"currenttime",
|
|
96
278
|
"fit",
|
|
97
|
-
"fps"
|
|
279
|
+
"fps",
|
|
280
|
+
"auto-init",
|
|
281
|
+
"workbench"
|
|
98
282
|
];
|
|
99
283
|
}
|
|
100
284
|
static {
|
|
@@ -118,59 +302,87 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
118
302
|
attributeChangedCallback(name, old, value) {
|
|
119
303
|
if (name === "mode" && value) this.mode = value;
|
|
120
304
|
if (name === "overlap" && value) this.overlapMs = parseTimeToMs(value);
|
|
305
|
+
if (name === "auto-init") this.autoInit = value !== null;
|
|
121
306
|
if (name === "fps" && value) this.fps = Number.parseFloat(value);
|
|
307
|
+
if (name === "workbench") this.workbench = value !== null;
|
|
122
308
|
super.attributeChangedCallback(name, old, value);
|
|
123
309
|
}
|
|
124
310
|
#resizeObserver;
|
|
311
|
+
/** Content epoch - increments when visual content changes (used by thumbnail cache) */
|
|
312
|
+
#contentEpoch = 0;
|
|
125
313
|
#currentTime = void 0;
|
|
314
|
+
#userTimeMs = 0;
|
|
126
315
|
#seekInProgress = false;
|
|
127
316
|
#pendingSeekTime;
|
|
128
317
|
#processingPendingSeek = false;
|
|
318
|
+
#restoringFromLocalStorage = false;
|
|
319
|
+
/** @internal */
|
|
320
|
+
isRestoringFromLocalStorage() {
|
|
321
|
+
return this.#restoringFromLocalStorage;
|
|
322
|
+
}
|
|
323
|
+
/** @internal - Used by PlaybackController to set restoration state */
|
|
324
|
+
setRestoringFromLocalStorage(value) {
|
|
325
|
+
this.#restoringFromLocalStorage = value;
|
|
326
|
+
}
|
|
129
327
|
#customFrameTasks = /* @__PURE__ */ new Set();
|
|
328
|
+
#onFrameCallback = null;
|
|
329
|
+
#onFrameCleanup = null;
|
|
330
|
+
#playbackListener = null;
|
|
130
331
|
/**
|
|
131
332
|
* Get the effective FPS for this timegroup.
|
|
132
333
|
* During rendering, uses the render options FPS if available.
|
|
133
334
|
* Otherwise uses the configured fps property.
|
|
335
|
+
* @public
|
|
134
336
|
*/
|
|
135
337
|
get effectiveFps() {
|
|
136
338
|
if (typeof window !== "undefined" && window.EF_FRAMEGEN?.renderOptions) return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;
|
|
137
339
|
return this.fps;
|
|
138
340
|
}
|
|
139
341
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* @
|
|
342
|
+
* Get the current content epoch (used by thumbnail cache).
|
|
343
|
+
* The epoch increments whenever visual content changes.
|
|
344
|
+
* @public
|
|
143
345
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!fps || fps <= 0) return timeSeconds;
|
|
147
|
-
const frameDurationS = 1 / fps;
|
|
148
|
-
return Math.round(timeSeconds / frameDurationS) * frameDurationS;
|
|
346
|
+
get contentEpoch() {
|
|
347
|
+
return this.#contentEpoch;
|
|
149
348
|
}
|
|
150
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Increment content epoch (called when visual content changes).
|
|
351
|
+
* This invalidates cached thumbnails by changing their cache keys.
|
|
352
|
+
* @public
|
|
353
|
+
*/
|
|
354
|
+
incrementContentEpoch() {
|
|
355
|
+
this.#contentEpoch++;
|
|
356
|
+
}
|
|
357
|
+
async #runThrottledFrameTask() {
|
|
151
358
|
if (this.playbackController) return this.playbackController.runThrottledFrameTask();
|
|
152
359
|
await this.frameTask.run();
|
|
153
360
|
}
|
|
361
|
+
/** @public */
|
|
154
362
|
set currentTime(time) {
|
|
155
|
-
|
|
363
|
+
const seekTarget = evaluateSeekTarget(time, this.durationMs, this.effectiveFps);
|
|
156
364
|
if (this.playbackController) {
|
|
157
|
-
this.playbackController.currentTime =
|
|
365
|
+
this.playbackController.currentTime = seekTarget;
|
|
366
|
+
this.#userTimeMs = seekTarget * 1e3;
|
|
158
367
|
return;
|
|
159
368
|
}
|
|
160
|
-
time = Math.max(0, Math.min(this.durationMs / 1e3, time));
|
|
161
369
|
if (!this.isRootTimegroup) return;
|
|
162
|
-
if (Number.isNaN(
|
|
163
|
-
if (
|
|
164
|
-
if (this.#pendingSeekTime ===
|
|
370
|
+
if (Number.isNaN(seekTarget)) return;
|
|
371
|
+
if (seekTarget === this.#currentTime && !this.#processingPendingSeek && !this.#restoringFromLocalStorage) return;
|
|
372
|
+
if (this.#pendingSeekTime === seekTarget) return;
|
|
373
|
+
if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {}
|
|
165
374
|
if (this.#seekInProgress) {
|
|
166
|
-
this.#pendingSeekTime =
|
|
167
|
-
this.#currentTime =
|
|
375
|
+
this.#pendingSeekTime = seekTarget;
|
|
376
|
+
this.#currentTime = seekTarget;
|
|
377
|
+
this.#userTimeMs = seekTarget * 1e3;
|
|
168
378
|
return;
|
|
169
379
|
}
|
|
170
|
-
this.#currentTime =
|
|
380
|
+
this.#currentTime = seekTarget;
|
|
381
|
+
this.#userTimeMs = seekTarget * 1e3;
|
|
171
382
|
this.#seekInProgress = true;
|
|
172
|
-
this.seekTask.run().finally(() => {
|
|
173
|
-
|
|
383
|
+
this.seekTask.run().catch(() => {}).finally(() => {
|
|
384
|
+
this.#seekInProgress = false;
|
|
385
|
+
if (this.#pendingSeekTime !== void 0 && this.#pendingSeekTime !== seekTarget) {
|
|
174
386
|
const pendingTime = this.#pendingSeekTime;
|
|
175
387
|
this.#pendingSeekTime = void 0;
|
|
176
388
|
this.#processingPendingSeek = true;
|
|
@@ -182,49 +394,141 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
182
394
|
} else this.#pendingSeekTime = void 0;
|
|
183
395
|
});
|
|
184
396
|
}
|
|
397
|
+
/** @public */
|
|
185
398
|
get currentTime() {
|
|
186
399
|
if (this.playbackController) return this.playbackController.currentTime;
|
|
187
400
|
return this.#currentTime ?? 0;
|
|
188
401
|
}
|
|
402
|
+
/** @public */
|
|
189
403
|
set currentTimeMs(ms) {
|
|
190
404
|
this.currentTime = ms / 1e3;
|
|
191
405
|
}
|
|
406
|
+
/** @public */
|
|
192
407
|
get currentTimeMs() {
|
|
193
408
|
return this.currentTime * 1e3;
|
|
194
409
|
}
|
|
195
410
|
/**
|
|
411
|
+
* The time the user last requested via seek/scrub.
|
|
412
|
+
* Preview systems should use this instead of currentTimeMs to avoid
|
|
413
|
+
* seeing intermediate times during batch operations (thumbnails, export).
|
|
414
|
+
* @public
|
|
415
|
+
*/
|
|
416
|
+
get userTimeMs() {
|
|
417
|
+
return this.#userTimeMs;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
196
420
|
* Seek to a specific time and wait for all frames to be ready.
|
|
197
421
|
* This is the recommended way to seek in tests and programmatic control.
|
|
198
422
|
*
|
|
423
|
+
* Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure
|
|
424
|
+
* all visible elements are ready after the seek completes.
|
|
425
|
+
*
|
|
426
|
+
* Updates both the source time AND userTimeMs (what the preview displays).
|
|
427
|
+
*
|
|
199
428
|
* @param timeMs - Time in milliseconds to seek to
|
|
200
429
|
* @returns Promise that resolves when the seek is complete and all visible children are ready
|
|
430
|
+
* @public
|
|
201
431
|
*/
|
|
202
432
|
async seek(timeMs) {
|
|
433
|
+
this.#userTimeMs = timeMs;
|
|
203
434
|
this.currentTimeMs = timeMs;
|
|
204
435
|
await this.seekTask.taskComplete;
|
|
205
436
|
if (this.playbackController) this.saveTimeToLocalStorage(this.currentTime);
|
|
206
437
|
await this.frameTask.taskComplete;
|
|
207
|
-
const visibleElements =
|
|
208
|
-
return evaluateAnimationVisibilityState(element).isVisible;
|
|
209
|
-
});
|
|
438
|
+
const visibleElements = this.#evaluateVisibleElementsForFrame();
|
|
210
439
|
await Promise.all(visibleElements.map(async (element) => {
|
|
211
440
|
if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") await element.waitForFrameReady();
|
|
212
441
|
else await element.updateComplete;
|
|
213
442
|
}));
|
|
214
443
|
}
|
|
215
444
|
/**
|
|
445
|
+
* Optimized seek for render loops.
|
|
446
|
+
* Unlike `seek()`, this:
|
|
447
|
+
* - Skips waitForMediaDurations (already loaded at render setup)
|
|
448
|
+
* - Skips localStorage persistence
|
|
449
|
+
* - Consolidates awaits to reduce event loop yields
|
|
450
|
+
*
|
|
451
|
+
* Still waits for all content to be ready (Lit updates, frame tasks, video frames).
|
|
452
|
+
*
|
|
453
|
+
* @param timeMs - Time in milliseconds to seek to
|
|
454
|
+
* @internal
|
|
455
|
+
*/
|
|
456
|
+
async seekForRender(timeMs) {
|
|
457
|
+
const newTime = timeMs / 1e3;
|
|
458
|
+
this.#userTimeMs = timeMs;
|
|
459
|
+
this.#currentTime = newTime;
|
|
460
|
+
this.requestUpdate("currentTime");
|
|
461
|
+
await this.updateComplete;
|
|
462
|
+
const allLitElements = this.#getAllLitElementDescendants();
|
|
463
|
+
await Promise.all(allLitElements.map((el) => el.updateComplete));
|
|
464
|
+
const textElements = allLitElements.filter((el) => el.tagName === "EF-TEXT");
|
|
465
|
+
if (textElements.length > 0) await Promise.all(textElements.map((el) => {
|
|
466
|
+
if ("whenSegmentsReady" in el && typeof el.whenSegmentsReady === "function") return el.whenSegmentsReady();
|
|
467
|
+
return Promise.resolve();
|
|
468
|
+
}));
|
|
469
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
470
|
+
const visibleElements = this.#evaluateVisibleElementsForFrame();
|
|
471
|
+
await Promise.all([this.frameTask.run(), ...visibleElements.map((element) => {
|
|
472
|
+
if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") return element.waitForFrameReady();
|
|
473
|
+
else return element.updateComplete;
|
|
474
|
+
})]);
|
|
475
|
+
this.offsetWidth;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Collects all LitElement descendants recursively.
|
|
479
|
+
* Used by seekForRender to ensure all reactive elements have updated.
|
|
480
|
+
*/
|
|
481
|
+
#getAllLitElementDescendants() {
|
|
482
|
+
const result = [];
|
|
483
|
+
const walk = (el) => {
|
|
484
|
+
for (const child of el.children) {
|
|
485
|
+
if (child instanceof LitElement) result.push(child);
|
|
486
|
+
walk(child);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
walk(this);
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
216
493
|
* Determines if this is a root timegroup (no parent timegroups)
|
|
494
|
+
* @public
|
|
217
495
|
*/
|
|
218
496
|
get isRootTimegroup() {
|
|
219
497
|
return !this.parentTimegroup;
|
|
220
498
|
}
|
|
221
499
|
/**
|
|
500
|
+
* Property-based frame task callback for React integration.
|
|
501
|
+
* When set, automatically registers the callback as a frame task.
|
|
502
|
+
* Setting a new value automatically cleans up the previous callback.
|
|
503
|
+
* Set to null or undefined to remove the callback.
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* // React usage:
|
|
507
|
+
* <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {
|
|
508
|
+
* // Per-frame updates
|
|
509
|
+
* }} />
|
|
510
|
+
*
|
|
511
|
+
* @public
|
|
512
|
+
*/
|
|
513
|
+
get onFrame() {
|
|
514
|
+
return this.#onFrameCallback;
|
|
515
|
+
}
|
|
516
|
+
set onFrame(callback) {
|
|
517
|
+
if (this.#onFrameCleanup) {
|
|
518
|
+
this.#onFrameCleanup();
|
|
519
|
+
this.#onFrameCleanup = null;
|
|
520
|
+
}
|
|
521
|
+
this.#onFrameCallback = callback ?? null;
|
|
522
|
+
if (callback) this.#onFrameCleanup = this.addFrameTask(callback);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
222
525
|
* Register a custom frame task callback that will be executed during frame rendering.
|
|
223
526
|
* The callback receives timing information and can be async or sync.
|
|
224
527
|
* Multiple callbacks can be registered and will execute in parallel.
|
|
225
528
|
*
|
|
226
529
|
* @param callback - Function to execute on each frame
|
|
227
530
|
* @returns A cleanup function that removes the callback when called
|
|
531
|
+
* @public
|
|
228
532
|
*/
|
|
229
533
|
addFrameTask(callback) {
|
|
230
534
|
if (typeof callback !== "function") throw new Error("Frame task callback must be a function");
|
|
@@ -237,10 +541,12 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
237
541
|
* Remove a previously registered custom frame task callback.
|
|
238
542
|
*
|
|
239
543
|
* @param callback - The callback function to remove
|
|
544
|
+
* @public
|
|
240
545
|
*/
|
|
241
546
|
removeFrameTask(callback) {
|
|
242
547
|
this.#customFrameTasks.delete(callback);
|
|
243
548
|
}
|
|
549
|
+
/** @internal */
|
|
244
550
|
saveTimeToLocalStorage(time) {
|
|
245
551
|
try {
|
|
246
552
|
if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
|
|
@@ -257,130 +563,397 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
257
563
|
flushStartTimeMsCache();
|
|
258
564
|
this.requestUpdate();
|
|
259
565
|
};
|
|
566
|
+
/** @internal */
|
|
260
567
|
loadTimeFromLocalStorage() {
|
|
261
568
|
if (this.id) try {
|
|
262
569
|
const storedValue = localStorage.getItem(this.storageKey);
|
|
263
570
|
if (storedValue === null) return;
|
|
264
|
-
|
|
571
|
+
const parsedValue = Number.parseFloat(storedValue);
|
|
572
|
+
if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) return;
|
|
573
|
+
return parsedValue;
|
|
265
574
|
} catch (error) {
|
|
266
575
|
log("Failed to load time from localStorage", error);
|
|
267
576
|
}
|
|
268
577
|
}
|
|
269
578
|
connectedCallback() {
|
|
270
579
|
super.connectedCallback();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
this.currentTime = maybeLoadedTime;
|
|
277
|
-
didLoadFromStorage = true;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
|
|
281
|
-
else if (didLoadFromStorage) await this.seekTask.run();
|
|
580
|
+
requestAnimationFrame(() => {
|
|
581
|
+
requestAnimationFrame(() => {
|
|
582
|
+
if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
|
|
583
|
+
if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
|
|
584
|
+
});
|
|
282
585
|
});
|
|
283
|
-
|
|
284
|
-
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Called when this timegroup becomes a root (no parent timegroup).
|
|
589
|
+
* Sets up the playback listener after PlaybackController is created.
|
|
590
|
+
* @internal
|
|
591
|
+
*/
|
|
592
|
+
didBecomeRoot() {
|
|
593
|
+
super.didBecomeRoot();
|
|
594
|
+
this.#setupPlaybackListener();
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Setup listener on playbackController to sync userTimeMs during playback.
|
|
598
|
+
*/
|
|
599
|
+
#setupPlaybackListener() {
|
|
600
|
+
if (this.#playbackListener || !this.playbackController) return;
|
|
601
|
+
this.#playbackListener = (event) => {
|
|
602
|
+
if (event.property === "currentTimeMs" && typeof event.value === "number") {
|
|
603
|
+
if (this.playing) this.#userTimeMs = event.value;
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
this.playbackController.addListener(this.#playbackListener);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Remove playback listener on disconnect.
|
|
610
|
+
*/
|
|
611
|
+
#removePlaybackListener() {
|
|
612
|
+
if (this.#playbackListener && this.playbackController) this.playbackController.removeListener(this.#playbackListener);
|
|
613
|
+
this.#playbackListener = null;
|
|
285
614
|
}
|
|
286
615
|
#previousDurationMs = 0;
|
|
287
616
|
updated(changedProperties) {
|
|
288
617
|
super.updated(changedProperties);
|
|
289
|
-
if (changedProperties.has("mode") || changedProperties.has("overlapMs"))
|
|
618
|
+
if (changedProperties.has("mode") || changedProperties.has("overlapMs")) durationCache.delete(this);
|
|
290
619
|
if (this.#previousDurationMs !== this.durationMs) {
|
|
291
620
|
this.#previousDurationMs = this.durationMs;
|
|
292
|
-
this
|
|
621
|
+
this.#runThrottledFrameTask();
|
|
293
622
|
}
|
|
294
623
|
}
|
|
295
624
|
disconnectedCallback() {
|
|
296
625
|
super.disconnectedCallback();
|
|
297
626
|
this.#resizeObserver?.disconnect();
|
|
627
|
+
this.#removePlaybackListener();
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Capture the timegroup at a specific timestamp as a canvas.
|
|
631
|
+
* Does NOT modify currentTimeMs - captures are rendered independently.
|
|
632
|
+
*
|
|
633
|
+
* @param options - Capture options including timeMs, scale, contentReadyMode
|
|
634
|
+
* @returns Promise resolving to an HTMLCanvasElement with the captured frame
|
|
635
|
+
* @public
|
|
636
|
+
*/
|
|
637
|
+
async captureAtTime(options) {
|
|
638
|
+
return captureTimegroupAtTime(this, options);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Capture multiple timestamps as canvas thumbnails in a single batch.
|
|
642
|
+
*
|
|
643
|
+
* CLONE-TIMELINE ARCHITECTURE:
|
|
644
|
+
* Creates a single render clone and reuses it across all captures.
|
|
645
|
+
* Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.
|
|
646
|
+
*
|
|
647
|
+
* @param timestamps - Array of timestamps (in milliseconds) to capture
|
|
648
|
+
* @param options - Capture options (scale, contentReadyMode, blockingTimeoutMs)
|
|
649
|
+
* @returns Promise resolving to array of HTMLCanvasElements
|
|
650
|
+
* @public
|
|
651
|
+
*/
|
|
652
|
+
async captureBatch(timestamps, options = {}) {
|
|
653
|
+
if (timestamps.length === 0) return [];
|
|
654
|
+
const { scale = .25, contentReadyMode = "immediate", blockingTimeoutMs = 5e3 } = options;
|
|
655
|
+
const batchStartTime = performance.now();
|
|
656
|
+
const cloneStartTime = performance.now();
|
|
657
|
+
const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } = await this.createRenderClone();
|
|
658
|
+
const cloneTime = performance.now() - cloneStartTime;
|
|
659
|
+
const prefetchStartTime = performance.now();
|
|
660
|
+
const videoElements = renderClone.querySelectorAll("ef-video");
|
|
661
|
+
if (videoElements.length > 0) await Promise.all(Array.from(videoElements).map((video) => video.prefetchScrubSegments(timestamps)));
|
|
662
|
+
const prefetchTime = performance.now() - prefetchStartTime;
|
|
663
|
+
const canvases = [];
|
|
664
|
+
let totalSeekTime = 0;
|
|
665
|
+
let totalCaptureTime = 0;
|
|
666
|
+
try {
|
|
667
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
668
|
+
const timeMs = timestamps[i];
|
|
669
|
+
const seekStart = performance.now();
|
|
670
|
+
await renderClone.seekForRender(timeMs);
|
|
671
|
+
totalSeekTime += performance.now() - seekStart;
|
|
672
|
+
const captureStart = performance.now();
|
|
673
|
+
const canvas = await captureFromClone(renderClone, renderContainer, {
|
|
674
|
+
scale,
|
|
675
|
+
contentReadyMode,
|
|
676
|
+
blockingTimeoutMs,
|
|
677
|
+
originalTimegroup: this
|
|
678
|
+
});
|
|
679
|
+
totalCaptureTime += performance.now() - captureStart;
|
|
680
|
+
canvases.push(canvas);
|
|
681
|
+
}
|
|
682
|
+
return canvases;
|
|
683
|
+
} finally {
|
|
684
|
+
const totalTime = performance.now() - batchStartTime;
|
|
685
|
+
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`);
|
|
686
|
+
cleanupRenderClone();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Render the timegroup to an MP4 video file and trigger download.
|
|
691
|
+
* Captures each frame at the specified fps, encodes using WebCodecs via
|
|
692
|
+
* MediaBunny, and downloads the resulting video.
|
|
693
|
+
*
|
|
694
|
+
* @param options - Rendering options (fps, codec, bitrate, filename, etc.)
|
|
695
|
+
* @returns Promise that resolves when video is downloaded
|
|
696
|
+
* @public
|
|
697
|
+
*/
|
|
698
|
+
async renderToVideo(options) {
|
|
699
|
+
return renderTimegroupToVideo(this, options);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Runs the initializer function with validation for synchronous execution and time budget.
|
|
703
|
+
* @throws Error if no initializer is set
|
|
704
|
+
* @throws Error if initializer returns a Promise (async not allowed)
|
|
705
|
+
* @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS
|
|
706
|
+
* @internal
|
|
707
|
+
*/
|
|
708
|
+
#runInitializer(cloneEl) {
|
|
709
|
+
if (!this.initializer) return;
|
|
710
|
+
const startTime = performance.now();
|
|
711
|
+
const result = this.initializer(cloneEl);
|
|
712
|
+
const elapsed = performance.now() - startTime;
|
|
713
|
+
if (result !== void 0 && result !== null && typeof result.then === "function") throw new Error("Timeline initializer must be synchronous. Do not return a Promise from the initializer function.");
|
|
714
|
+
if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) throw new Error(`Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. Initializers must be fast - move expensive work outside the initializer.`);
|
|
715
|
+
if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) console.warn(`[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. Consider optimizing for better render performance.`);
|
|
298
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Copy captionsData property from original to clone.
|
|
719
|
+
* cloneNode() only copies attributes, not JavaScript properties.
|
|
720
|
+
* captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),
|
|
721
|
+
* so we must manually copy it to the cloned elements.
|
|
722
|
+
* @internal
|
|
723
|
+
*/
|
|
724
|
+
#copyCaptionsData(original, clone) {
|
|
725
|
+
const originalCaptions = original.querySelectorAll("ef-captions");
|
|
726
|
+
const cloneCaptions = clone.querySelectorAll("ef-captions");
|
|
727
|
+
for (let i = 0; i < originalCaptions.length && i < cloneCaptions.length; i++) {
|
|
728
|
+
const origCap = originalCaptions[i];
|
|
729
|
+
const cloneCap = cloneCaptions[i];
|
|
730
|
+
if (origCap.captionsData) cloneCap.captionsData = origCap.captionsData;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Copy ef-text _textContent property from original to cloned elements.
|
|
735
|
+
* This MUST be called BEFORE elements upgrade (before updateComplete)
|
|
736
|
+
* because splitText() runs in connectedCallback and will clear segments
|
|
737
|
+
* if _textContent is null/empty.
|
|
738
|
+
* @internal
|
|
739
|
+
*/
|
|
740
|
+
#copyTextContent(original, clone) {
|
|
741
|
+
const originalTexts = original.querySelectorAll("ef-text");
|
|
742
|
+
const cloneTexts = clone.querySelectorAll("ef-text");
|
|
743
|
+
for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {
|
|
744
|
+
const origText = originalTexts[i];
|
|
745
|
+
const cloneText = cloneTexts[i];
|
|
746
|
+
if (origText._textContent !== void 0) cloneText._textContent = origText._textContent;
|
|
747
|
+
if (origText._templateElement !== void 0) cloneText._templateElement = origText._templateElement;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Copy ef-text-segment properties from original to cloned elements.
|
|
752
|
+
* segmentText and other properties are set via JS, not attributes,
|
|
753
|
+
* so we must manually copy them to the cloned elements.
|
|
754
|
+
* @internal
|
|
755
|
+
*/
|
|
756
|
+
#copyTextSegmentData(original, clone) {
|
|
757
|
+
const originalSegments = original.querySelectorAll("ef-text-segment");
|
|
758
|
+
const cloneSegments = clone.querySelectorAll("ef-text-segment");
|
|
759
|
+
for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {
|
|
760
|
+
const origSeg = originalSegments[i];
|
|
761
|
+
const cloneSeg = cloneSegments[i];
|
|
762
|
+
if (origSeg.segmentText !== void 0) cloneSeg.segmentText = origSeg.segmentText;
|
|
763
|
+
if (origSeg.segmentIndex !== void 0) cloneSeg.segmentIndex = origSeg.segmentIndex;
|
|
764
|
+
if (origSeg.staggerOffsetMs !== void 0) cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;
|
|
765
|
+
if (origSeg.segmentStartMs !== void 0) cloneSeg.segmentStartMs = origSeg.segmentStartMs;
|
|
766
|
+
if (origSeg.segmentEndMs !== void 0) cloneSeg.segmentEndMs = origSeg.segmentEndMs;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Wait for all ef-captions elements to have their data loaded.
|
|
771
|
+
* This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.
|
|
772
|
+
* Used by createRenderClone to ensure captions are ready before rendering.
|
|
773
|
+
* @internal
|
|
774
|
+
*/
|
|
775
|
+
async #waitForCaptionsData(root) {
|
|
776
|
+
const captionsElements = root.querySelectorAll("ef-captions");
|
|
777
|
+
if (captionsElements.length === 0) return;
|
|
778
|
+
const waitPromises = [];
|
|
779
|
+
for (const el of captionsElements) {
|
|
780
|
+
const task = el.unifiedCaptionsDataTask;
|
|
781
|
+
if (!task) continue;
|
|
782
|
+
if (task.status === TaskStatus.COMPLETE || task.status === TaskStatus.ERROR) continue;
|
|
783
|
+
if (task.status === TaskStatus.INITIAL) task.run().catch(() => {});
|
|
784
|
+
if (task.taskComplete) waitPromises.push(task.taskComplete);
|
|
785
|
+
}
|
|
786
|
+
if (waitPromises.length > 0) await Promise.all(waitPromises);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Create an independent clone of this timegroup for rendering.
|
|
790
|
+
* The clone is a fully functional ef-timegroup with its own animations
|
|
791
|
+
* and time state, isolated from the original (Prime-timeline).
|
|
792
|
+
*
|
|
793
|
+
* OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`
|
|
794
|
+
* to re-run JavaScript setup (frame callbacks, React components) on each clone.
|
|
795
|
+
*
|
|
796
|
+
* This enables:
|
|
797
|
+
* - Rendering without affecting user's preview position
|
|
798
|
+
* - Concurrent renders with different clones
|
|
799
|
+
* - Re-running JavaScript setup on each clone (if initializer is provided)
|
|
800
|
+
*
|
|
801
|
+
* @returns Promise resolving to clone, container, and cleanup function
|
|
802
|
+
* @throws Error if initializer is async or takes too long
|
|
803
|
+
* @public
|
|
804
|
+
*/
|
|
805
|
+
async createRenderClone() {
|
|
806
|
+
const container = document.createElement("div");
|
|
807
|
+
container.className = "ef-render-clone-container";
|
|
808
|
+
container.style.cssText = `
|
|
809
|
+
position: fixed;
|
|
810
|
+
left: -9999px;
|
|
811
|
+
top: 0;
|
|
812
|
+
width: ${this.offsetWidth || 1920}px;
|
|
813
|
+
height: ${this.offsetHeight || 1080}px;
|
|
814
|
+
pointer-events: none;
|
|
815
|
+
overflow: hidden;
|
|
816
|
+
`;
|
|
817
|
+
const cloneEl = this.cloneNode(true);
|
|
818
|
+
cloneEl.removeAttribute("id");
|
|
819
|
+
this.#copyCaptionsData(this, cloneEl);
|
|
820
|
+
this.#copyTextContent(this, cloneEl);
|
|
821
|
+
const originalConfig = this.closest("ef-configuration");
|
|
822
|
+
if (originalConfig) {
|
|
823
|
+
const configClone = originalConfig.cloneNode(false);
|
|
824
|
+
configClone.appendChild(cloneEl);
|
|
825
|
+
container.appendChild(configClone);
|
|
826
|
+
} else container.appendChild(cloneEl);
|
|
827
|
+
document.body.appendChild(container);
|
|
828
|
+
await cloneEl.updateComplete;
|
|
829
|
+
this.#copyTextSegmentData(this, cloneEl);
|
|
830
|
+
this.#runInitializer(cloneEl);
|
|
831
|
+
let actualClone = container.querySelector("ef-timegroup");
|
|
832
|
+
if (!actualClone) throw new Error("No ef-timegroup found after initializer. Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).");
|
|
833
|
+
await customElements.whenDefined("ef-timegroup");
|
|
834
|
+
customElements.upgrade(container);
|
|
835
|
+
actualClone = container.querySelector("ef-timegroup");
|
|
836
|
+
if (!actualClone) throw new Error("ef-timegroup element lost after upgrade");
|
|
837
|
+
await actualClone.updateComplete;
|
|
838
|
+
const setupParentChildRelationships = (parent, root) => {
|
|
839
|
+
for (const child of parent.children) if (child.tagName === "EF-TIMEGROUP") {
|
|
840
|
+
const childTG = child;
|
|
841
|
+
childTG.parentTimegroup = parent;
|
|
842
|
+
childTG.rootTimegroup = root;
|
|
843
|
+
childTG.lockRootTimegroup();
|
|
844
|
+
setupParentChildRelationships(childTG, root);
|
|
845
|
+
} else if ("parentTimegroup" in child && "rootTimegroup" in child) {
|
|
846
|
+
const temporal = child;
|
|
847
|
+
temporal.parentTimegroup = parent;
|
|
848
|
+
temporal.rootTimegroup = root;
|
|
849
|
+
if ("lockRootTimegroup" in temporal && typeof temporal.lockRootTimegroup === "function") temporal.lockRootTimegroup();
|
|
850
|
+
} else if (child instanceof Element) setupParentChildRelationshipsInContainer(child, parent, root);
|
|
851
|
+
};
|
|
852
|
+
const setupParentChildRelationshipsInContainer = (container$1, nearestParentTG, root) => {
|
|
853
|
+
for (const child of container$1.children) if (child.tagName === "EF-TIMEGROUP") {
|
|
854
|
+
const childTG = child;
|
|
855
|
+
childTG.parentTimegroup = nearestParentTG;
|
|
856
|
+
childTG.rootTimegroup = root;
|
|
857
|
+
childTG.lockRootTimegroup();
|
|
858
|
+
setupParentChildRelationships(childTG, root);
|
|
859
|
+
} else if ("parentTimegroup" in child && "rootTimegroup" in child) {
|
|
860
|
+
const temporal = child;
|
|
861
|
+
temporal.parentTimegroup = nearestParentTG;
|
|
862
|
+
temporal.rootTimegroup = root;
|
|
863
|
+
if ("lockRootTimegroup" in temporal && typeof temporal.lockRootTimegroup === "function") temporal.lockRootTimegroup();
|
|
864
|
+
} else if (child instanceof Element) setupParentChildRelationshipsInContainer(child, nearestParentTG, root);
|
|
865
|
+
};
|
|
866
|
+
actualClone.rootTimegroup = actualClone;
|
|
867
|
+
setupParentChildRelationships(actualClone, actualClone);
|
|
868
|
+
await actualClone.updateComplete;
|
|
869
|
+
actualClone.rootTimegroup = actualClone;
|
|
870
|
+
actualClone.lockRootTimegroup();
|
|
871
|
+
const finalizeRootTimegroup = (el) => {
|
|
872
|
+
if ("rootTimegroup" in el && "lockRootTimegroup" in el) {
|
|
873
|
+
el.rootTimegroup = actualClone;
|
|
874
|
+
el.lockRootTimegroup();
|
|
875
|
+
}
|
|
876
|
+
for (const child of el.children) finalizeRootTimegroup(child);
|
|
877
|
+
};
|
|
878
|
+
finalizeRootTimegroup(actualClone);
|
|
879
|
+
await actualClone.waitForMediaDurations();
|
|
880
|
+
await this.#waitForCaptionsData(actualClone);
|
|
881
|
+
if (actualClone.playbackController) {
|
|
882
|
+
actualClone.playbackController.remove();
|
|
883
|
+
actualClone.playbackController = void 0;
|
|
884
|
+
}
|
|
885
|
+
await actualClone.seek(0);
|
|
886
|
+
return {
|
|
887
|
+
clone: actualClone,
|
|
888
|
+
container,
|
|
889
|
+
cleanup: () => {
|
|
890
|
+
container.remove();
|
|
891
|
+
const reactRoot = actualClone._reactRoot;
|
|
892
|
+
if (reactRoot) queueMicrotask(() => {
|
|
893
|
+
reactRoot.unmount();
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
/** @internal */
|
|
299
899
|
get storageKey() {
|
|
300
900
|
if (!this.id) throw new Error("Timegroup must have an id to use localStorage.");
|
|
301
901
|
return `ef-timegroup-${this.id}`;
|
|
302
902
|
}
|
|
903
|
+
/** @internal */
|
|
303
904
|
get intrinsicDurationMs() {
|
|
304
905
|
if (this.hasExplicitDuration) return this.explicitDurationMs;
|
|
305
906
|
}
|
|
907
|
+
/** @internal */
|
|
306
908
|
get hasOwnDuration() {
|
|
307
|
-
return this.mode
|
|
909
|
+
return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);
|
|
308
910
|
}
|
|
911
|
+
/** @public */
|
|
309
912
|
get durationMs() {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return this.parentTimegroup.durationMs;
|
|
314
|
-
case "fixed": return super.durationMs;
|
|
315
|
-
case "sequence": {
|
|
316
|
-
const cachedDuration = sequenceDurationCache.get(this);
|
|
317
|
-
if (cachedDuration !== void 0) return cachedDuration;
|
|
318
|
-
let duration = 0;
|
|
319
|
-
this.childTemporals.forEach((child, index) => {
|
|
320
|
-
if (child instanceof _EFTimegroup && child.mode === "fit") return;
|
|
321
|
-
if (index > 0) duration -= this.overlapMs;
|
|
322
|
-
duration += child.durationMs;
|
|
323
|
-
});
|
|
324
|
-
sequenceDurationCache.set(this, duration);
|
|
325
|
-
return duration;
|
|
326
|
-
}
|
|
327
|
-
case "contain": {
|
|
328
|
-
let maxDuration = 0;
|
|
329
|
-
for (const child of this.childTemporals) {
|
|
330
|
-
if (child instanceof _EFTimegroup && child.mode === "fit") continue;
|
|
331
|
-
if (!child.hasOwnDuration) continue;
|
|
332
|
-
maxDuration = Math.max(maxDuration, child.durationMs);
|
|
333
|
-
}
|
|
334
|
-
return maxDuration;
|
|
335
|
-
}
|
|
336
|
-
default: throw new Error(`Invalid time mode: ${this.mode}`);
|
|
337
|
-
}
|
|
913
|
+
if (this.mode === "fixed") return super.durationMs;
|
|
914
|
+
const childTemporalsAsElements = this.childTemporals;
|
|
915
|
+
return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);
|
|
338
916
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const startTimeMs = temporal.startTimeMs;
|
|
348
|
-
const endTimeMs = temporal.endTimeMs;
|
|
349
|
-
const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
|
|
350
|
-
const elementEndsAfterStart = temporal.tagName.toLowerCase() === "ef-timegroup" && !temporal.parentTimegroup ? endTimeMs >= timelineTimeMs : endTimeMs > timelineTimeMs;
|
|
351
|
-
return elementStartsBeforeEnd && elementEndsAfterStart;
|
|
352
|
-
}).map((temporal) => temporal.frameTask);
|
|
353
|
-
frameTasks.forEach((task) => {
|
|
354
|
-
task.run();
|
|
917
|
+
/**
|
|
918
|
+
* Evaluates which elements should be rendered in the current frame.
|
|
919
|
+
* Filters to only include temporally visible elements for frame processing.
|
|
920
|
+
* Uses animation-friendly visibility to prevent animation jumps at exact boundaries.
|
|
921
|
+
*/
|
|
922
|
+
#evaluateVisibleElementsForFrame() {
|
|
923
|
+
return deepGetElementsWithFrameTasks(this).filter((element) => {
|
|
924
|
+
return evaluateAnimationVisibilityState(element).isVisible;
|
|
355
925
|
});
|
|
356
|
-
return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);
|
|
357
|
-
}
|
|
358
|
-
async waitForNestedUpdates(signal) {
|
|
359
|
-
const limit = 10;
|
|
360
|
-
let steps = 0;
|
|
361
|
-
let isComplete = true;
|
|
362
|
-
while (true) {
|
|
363
|
-
steps++;
|
|
364
|
-
if (steps > limit) throw new Error("Reached update depth limit.");
|
|
365
|
-
isComplete = await this.updateComplete;
|
|
366
|
-
signal?.throwIfAborted();
|
|
367
|
-
if (isComplete) break;
|
|
368
|
-
}
|
|
369
926
|
}
|
|
370
|
-
|
|
927
|
+
/** @internal */
|
|
928
|
+
async waitForFrameTasks(signal) {
|
|
371
929
|
return await withSpan("timegroup.waitForFrameTasks", {
|
|
372
930
|
timegroupId: this.id || "unknown",
|
|
373
931
|
mode: this.mode
|
|
374
932
|
}, void 0, async (span) => {
|
|
933
|
+
signal?.throwIfAborted();
|
|
375
934
|
const innerStart = performance.now();
|
|
376
935
|
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
377
936
|
if (isTracingEnabled()) span.setAttribute("temporalElementsCount", temporalElements.length);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
});
|
|
937
|
+
signal?.throwIfAborted();
|
|
938
|
+
const visibleElements = this.#evaluateVisibleElementsForFrame();
|
|
381
939
|
if (isTracingEnabled()) span.setAttribute("visibleElementsCount", visibleElements.length);
|
|
382
940
|
const promiseStart = performance.now();
|
|
383
|
-
await Promise.all(visibleElements.map((element) =>
|
|
941
|
+
await Promise.all(visibleElements.map(async (element) => {
|
|
942
|
+
signal?.throwIfAborted();
|
|
943
|
+
try {
|
|
944
|
+
if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") await element.waitForFrameReady();
|
|
945
|
+
else {
|
|
946
|
+
await element.updateComplete;
|
|
947
|
+
await element.frameTask.run();
|
|
948
|
+
}
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) {
|
|
951
|
+
signal?.throwIfAborted();
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
}));
|
|
384
957
|
const promiseEnd = performance.now();
|
|
385
958
|
const innerEnd = performance.now();
|
|
386
959
|
if (isTracingEnabled()) {
|
|
@@ -389,9 +962,21 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
389
962
|
}
|
|
390
963
|
});
|
|
391
964
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
965
|
+
#mediaDurationsPromise = void 0;
|
|
966
|
+
/** @internal */
|
|
967
|
+
async waitForMediaDurations(signal) {
|
|
968
|
+
signal?.throwIfAborted();
|
|
969
|
+
if (!this.#mediaDurationsPromise) this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch((err) => {
|
|
970
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
971
|
+
this.#mediaDurationsPromise = void 0;
|
|
972
|
+
throw err;
|
|
973
|
+
}
|
|
974
|
+
console.error(`[EFTimegroup] waitForMediaDurations failed for ${this.id || "unnamed"}:`, err);
|
|
975
|
+
this.#mediaDurationsPromise = void 0;
|
|
976
|
+
throw err;
|
|
977
|
+
});
|
|
978
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
979
|
+
return this.#mediaDurationsPromise;
|
|
395
980
|
}
|
|
396
981
|
/**
|
|
397
982
|
* Wait for all media elements to load their initial segments.
|
|
@@ -399,25 +984,96 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
399
984
|
* that caused issues with constructing audio data. We had negative durations
|
|
400
985
|
* in calculations and it was not clear why.
|
|
401
986
|
*/
|
|
402
|
-
async #waitForMediaDurations() {
|
|
987
|
+
async #waitForMediaDurations(signal) {
|
|
403
988
|
return withSpan("timegroup.waitForMediaDurations", {
|
|
404
989
|
timegroupId: this.id || "unknown",
|
|
405
990
|
mode: this.mode
|
|
406
991
|
}, void 0, async (span) => {
|
|
407
|
-
|
|
992
|
+
signal?.throwIfAborted();
|
|
993
|
+
await new Promise((resolve, reject) => {
|
|
994
|
+
if (signal?.aborted) {
|
|
995
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const abortHandler = () => {
|
|
999
|
+
clearTimeout(timeoutId);
|
|
1000
|
+
cancelAnimationFrame(rafId2);
|
|
1001
|
+
cancelAnimationFrame(rafId1);
|
|
1002
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1003
|
+
};
|
|
1004
|
+
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1005
|
+
let rafId1;
|
|
1006
|
+
let rafId2;
|
|
1007
|
+
let timeoutId;
|
|
1008
|
+
rafId1 = requestAnimationFrame(() => {
|
|
1009
|
+
if (signal?.aborted) {
|
|
1010
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
rafId2 = requestAnimationFrame(() => {
|
|
1014
|
+
if (signal?.aborted) {
|
|
1015
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
timeoutId = setTimeout(() => {
|
|
1019
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
1020
|
+
resolve();
|
|
1021
|
+
}, 10);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
signal?.throwIfAborted();
|
|
408
1026
|
const mediaElements = deepGetMediaElements(this);
|
|
409
1027
|
if (isTracingEnabled()) span.setAttribute("mediaElementsCount", mediaElements.length);
|
|
410
|
-
|
|
1028
|
+
signal?.throwIfAborted();
|
|
1029
|
+
const mediaLoadStart = Date.now();
|
|
1030
|
+
const MEDIA_LOAD_TIMEOUT_MS = 3e4;
|
|
1031
|
+
const loadPromises = mediaElements.map(async (m, index) => {
|
|
1032
|
+
signal?.throwIfAborted();
|
|
1033
|
+
const elementStart = Date.now();
|
|
1034
|
+
try {
|
|
1035
|
+
const status = m.mediaEngineTask.status;
|
|
1036
|
+
if (status === TaskStatus.COMPLETE || status === TaskStatus.ERROR) return;
|
|
1037
|
+
if (status === TaskStatus.INITIAL) m.mediaEngineTask.run().catch(() => {});
|
|
1038
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1039
|
+
if (signal?.aborted) {
|
|
1040
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
const timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);
|
|
1044
|
+
signal?.addEventListener("abort", () => {
|
|
1045
|
+
clearTimeout(timeoutId);
|
|
1046
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1047
|
+
}, { once: true });
|
|
1048
|
+
});
|
|
1049
|
+
const taskPromise = m.mediaEngineTask.taskComplete;
|
|
1050
|
+
taskPromise.catch(() => {});
|
|
1051
|
+
await Promise.race([taskPromise, timeoutPromise]);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
1054
|
+
if (isTracingEnabled()) {
|
|
1055
|
+
const elementElapsed = Date.now() - elementStart;
|
|
1056
|
+
console.error(`[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`, error);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
const results = await Promise.allSettled(loadPromises);
|
|
1061
|
+
if (results.some((r) => r.status === "rejected" && r.reason instanceof DOMException && r.reason.name === "AbortError")) throw new DOMException("Aborted", "AbortError");
|
|
1062
|
+
const failures = results.filter((r) => r.status === "rejected");
|
|
1063
|
+
if (failures.length > 0 && isTracingEnabled()) {
|
|
1064
|
+
const mediaLoadElapsed = Date.now() - mediaLoadStart;
|
|
1065
|
+
console.warn(`[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`, failures.map((r) => r.status === "rejected" ? r.reason : null));
|
|
1066
|
+
}
|
|
411
1067
|
flushStartTimeMsCache();
|
|
412
1068
|
flushSequenceDurationCache();
|
|
413
|
-
this.requestUpdate("currentTime");
|
|
414
|
-
await this.updateComplete;
|
|
1069
|
+
setTimeout(() => this.requestUpdate("currentTime"), 0);
|
|
415
1070
|
});
|
|
416
1071
|
}
|
|
1072
|
+
/** @internal */
|
|
417
1073
|
get childTemporals() {
|
|
418
1074
|
return shallowGetTemporalElements(this);
|
|
419
1075
|
}
|
|
420
|
-
get contextProvider() {
|
|
1076
|
+
get #contextProvider() {
|
|
421
1077
|
let parent = this.parentNode;
|
|
422
1078
|
while (parent) {
|
|
423
1079
|
if (isContextMixin(parent)) return parent;
|
|
@@ -430,34 +1086,59 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
430
1086
|
*
|
|
431
1087
|
* A timegroup should be wrapped with a workbench if:
|
|
432
1088
|
* - It's being rendered (EF_RENDERING), OR
|
|
433
|
-
* -
|
|
1089
|
+
* - The workbench property is set to true
|
|
434
1090
|
*
|
|
435
1091
|
* If the timegroup is already wrapped in a context provider like ef-preview,
|
|
436
1092
|
* it should NOT be wrapped in a workbench.
|
|
1093
|
+
* @internal
|
|
437
1094
|
*/
|
|
438
1095
|
shouldWrapWithWorkbench() {
|
|
439
|
-
if (
|
|
440
|
-
if (
|
|
441
|
-
|
|
1096
|
+
if (!this.isRootTimegroup) return false;
|
|
1097
|
+
if (this.closest("ef-canvas") !== null) return false;
|
|
1098
|
+
if (this.closest("ef-preview") !== null || this.closest("ef-workbench") !== null || this.closest("ef-preview-context") !== null) return false;
|
|
1099
|
+
if (this.closest("test-context") !== null) return false;
|
|
1100
|
+
if (this.closest(".ef-render-clone-container") !== null) return false;
|
|
1101
|
+
if (EF_RENDERING?.() === true) return true;
|
|
1102
|
+
return this.workbench;
|
|
442
1103
|
}
|
|
1104
|
+
/** @internal */
|
|
443
1105
|
wrapWithWorkbench() {
|
|
444
1106
|
const workbench = document.createElement("ef-workbench");
|
|
445
1107
|
this.parentElement?.append(workbench);
|
|
446
|
-
if (!this.hasAttribute("id")) this.setAttribute("id", "root-
|
|
447
|
-
|
|
448
|
-
workbench
|
|
1108
|
+
if (!this.hasAttribute("id")) this.setAttribute("id", "root-timegroup");
|
|
1109
|
+
const panZoom = document.createElement("ef-pan-zoom");
|
|
1110
|
+
panZoom.id = "workbench-panzoom";
|
|
1111
|
+
panZoom.setAttribute("slot", "canvas");
|
|
1112
|
+
panZoom.setAttribute("auto-fit", "");
|
|
1113
|
+
panZoom.style.width = "100%";
|
|
1114
|
+
panZoom.style.height = "100%";
|
|
1115
|
+
const rect = this.getBoundingClientRect();
|
|
1116
|
+
const canvas = document.createElement("ef-canvas");
|
|
1117
|
+
canvas.id = "workbench-canvas";
|
|
1118
|
+
canvas.style.width = `${Math.max(rect.width, 1920)}px`;
|
|
1119
|
+
canvas.style.height = `${Math.max(rect.height, 1080)}px`;
|
|
1120
|
+
canvas.style.display = "block";
|
|
1121
|
+
canvas.append(this);
|
|
1122
|
+
panZoom.append(canvas);
|
|
1123
|
+
workbench.append(panZoom);
|
|
1124
|
+
const hierarchy = document.createElement("ef-hierarchy");
|
|
1125
|
+
hierarchy.setAttribute("slot", "hierarchy");
|
|
1126
|
+
hierarchy.setAttribute("target", "workbench-canvas");
|
|
1127
|
+
hierarchy.setAttribute("header", "Scenes");
|
|
1128
|
+
workbench.append(hierarchy);
|
|
449
1129
|
const filmstrip = document.createElement("ef-filmstrip");
|
|
450
1130
|
filmstrip.setAttribute("slot", "timeline");
|
|
451
1131
|
filmstrip.setAttribute("target", this.id);
|
|
452
1132
|
workbench.append(filmstrip);
|
|
453
1133
|
}
|
|
454
|
-
get efElements() {
|
|
1134
|
+
get #efElements() {
|
|
455
1135
|
return Array.from(this.querySelectorAll("ef-audio, ef-video, ef-image, ef-captions, ef-waveform"));
|
|
456
1136
|
}
|
|
457
1137
|
/**
|
|
458
1138
|
* Returns media elements for playback audio rendering
|
|
459
1139
|
* For standalone media, returns [this]; for timegroups, returns all descendants
|
|
460
1140
|
* Used by PlaybackController for audio-driven playback
|
|
1141
|
+
* @internal
|
|
461
1142
|
*/
|
|
462
1143
|
getMediaElements() {
|
|
463
1144
|
return deepGetMediaElements(this);
|
|
@@ -466,15 +1147,16 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
466
1147
|
* Render audio buffer for playback
|
|
467
1148
|
* Called by PlaybackController during live playback
|
|
468
1149
|
* Delegates to shared renderTemporalAudio utility for consistent behavior
|
|
1150
|
+
* @internal
|
|
469
1151
|
*/
|
|
470
|
-
async renderAudio(fromMs, toMs) {
|
|
471
|
-
return renderTemporalAudio(this, fromMs, toMs);
|
|
1152
|
+
async renderAudio(fromMs, toMs, signal) {
|
|
1153
|
+
return renderTemporalAudio(this, fromMs, toMs, signal);
|
|
472
1154
|
}
|
|
473
1155
|
/**
|
|
474
1156
|
* TEMPORARY TEST METHOD: Renders audio and immediately plays it back
|
|
475
1157
|
* Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds
|
|
476
1158
|
*/
|
|
477
|
-
async testPlayAudio(fromMs, toMs) {
|
|
1159
|
+
async #testPlayAudio(fromMs, toMs) {
|
|
478
1160
|
const renderedBuffer = await this.renderAudio(fromMs, toMs);
|
|
479
1161
|
const playbackContext = new AudioContext();
|
|
480
1162
|
const bufferSource = playbackContext.createBufferSource();
|
|
@@ -488,13 +1170,13 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
488
1170
|
};
|
|
489
1171
|
});
|
|
490
1172
|
}
|
|
491
|
-
async loadMd5Sums() {
|
|
492
|
-
const efElements = this
|
|
1173
|
+
async #loadMd5Sums() {
|
|
1174
|
+
const efElements = this.#efElements;
|
|
493
1175
|
const loaderTasks = [];
|
|
494
1176
|
for (const el of efElements) {
|
|
495
1177
|
const md5SumLoader = el.md5SumLoader;
|
|
496
1178
|
if (md5SumLoader instanceof Task) {
|
|
497
|
-
md5SumLoader.run();
|
|
1179
|
+
md5SumLoader.run().catch(() => {});
|
|
498
1180
|
loaderTasks.push(md5SumLoader.taskComplete);
|
|
499
1181
|
}
|
|
500
1182
|
}
|
|
@@ -503,6 +1185,14 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
503
1185
|
if ("productionSrc" in el && el.productionSrc instanceof Function) el.setAttribute("src", el.productionSrc());
|
|
504
1186
|
});
|
|
505
1187
|
}
|
|
1188
|
+
#timegroupFrameTaskCount = 0;
|
|
1189
|
+
#timegroupFrameTaskLastReset = Date.now();
|
|
1190
|
+
static {
|
|
1191
|
+
this.TIMEGROUP_FRAME_TASK_THRESHOLD = 100;
|
|
1192
|
+
}
|
|
1193
|
+
static {
|
|
1194
|
+
this.TIMEGROUP_FRAME_TASK_RESET_MS = 1e3;
|
|
1195
|
+
}
|
|
506
1196
|
async #executeCustomFrameTasks() {
|
|
507
1197
|
if (this.#customFrameTasks.size > 0) {
|
|
508
1198
|
const percentComplete = this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;
|
|
@@ -516,10 +1206,41 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
516
1206
|
await Promise.all(Array.from(this.#customFrameTasks).map((callback) => Promise.resolve(callback(frameInfo))));
|
|
517
1207
|
}
|
|
518
1208
|
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get container information for this timegroup.
|
|
1211
|
+
* Timegroups are always containers and can contain children.
|
|
1212
|
+
* Display mode is determined from computed styles.
|
|
1213
|
+
*
|
|
1214
|
+
* @public
|
|
1215
|
+
*/
|
|
1216
|
+
getContainerInfo() {
|
|
1217
|
+
return {
|
|
1218
|
+
...getContainerInfoFromElement(this),
|
|
1219
|
+
isContainer: true,
|
|
1220
|
+
canContainChildren: true
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Get position information for this timegroup.
|
|
1225
|
+
* Returns computed bounds, transform, and rotation.
|
|
1226
|
+
*
|
|
1227
|
+
* @public
|
|
1228
|
+
*/
|
|
1229
|
+
getPositionInfo() {
|
|
1230
|
+
return getPositionInfoFromElement(this);
|
|
1231
|
+
}
|
|
519
1232
|
};
|
|
520
1233
|
__decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
|
|
521
1234
|
__decorate([provide({ context: efContext })], EFTimegroup.prototype, "efContext", void 0);
|
|
522
1235
|
__decorate([property({ type: Number })], EFTimegroup.prototype, "fps", void 0);
|
|
1236
|
+
__decorate([property({
|
|
1237
|
+
type: Boolean,
|
|
1238
|
+
attribute: "auto-init"
|
|
1239
|
+
})], EFTimegroup.prototype, "autoInit", void 0);
|
|
1240
|
+
__decorate([property({
|
|
1241
|
+
type: Boolean,
|
|
1242
|
+
reflect: true
|
|
1243
|
+
})], EFTimegroup.prototype, "workbench", void 0);
|
|
523
1244
|
__decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
|
|
524
1245
|
__decorate([property({
|
|
525
1246
|
type: Number,
|