@editframe/elements 0.37.3-beta → 0.38.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.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +7 -10
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +8 -4
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +4 -4
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- 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/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +3 -1
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.js +267 -145
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
- package/dist/preview/renderTimegroupToVideo.js +85 -105
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.js +1 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +21 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { RenderContext } from "./RenderContext.js";
|
|
2
|
+
import { DEFAULT_HEIGHT, DEFAULT_WIDTH } from "./previewTypes.js";
|
|
3
|
+
import { captureTimelineToDataUri } from "./rendering/serializeTimelineDirect.js";
|
|
4
|
+
import { getEffectiveRenderMode } from "./renderers.js";
|
|
5
|
+
import { loadImageFromDataUri } from "./rendering/loadImage.js";
|
|
6
|
+
import { renderToImageNative } from "./rendering/renderToImageNative.js";
|
|
7
|
+
|
|
8
|
+
//#region src/preview/renderElementToCanvas.ts
|
|
9
|
+
/**
|
|
10
|
+
* Render any element to canvas or image.
|
|
11
|
+
*
|
|
12
|
+
* This is a low-level rendering function that renders the element as-is.
|
|
13
|
+
* The caller is responsible for:
|
|
14
|
+
* - Creating clones if needed
|
|
15
|
+
* - Seeking to the correct time
|
|
16
|
+
* - Finding the correct element to render
|
|
17
|
+
*
|
|
18
|
+
* Use cases:
|
|
19
|
+
* - Preview: Pass prime timeline element (already at correct time)
|
|
20
|
+
* - Video/thumbnails: Pass element from reused clone (already seeked)
|
|
21
|
+
* - One-off capture: Create clone, seek, pass element, clean up
|
|
22
|
+
*
|
|
23
|
+
* @param element - Element to render (timegroup, temporal element, or plain DOM)
|
|
24
|
+
* @param options - Render options
|
|
25
|
+
* @returns Canvas or Image (both are CanvasImageSource)
|
|
26
|
+
*/
|
|
27
|
+
async function renderElementToCanvas(element, options) {
|
|
28
|
+
return await renderElementToImage(element, options);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Render an element using either native or foreignObject mode.
|
|
32
|
+
* Returns Canvas or Image directly without unnecessary copying.
|
|
33
|
+
*/
|
|
34
|
+
async function renderElementToImage(element, options) {
|
|
35
|
+
const { timeMs, scale = 1 } = options;
|
|
36
|
+
const computedStyle = getComputedStyle(element);
|
|
37
|
+
const width = options.width ?? (parseFloat(computedStyle.width) || DEFAULT_WIDTH);
|
|
38
|
+
const height = options.height ?? (parseFloat(computedStyle.height) || DEFAULT_HEIGHT);
|
|
39
|
+
const renderContext = options.renderContext ?? new RenderContext();
|
|
40
|
+
const shouldDisposeContext = !options.renderContext;
|
|
41
|
+
try {
|
|
42
|
+
if ((options.renderMode ?? getEffectiveRenderMode()) === "native") {
|
|
43
|
+
const elementContainer = document.createElement("div");
|
|
44
|
+
elementContainer.style.cssText = `
|
|
45
|
+
position: fixed;
|
|
46
|
+
left: 0;
|
|
47
|
+
top: 0;
|
|
48
|
+
width: ${width}px;
|
|
49
|
+
height: ${height}px;
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
`;
|
|
53
|
+
elementContainer.appendChild(element.cloneNode(true));
|
|
54
|
+
document.body.appendChild(elementContainer);
|
|
55
|
+
try {
|
|
56
|
+
return await renderToImageNative(elementContainer, width, height, { skipDprScaling: true });
|
|
57
|
+
} finally {
|
|
58
|
+
elementContainer.remove();
|
|
59
|
+
}
|
|
60
|
+
} else return await loadImageFromDataUri(await captureTimelineToDataUri(element, width, height, {
|
|
61
|
+
renderContext,
|
|
62
|
+
canvasScale: scale,
|
|
63
|
+
timeMs
|
|
64
|
+
}));
|
|
65
|
+
} finally {
|
|
66
|
+
if (shouldDisposeContext) renderContext.dispose();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { renderElementToCanvas };
|
|
72
|
+
//# sourceMappingURL=renderElementToCanvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderElementToCanvas.js","names":[],"sources":["../../src/preview/renderElementToCanvas.ts"],"sourcesContent":["/**\n * Render any DOM element to canvas.\n *\n * Low-level rendering function that renders elements as-is.\n * Supports both native (drawElementImage) and foreignObject render modes.\n *\n * Caller is responsible for clone management and seeking.\n */\n\nimport { getEffectiveRenderMode } from \"./renderers.js\";\nimport type { RenderMode } from \"./previewSettings.js\";\nimport { RenderContext } from \"./RenderContext.js\";\nimport { captureTimelineToDataUri } from \"./rendering/serializeTimelineDirect.js\";\nimport { loadImageFromDataUri } from \"./rendering/loadImage.js\";\nimport { renderToImageNative } from \"./rendering/renderToImageNative.js\";\nimport { DEFAULT_WIDTH, DEFAULT_HEIGHT } from \"./previewTypes.js\";\n\n/**\n * Options for rendering an element to canvas.\n */\nexport interface RenderElementOptions {\n /** Time to render at in milliseconds (used for serialization metadata) */\n timeMs: number;\n /** Scale factor for canvas encoding (default: 1.0) */\n scale?: number;\n /** Output width in pixels (defaults to element's computed width or 1920) */\n width?: number;\n /** Output height in pixels (defaults to element's computed height or 1080) */\n height?: number;\n /** Render context for canvas pixel caching */\n renderContext?: RenderContext;\n /** Override render mode (native or foreignObject) */\n renderMode?: RenderMode;\n}\n\n/**\n * Render any element to canvas or image.\n *\n * This is a low-level rendering function that renders the element as-is.\n * The caller is responsible for:\n * - Creating clones if needed\n * - Seeking to the correct time\n * - Finding the correct element to render\n *\n * Use cases:\n * - Preview: Pass prime timeline element (already at correct time)\n * - Video/thumbnails: Pass element from reused clone (already seeked)\n * - One-off capture: Create clone, seek, pass element, clean up\n *\n * @param element - Element to render (timegroup, temporal element, or plain DOM)\n * @param options - Render options\n * @returns Canvas or Image (both are CanvasImageSource)\n */\nexport async function renderElementToCanvas(\n element: Element,\n options: RenderElementOptions,\n): Promise<CanvasImageSource> {\n return await renderElementToImage(element, options);\n}\n\n/**\n * Render an element using either native or foreignObject mode.\n * Returns Canvas or Image directly without unnecessary copying.\n */\nasync function renderElementToImage(\n element: Element,\n options: RenderElementOptions,\n): Promise<CanvasImageSource> {\n const { timeMs, scale = 1.0 } = options;\n\n // Get element dimensions\n const computedStyle = getComputedStyle(element);\n const width =\n options.width ?? (parseFloat(computedStyle.width) || DEFAULT_WIDTH);\n const height =\n options.height ?? (parseFloat(computedStyle.height) || DEFAULT_HEIGHT);\n\n // Create render context for caching\n const renderContext = options.renderContext ?? new RenderContext();\n const shouldDisposeContext = !options.renderContext;\n\n try {\n // Determine render mode\n const renderMode = options.renderMode ?? getEffectiveRenderMode();\n\n if (renderMode === \"native\") {\n // NATIVE PATH: Render element using drawElementImage\n const elementContainer = document.createElement(\"div\");\n elementContainer.style.cssText = `\n position: fixed;\n left: 0;\n top: 0;\n width: ${width}px;\n height: ${height}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // Clone element into container\n elementContainer.appendChild(element.cloneNode(true));\n document.body.appendChild(elementContainer);\n\n try {\n // Return canvas directly - no copy needed!\n return await renderToImageNative(elementContainer, width, height, {\n skipDprScaling: true,\n });\n } finally {\n elementContainer.remove();\n }\n } else {\n // FOREIGNOBJECT PATH: Direct serialization\n const dataUri = await captureTimelineToDataUri(element, width, height, {\n renderContext,\n canvasScale: scale,\n timeMs,\n });\n\n // Return image directly - no copy needed!\n return await loadImageFromDataUri(dataUri);\n }\n } finally {\n if (shouldDisposeContext) {\n renderContext.dispose();\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,eAAsB,sBACpB,SACA,SAC4B;AAC5B,QAAO,MAAM,qBAAqB,SAAS,QAAQ;;;;;;AAOrD,eAAe,qBACb,SACA,SAC4B;CAC5B,MAAM,EAAE,QAAQ,QAAQ,MAAQ;CAGhC,MAAM,gBAAgB,iBAAiB,QAAQ;CAC/C,MAAM,QACJ,QAAQ,UAAU,WAAW,cAAc,MAAM,IAAI;CACvD,MAAM,SACJ,QAAQ,WAAW,WAAW,cAAc,OAAO,IAAI;CAGzD,MAAM,gBAAgB,QAAQ,iBAAiB,IAAI,eAAe;CAClE,MAAM,uBAAuB,CAAC,QAAQ;AAEtC,KAAI;AAIF,OAFmB,QAAQ,cAAc,wBAAwB,MAE9C,UAAU;GAE3B,MAAM,mBAAmB,SAAS,cAAc,MAAM;AACtD,oBAAiB,MAAM,UAAU;;;;iBAItB,MAAM;kBACL,OAAO;;;;AAMnB,oBAAiB,YAAY,QAAQ,UAAU,KAAK,CAAC;AACrD,YAAS,KAAK,YAAY,iBAAiB;AAE3C,OAAI;AAEF,WAAO,MAAM,oBAAoB,kBAAkB,OAAO,QAAQ,EAChE,gBAAgB,MACjB,CAAC;aACM;AACR,qBAAiB,QAAQ;;QAW3B,QAAO,MAAM,qBAPG,MAAM,yBAAyB,SAAS,OAAO,QAAQ;GACrE;GACA,aAAa;GACb;GACD,CAAC,CAGwC;WAEpC;AACR,MAAI,qBACF,eAAc,SAAS"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { FrameController } from "./FrameController.js";
|
|
2
|
+
import { updateAnimations } from "../elements/updateAnimations.js";
|
|
2
3
|
import { logger } from "./logger.js";
|
|
3
|
-
import { DEFAULT_BLOCKING_TIMEOUT_MS, DEFAULT_HEIGHT, DEFAULT_THUMBNAIL_SCALE, DEFAULT_WIDTH, createPreviewContainer, isVisibleAtTime } from "./previewTypes.js";
|
|
4
|
-
import { buildCloneStructure, collectDocumentStyles, overrideRootCloneStyles, removeHiddenNodesForSerialization, restoreHiddenNodes } from "./renderTimegroupPreview.js";
|
|
5
|
-
import { getEffectiveRenderMode } from "./renderers.js";
|
|
6
4
|
import { RenderContext } from "./RenderContext.js";
|
|
5
|
+
import { DEFAULT_BLOCKING_TIMEOUT_MS, DEFAULT_CAPTURE_SCALE, DEFAULT_HEIGHT, DEFAULT_WIDTH, isVisibleAtTime } from "./previewTypes.js";
|
|
6
|
+
import { captureTimelineToDataUri } from "./rendering/serializeTimelineDirect.js";
|
|
7
|
+
import { getRenderMode, isNativeCanvasApiAvailable } from "./previewSettings.js";
|
|
8
|
+
import { getEffectiveRenderMode } from "./renderers.js";
|
|
7
9
|
import { defaultProfiler } from "./RenderProfiler.js";
|
|
10
|
+
import { loadImageFromDataUri } from "./rendering/loadImage.js";
|
|
8
11
|
import { createDprCanvas, renderToImageNative } from "./rendering/renderToImageNative.js";
|
|
9
12
|
import { clearInlineImageCache } from "./rendering/inlineImages.js";
|
|
10
|
-
import { loadImageFromDataUri, renderToImage } from "./rendering/renderToImage.js";
|
|
11
13
|
|
|
12
14
|
//#region src/preview/renderTimegroupToCanvas.ts
|
|
13
15
|
/** Number of rows to sample when checking canvas content */
|
|
@@ -57,37 +59,34 @@ function resetRenderState() {
|
|
|
57
59
|
resetCacheMetrics();
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
|
-
*
|
|
61
|
-
|
|
62
|
-
function createDebugLabel() {
|
|
63
|
-
const debugLabel = document.createElement("div");
|
|
64
|
-
debugLabel.style.cssText = `
|
|
65
|
-
position: absolute;
|
|
66
|
-
top: -24px;
|
|
67
|
-
left: 0;
|
|
68
|
-
padding: 2px 8px;
|
|
69
|
-
font: bold 12px monospace;
|
|
70
|
-
background: rgba(0, 0, 0, 0.8);
|
|
71
|
-
border-radius: 3px;
|
|
72
|
-
white-space: nowrap;
|
|
73
|
-
z-index: 1000;
|
|
74
|
-
pointer-events: none;
|
|
75
|
-
`;
|
|
76
|
-
return debugLabel;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Update debug label with resolution info.
|
|
62
|
+
* DEBUG: Capture a single thumbnail at the current time.
|
|
63
|
+
* Call from console: window.debugCaptureThumbnail()
|
|
80
64
|
*/
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
if (typeof window !== "undefined") window.debugCaptureThumbnail = async function() {
|
|
66
|
+
const timegroup = document.querySelector("ef-timegroup");
|
|
67
|
+
if (!timegroup) {
|
|
68
|
+
console.error("No timegroup found");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const currentTime = timegroup.currentTimeMs ?? 0;
|
|
72
|
+
try {
|
|
73
|
+
const result = await captureTimegroupAtTime(timegroup, {
|
|
74
|
+
timeMs: currentTime,
|
|
75
|
+
scale: .25,
|
|
76
|
+
contentReadyMode: "blocking",
|
|
77
|
+
blockingTimeoutMs: 1e3
|
|
78
|
+
});
|
|
79
|
+
const img = document.createElement("img");
|
|
80
|
+
if (result instanceof HTMLCanvasElement) img.src = result.toDataURL();
|
|
81
|
+
else if (result instanceof HTMLImageElement) img.src = result.src;
|
|
82
|
+
img.style.cssText = "position:fixed;top:10px;right:10px;border:2px solid red;z-index:99999;";
|
|
83
|
+
document.body.appendChild(img);
|
|
84
|
+
return result;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error("[DEBUG] Capture failed:", err);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
91
90
|
/**
|
|
92
91
|
* Wait for next animation frame (allows browser to complete layout)
|
|
93
92
|
*/
|
|
@@ -99,7 +98,7 @@ function waitForFrame() {
|
|
|
99
98
|
* Returns true if there's ANY non-transparent pixel.
|
|
100
99
|
*/
|
|
101
100
|
function canvasHasContent(canvas) {
|
|
102
|
-
const ctx = canvas.getContext("2d");
|
|
101
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
103
102
|
if (!ctx) return false;
|
|
104
103
|
try {
|
|
105
104
|
const width = canvas.width;
|
|
@@ -167,105 +166,157 @@ async function waitForVideoContent(timegroup, timeMs, maxWaitMs) {
|
|
|
167
166
|
/**
|
|
168
167
|
* Captures a frame from an already-seeked render clone.
|
|
169
168
|
* Used internally by captureBatch for efficiency (reuses one clone across all captures).
|
|
170
|
-
*
|
|
169
|
+
*
|
|
171
170
|
* @param renderClone - A render clone that has already been seeked to the target time
|
|
172
171
|
* @param renderContainer - The container holding the render clone (from createRenderClone)
|
|
173
172
|
* @param options - Capture options
|
|
174
|
-
* @returns Canvas with the rendered frame
|
|
173
|
+
* @returns Canvas or Image with the rendered frame (both are CanvasImageSource)
|
|
175
174
|
*/
|
|
176
|
-
async function captureFromClone(renderClone,
|
|
177
|
-
const { scale =
|
|
175
|
+
async function captureFromClone(renderClone, _renderContainer, options = {}) {
|
|
176
|
+
const { scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, originalTimegroup, timeMs: explicitTimeMs, canvasMode } = options;
|
|
177
|
+
const timeMs = explicitTimeMs ?? renderClone.currentTimeMs;
|
|
178
178
|
const sourceForDimensions = originalTimegroup ?? renderClone;
|
|
179
179
|
const width = sourceForDimensions.offsetWidth || DEFAULT_WIDTH;
|
|
180
180
|
const height = sourceForDimensions.offsetHeight || DEFAULT_HEIGHT;
|
|
181
|
-
const dpr = window.devicePixelRatio || 1;
|
|
182
|
-
const canvas = document.createElement("canvas");
|
|
183
|
-
canvas.width = Math.floor(width * scale * dpr);
|
|
184
|
-
canvas.height = Math.floor(height * scale * dpr);
|
|
185
|
-
canvas.style.width = `${Math.floor(width * scale)}px`;
|
|
186
|
-
canvas.style.height = `${Math.floor(height * scale)}px`;
|
|
187
|
-
const ctx = canvas.getContext("2d");
|
|
188
|
-
if (!ctx) throw new Error("Failed to get canvas 2d context");
|
|
189
|
-
const timeMs = renderClone.currentTimeMs;
|
|
190
181
|
if (contentReadyMode === "blocking") {
|
|
191
182
|
const result = await waitForVideoContent(renderClone, timeMs, blockingTimeoutMs);
|
|
192
183
|
if (!result.ready) throw new ContentNotReadyError(timeMs, blockingTimeoutMs, result.blankVideos);
|
|
193
184
|
}
|
|
185
|
+
const effectiveCanvasMode = (() => {
|
|
186
|
+
if (!canvasMode) return "foreignObject";
|
|
187
|
+
if (canvasMode === "native" && !isNativeCanvasApiAvailable()) {
|
|
188
|
+
logger.debug("[captureFromClone] Native canvas mode requested but not available, falling back to foreignObject");
|
|
189
|
+
return "foreignObject";
|
|
190
|
+
}
|
|
191
|
+
return canvasMode;
|
|
192
|
+
})();
|
|
194
193
|
const renderContext = new RenderContext();
|
|
195
194
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
width: ${width}px;
|
|
203
|
-
height: ${height}px;
|
|
204
|
-
pointer-events: none;
|
|
205
|
-
overflow: hidden;
|
|
206
|
-
`;
|
|
207
|
-
image = await renderToImageNative(renderContainer, width, height, { skipDprScaling: true });
|
|
195
|
+
if (effectiveCanvasMode === "native") {
|
|
196
|
+
const t0 = performance.now();
|
|
197
|
+
const canvas = await renderToImageNative(renderClone, width, height, { skipDprScaling: true });
|
|
198
|
+
const renderTime = performance.now() - t0;
|
|
199
|
+
logger.debug(`[captureFromClone] native render=${renderTime.toFixed(0)}ms (canvasScale=${scale})`);
|
|
200
|
+
return canvas;
|
|
208
201
|
} else {
|
|
209
202
|
const t0 = performance.now();
|
|
210
|
-
const
|
|
211
|
-
const buildTime = performance.now() - t0;
|
|
212
|
-
const bgSource = originalTimegroup ?? renderClone;
|
|
213
|
-
const previewContainer = createPreviewContainer({
|
|
214
|
-
width,
|
|
215
|
-
height,
|
|
216
|
-
background: getComputedStyle(bgSource).background || "#000"
|
|
217
|
-
});
|
|
218
|
-
const t1 = performance.now();
|
|
219
|
-
const styleEl = document.createElement("style");
|
|
220
|
-
styleEl.textContent = collectDocumentStyles();
|
|
221
|
-
const stylesTime = performance.now() - t1;
|
|
222
|
-
previewContainer.appendChild(styleEl);
|
|
223
|
-
previewContainer.appendChild(container$1);
|
|
224
|
-
overrideRootCloneStyles(syncState, true);
|
|
225
|
-
const t2 = performance.now();
|
|
226
|
-
image = await renderToImage(previewContainer, width, height, {
|
|
227
|
-
canvasScale: scale,
|
|
203
|
+
const dataUri = await captureTimelineToDataUri(renderClone, width, height, {
|
|
228
204
|
renderContext,
|
|
229
|
-
|
|
205
|
+
canvasScale: scale,
|
|
206
|
+
timeMs
|
|
230
207
|
});
|
|
231
|
-
const
|
|
232
|
-
|
|
208
|
+
const serializeTime = performance.now() - t0;
|
|
209
|
+
const t1 = performance.now();
|
|
210
|
+
const image = await loadImageFromDataUri(dataUri);
|
|
211
|
+
const loadTime = performance.now() - t1;
|
|
212
|
+
logger.debug(`[captureFromClone] foreignObject serialize=${serializeTime.toFixed(0)}ms, load=${loadTime.toFixed(0)}ms (canvasScale=${scale})`);
|
|
213
|
+
return image;
|
|
233
214
|
}
|
|
234
|
-
const srcWidth = image.width;
|
|
235
|
-
const srcHeight = image.height;
|
|
236
|
-
ctx.drawImage(image, 0, 0, srcWidth, srcHeight, 0, 0, canvas.width, canvas.height);
|
|
237
|
-
return canvas;
|
|
238
215
|
} finally {
|
|
239
216
|
renderContext.dispose();
|
|
240
217
|
}
|
|
241
218
|
}
|
|
242
219
|
/**
|
|
243
220
|
* Captures a single frame from a timegroup at a specific time.
|
|
244
|
-
*
|
|
221
|
+
*
|
|
245
222
|
* CLONE-TIMELINE ARCHITECTURE:
|
|
246
223
|
* Creates an independent render clone, seeks it to the target time, and captures.
|
|
247
224
|
* Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.
|
|
248
|
-
*
|
|
225
|
+
*
|
|
249
226
|
* @param timegroup - The source timegroup
|
|
250
227
|
* @param options - Capture options including timeMs, scale, contentReadyMode
|
|
251
228
|
* @returns Canvas with the rendered frame
|
|
252
229
|
* @throws ContentNotReadyError if blocking mode times out waiting for video content
|
|
253
230
|
*/
|
|
254
231
|
async function captureTimegroupAtTime(timegroup, options) {
|
|
255
|
-
const { timeMs, scale =
|
|
232
|
+
const { timeMs, scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, canvasMode, skipClone = false } = options;
|
|
233
|
+
if (skipClone) {
|
|
234
|
+
const seekStart = performance.now();
|
|
235
|
+
await timegroup.seekForRender(timeMs);
|
|
236
|
+
const seekMs = performance.now() - seekStart;
|
|
237
|
+
const renderStart = performance.now();
|
|
238
|
+
const result = await captureFromClone(timegroup, timegroup.parentElement || document.body, {
|
|
239
|
+
scale,
|
|
240
|
+
contentReadyMode,
|
|
241
|
+
blockingTimeoutMs,
|
|
242
|
+
originalTimegroup: void 0,
|
|
243
|
+
canvasMode,
|
|
244
|
+
timeMs
|
|
245
|
+
});
|
|
246
|
+
const renderMs = performance.now() - renderStart;
|
|
247
|
+
if (typeof result === "object" && result !== null) result.__perfTiming = {
|
|
248
|
+
cloneMs: 0,
|
|
249
|
+
seekMs,
|
|
250
|
+
renderMs
|
|
251
|
+
};
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
const cloneStart = performance.now();
|
|
256
255
|
const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } = await timegroup.createRenderClone();
|
|
256
|
+
const cloneMs = performance.now() - cloneStart;
|
|
257
257
|
try {
|
|
258
|
+
const seekStart = performance.now();
|
|
258
259
|
await renderClone.seekForRender(timeMs);
|
|
259
|
-
|
|
260
|
+
const seekMs = performance.now() - seekStart;
|
|
261
|
+
const renderStart = performance.now();
|
|
262
|
+
const result = await captureFromClone(renderClone, renderContainer, {
|
|
260
263
|
scale,
|
|
261
264
|
contentReadyMode,
|
|
262
265
|
blockingTimeoutMs,
|
|
263
|
-
originalTimegroup: timegroup
|
|
266
|
+
originalTimegroup: timegroup,
|
|
267
|
+
canvasMode
|
|
264
268
|
});
|
|
269
|
+
const renderMs = performance.now() - renderStart;
|
|
270
|
+
if (typeof result === "object" && result !== null) result.__perfTiming = {
|
|
271
|
+
cloneMs,
|
|
272
|
+
seekMs,
|
|
273
|
+
renderMs
|
|
274
|
+
};
|
|
275
|
+
return result;
|
|
265
276
|
} finally {
|
|
266
277
|
cleanupRenderClone();
|
|
267
278
|
}
|
|
268
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Generate thumbnails using an existing render clone and mutable queue.
|
|
282
|
+
* The queue can be modified while generation is in progress.
|
|
283
|
+
*
|
|
284
|
+
* @param renderClone - Pre-created render clone to use
|
|
285
|
+
* @param renderContainer - Container for the render clone
|
|
286
|
+
* @param queue - Mutable queue that provides timestamps
|
|
287
|
+
* @param options - Capture options (scale, contentReadyMode, etc.)
|
|
288
|
+
* @yields Objects with { timeMs, canvas } for each captured thumbnail
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* const queue = new MutableTimestampQueue();
|
|
293
|
+
* queue.reset([0, 100, 200]);
|
|
294
|
+
*
|
|
295
|
+
* for await (const { timeMs, canvas } of generateThumbnailsFromClone(clone, container, queue)) {
|
|
296
|
+
* cache.set(timeMs, canvas);
|
|
297
|
+
* // Queue can be modified here while generator continues
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
async function* generateThumbnailsFromClone(renderClone, renderContainer, queue, options = {}) {
|
|
302
|
+
const { scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, signal } = options;
|
|
303
|
+
while (true) {
|
|
304
|
+
if (signal?.aborted) break;
|
|
305
|
+
const timeMs = queue.shift();
|
|
306
|
+
if (timeMs === void 0) break;
|
|
307
|
+
await renderClone.seekForRender(timeMs);
|
|
308
|
+
if (signal?.aborted) break;
|
|
309
|
+
yield {
|
|
310
|
+
timeMs,
|
|
311
|
+
canvas: await captureFromClone(renderClone, renderContainer, {
|
|
312
|
+
scale,
|
|
313
|
+
contentReadyMode,
|
|
314
|
+
blockingTimeoutMs,
|
|
315
|
+
timeMs
|
|
316
|
+
})
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
269
320
|
/** Epsilon for comparing time values (ms) - times within this are considered equal */
|
|
270
321
|
const TIME_EPSILON_MS = 1;
|
|
271
322
|
/** Default scale for preview rendering */
|
|
@@ -282,10 +333,10 @@ function toAbsoluteTime(timegroup, relativeTimeMs) {
|
|
|
282
333
|
}
|
|
283
334
|
/**
|
|
284
335
|
* Renders a timegroup preview to a canvas using SVG foreignObject.
|
|
285
|
-
*
|
|
336
|
+
*
|
|
286
337
|
* Captures the prime timeline's current visual state including DOM changes
|
|
287
338
|
* from frame tasks (SVG paths, canvas content, text updates, etc.).
|
|
288
|
-
*
|
|
339
|
+
*
|
|
289
340
|
* Optimized with:
|
|
290
341
|
* - Passive clone structure rebuilt each frame from prime's current state
|
|
291
342
|
* - Temporal bucketing for time-based culling
|
|
@@ -302,7 +353,7 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
302
353
|
let currentResolutionScale = options.resolutionScale ?? DEFAULT_RESOLUTION_SCALE;
|
|
303
354
|
const width = timegroup.offsetWidth || DEFAULT_WIDTH;
|
|
304
355
|
const height = timegroup.offsetHeight || DEFAULT_HEIGHT;
|
|
305
|
-
const dpr = window.devicePixelRatio || 1;
|
|
356
|
+
const dpr = (typeof window !== "undefined" ? window.devicePixelRatio : 1) || 1;
|
|
306
357
|
let renderWidth = Math.floor(width * currentResolutionScale);
|
|
307
358
|
let renderHeight = Math.floor(height * currentResolutionScale);
|
|
308
359
|
const canvas = createDprCanvas({
|
|
@@ -313,11 +364,7 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
313
364
|
fullHeight: height,
|
|
314
365
|
dpr
|
|
315
366
|
});
|
|
316
|
-
const wrapperContainer =
|
|
317
|
-
wrapperContainer.style.cssText = "position: relative; display: inline-block;";
|
|
318
|
-
const debugLabel = createDebugLabel();
|
|
319
|
-
wrapperContainer.appendChild(debugLabel);
|
|
320
|
-
wrapperContainer.appendChild(canvas);
|
|
367
|
+
const wrapperContainer = canvas;
|
|
321
368
|
const ctx = canvas.getContext("2d");
|
|
322
369
|
if (!ctx) throw new Error("Failed to get canvas 2d context");
|
|
323
370
|
let rendering = false;
|
|
@@ -325,16 +372,34 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
325
372
|
let disposed = false;
|
|
326
373
|
const renderContext = new RenderContext();
|
|
327
374
|
const frameController = new FrameController(timegroup);
|
|
328
|
-
const previewContainer = createPreviewContainer({
|
|
329
|
-
width: renderWidth,
|
|
330
|
-
height: renderHeight,
|
|
331
|
-
background: getComputedStyle(timegroup).background || "#000"
|
|
332
|
-
});
|
|
333
|
-
const styleEl = document.createElement("style");
|
|
334
|
-
styleEl.textContent = collectDocumentStyles();
|
|
335
|
-
previewContainer.appendChild(styleEl);
|
|
336
375
|
let hasLoggedScale = false;
|
|
337
376
|
let pendingResolutionScale = null;
|
|
377
|
+
const useNative = getRenderMode() === "native" && isNativeCanvasApiAvailable();
|
|
378
|
+
let captureCanvas = null;
|
|
379
|
+
let captureCtx = null;
|
|
380
|
+
let originalParent = null;
|
|
381
|
+
let originalNextSibling = null;
|
|
382
|
+
let savedClipPath = "";
|
|
383
|
+
let savedPointerEvents = "";
|
|
384
|
+
if (useNative) {
|
|
385
|
+
captureCanvas = document.createElement("canvas");
|
|
386
|
+
captureCanvas.setAttribute("layoutsubtree", "");
|
|
387
|
+
captureCanvas.layoutSubtree = true;
|
|
388
|
+
captureCanvas.width = renderWidth;
|
|
389
|
+
captureCanvas.height = renderHeight;
|
|
390
|
+
captureCanvas.style.cssText = `position:fixed;left:0;top:0;width:${width}px;height:${height}px;opacity:0;pointer-events:none;z-index:-9999;`;
|
|
391
|
+
originalParent = timegroup.parentNode;
|
|
392
|
+
originalNextSibling = timegroup.nextSibling;
|
|
393
|
+
savedClipPath = timegroup.style.clipPath;
|
|
394
|
+
savedPointerEvents = timegroup.style.pointerEvents;
|
|
395
|
+
timegroup.style.clipPath = "";
|
|
396
|
+
timegroup.style.pointerEvents = "";
|
|
397
|
+
captureCanvas.appendChild(timegroup);
|
|
398
|
+
document.body.appendChild(captureCanvas);
|
|
399
|
+
captureCtx = captureCanvas.getContext("2d");
|
|
400
|
+
captureCanvas.offsetHeight;
|
|
401
|
+
timegroup.offsetHeight;
|
|
402
|
+
}
|
|
338
403
|
/**
|
|
339
404
|
* Apply pending resolution scale changes.
|
|
340
405
|
* Called at the start of refresh() before rendering, so the old content
|
|
@@ -347,12 +412,10 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
347
412
|
currentResolutionScale = newScale;
|
|
348
413
|
renderWidth = Math.floor(width * currentResolutionScale);
|
|
349
414
|
renderHeight = Math.floor(height * currentResolutionScale);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
container.style.transformOrigin = "top left";
|
|
355
|
-
} else container.style.transform = "";
|
|
415
|
+
if (captureCanvas) {
|
|
416
|
+
captureCanvas.width = renderWidth;
|
|
417
|
+
captureCanvas.height = renderHeight;
|
|
418
|
+
}
|
|
356
419
|
};
|
|
357
420
|
/**
|
|
358
421
|
* Dynamically change resolution scale without rebuilding clone structure.
|
|
@@ -366,52 +429,104 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
366
429
|
lastTimeMs = -1;
|
|
367
430
|
};
|
|
368
431
|
const getResolutionScale = () => pendingResolutionScale ?? currentResolutionScale;
|
|
432
|
+
let frameCount = 0;
|
|
433
|
+
let totalFrameControllerMs = 0;
|
|
434
|
+
let totalCaptureMs = 0;
|
|
435
|
+
let totalCopyMs = 0;
|
|
436
|
+
let totalFrameMs = 0;
|
|
369
437
|
const refresh = async () => {
|
|
370
|
-
if (
|
|
438
|
+
if (disposed) return;
|
|
371
439
|
const sourceTimeMs = timegroup.currentTimeMs ?? 0;
|
|
372
440
|
const userTimeMs = timegroup.userTimeMs ?? 0;
|
|
373
441
|
if (Math.abs(sourceTimeMs - userTimeMs) > TIME_EPSILON_MS) return;
|
|
374
442
|
if (userTimeMs === lastTimeMs) return;
|
|
443
|
+
if (rendering) return;
|
|
375
444
|
lastTimeMs = userTimeMs;
|
|
376
445
|
rendering = true;
|
|
377
446
|
applyPendingResolutionChange();
|
|
378
447
|
if (!hasLoggedScale) {
|
|
379
448
|
hasLoggedScale = true;
|
|
380
|
-
const mode =
|
|
449
|
+
const mode = useNative ? "native" : "foreignObject";
|
|
381
450
|
logger.debug(`[renderTimegroupToCanvas] Resolution scale: ${currentResolutionScale} (${width}x${height} → ${renderWidth}x${renderHeight}), canvas buffer: ${canvas.width}x${canvas.height}, CSS size: ${canvas.style.width}x${canvas.style.height}, renderMode: ${mode}`);
|
|
382
451
|
}
|
|
383
452
|
try {
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
previewContainer.appendChild(container$1);
|
|
392
|
-
overrideRootCloneStyles(syncState);
|
|
393
|
-
const removedNodes = removeHiddenNodesForSerialization(syncState);
|
|
394
|
-
const t0 = performance.now();
|
|
395
|
-
const image = await renderToImage(previewContainer, renderWidth, renderHeight, {
|
|
396
|
-
canvasScale: currentResolutionScale,
|
|
397
|
-
renderContext,
|
|
398
|
-
sourceMap: syncState.canvasSourceMap
|
|
453
|
+
const tFrame = performance.now();
|
|
454
|
+
const tFC0 = performance.now();
|
|
455
|
+
await frameController.renderFrame(userTimeMs, {
|
|
456
|
+
waitForLitUpdate: false,
|
|
457
|
+
onAnimationsUpdate: (root) => {
|
|
458
|
+
updateAnimations(root);
|
|
459
|
+
}
|
|
399
460
|
});
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
461
|
+
const fcMs = performance.now() - tFC0;
|
|
462
|
+
const tCapture0 = performance.now();
|
|
463
|
+
if (useNative && captureCanvas && captureCtx) {
|
|
464
|
+
if (captureCanvas.width !== width || captureCanvas.height !== height) {
|
|
465
|
+
captureCtx.save();
|
|
466
|
+
captureCtx.scale(captureCanvas.width / width, captureCanvas.height / height);
|
|
467
|
+
captureCtx.drawElementImage(timegroup, 0, 0);
|
|
468
|
+
captureCtx.restore();
|
|
469
|
+
} else captureCtx.drawElementImage(timegroup, 0, 0);
|
|
470
|
+
const captureMs = performance.now() - tCapture0;
|
|
471
|
+
const tCopy0 = performance.now();
|
|
472
|
+
const targetWidth = Math.floor(renderWidth * scale * dpr);
|
|
473
|
+
const targetHeight = Math.floor(renderHeight * scale * dpr);
|
|
474
|
+
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
|
475
|
+
canvas.width = targetWidth;
|
|
476
|
+
canvas.height = targetHeight;
|
|
477
|
+
} else ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
478
|
+
ctx.drawImage(captureCanvas, 0, 0, canvas.width, canvas.height);
|
|
479
|
+
const copyMs = performance.now() - tCopy0;
|
|
480
|
+
const frameMs = performance.now() - tFrame;
|
|
481
|
+
frameCount++;
|
|
482
|
+
totalFrameControllerMs += fcMs;
|
|
483
|
+
totalCaptureMs += captureMs;
|
|
484
|
+
totalCopyMs += copyMs;
|
|
485
|
+
totalFrameMs += frameMs;
|
|
486
|
+
defaultProfiler.incrementRenderCount();
|
|
487
|
+
if (defaultProfiler.shouldLogByFrameCount(60)) {
|
|
488
|
+
frameCount = 0;
|
|
489
|
+
totalFrameControllerMs = 0;
|
|
490
|
+
totalCaptureMs = 0;
|
|
491
|
+
totalCopyMs = 0;
|
|
492
|
+
totalFrameMs = 0;
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
const absoluteTimeMs = toAbsoluteTime(timegroup, userTimeMs);
|
|
496
|
+
const dataUri = await captureTimelineToDataUri(timegroup, width, height, {
|
|
497
|
+
renderContext,
|
|
498
|
+
canvasScale: currentResolutionScale,
|
|
499
|
+
timeMs: absoluteTimeMs
|
|
500
|
+
});
|
|
501
|
+
const captureMs = performance.now() - tCapture0;
|
|
502
|
+
const tCopy0 = performance.now();
|
|
503
|
+
const image = await loadImageFromDataUri(dataUri);
|
|
504
|
+
const copyMs = performance.now() - tCopy0;
|
|
505
|
+
const targetWidth = Math.floor(renderWidth * scale * dpr);
|
|
506
|
+
const targetHeight = Math.floor(renderHeight * scale * dpr);
|
|
507
|
+
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
|
508
|
+
canvas.width = targetWidth;
|
|
509
|
+
canvas.height = targetHeight;
|
|
510
|
+
} else ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
511
|
+
ctx.save();
|
|
512
|
+
ctx.scale(dpr * scale, dpr * scale);
|
|
513
|
+
ctx.drawImage(image, 0, 0, renderWidth, renderHeight);
|
|
514
|
+
ctx.restore();
|
|
515
|
+
const frameMs = performance.now() - tFrame;
|
|
516
|
+
frameCount++;
|
|
517
|
+
totalFrameControllerMs += fcMs;
|
|
518
|
+
totalCaptureMs += captureMs;
|
|
519
|
+
totalCopyMs += copyMs;
|
|
520
|
+
totalFrameMs += frameMs;
|
|
521
|
+
defaultProfiler.incrementRenderCount();
|
|
522
|
+
if (defaultProfiler.shouldLogByFrameCount(60)) {
|
|
523
|
+
frameCount = 0;
|
|
524
|
+
totalFrameControllerMs = 0;
|
|
525
|
+
totalCaptureMs = 0;
|
|
526
|
+
totalCopyMs = 0;
|
|
527
|
+
totalFrameMs = 0;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
415
530
|
} catch (e) {
|
|
416
531
|
logger.error("Canvas preview render failed:", e);
|
|
417
532
|
} finally {
|
|
@@ -426,6 +541,13 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
426
541
|
disposed = true;
|
|
427
542
|
frameController.abort();
|
|
428
543
|
renderContext.dispose();
|
|
544
|
+
if (useNative && originalParent) {
|
|
545
|
+
timegroup.style.clipPath = savedClipPath;
|
|
546
|
+
timegroup.style.pointerEvents = savedPointerEvents;
|
|
547
|
+
if (originalNextSibling) originalParent.insertBefore(timegroup, originalNextSibling);
|
|
548
|
+
else originalParent.appendChild(timegroup);
|
|
549
|
+
captureCanvas?.remove();
|
|
550
|
+
}
|
|
429
551
|
};
|
|
430
552
|
refresh();
|
|
431
553
|
return {
|
|
@@ -439,5 +561,5 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
|
|
|
439
561
|
}
|
|
440
562
|
|
|
441
563
|
//#endregion
|
|
442
|
-
export {
|
|
564
|
+
export { generateThumbnailsFromClone, renderTimegroupToCanvas, resetRenderState, waitForVideoContent };
|
|
443
565
|
//# sourceMappingURL=renderTimegroupToCanvas.js.map
|