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