@editframe/elements 0.37.3-beta → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.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 +2 -2
- 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 +5 -8
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +2 -2
- package/dist/gui/EFOverlayLayer.d.ts +2 -2
- package/dist/gui/EFPause.d.ts +2 -2
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +2 -2
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +2 -2
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +2 -2
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +6 -2
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +2 -2
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +2 -2
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +2 -2
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +2 -2
- 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 +5 -3
- 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 +2 -2
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +2 -2
- 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.d.ts +134 -32
- package/dist/preview/renderTimegroupToCanvas.js +321 -146
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
- package/dist/preview/renderTimegroupToVideo.js +94 -106
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.d.ts +56 -0
- package/dist/preview/renderers.js +13 -1
- 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.d.ts +13 -0
- package/dist/preview/rendering/inlineImages.js +7 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.d.ts +8 -0
- 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 +45 -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/tsdown.config.ts +6 -1
- 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/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
|
@@ -1,78 +1,5 @@
|
|
|
1
|
-
import { logger } from "./logger.js";
|
|
2
|
-
|
|
3
1
|
//#region src/preview/statsTrackingStrategy.ts
|
|
4
2
|
/**
|
|
5
|
-
* Canvas mode stats tracking strategy.
|
|
6
|
-
* Tracks all stats including render time, headroom, resolution scale, and adaptive resolution.
|
|
7
|
-
*/
|
|
8
|
-
var CanvasStatsStrategy = class {
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.animationFrame = null;
|
|
11
|
-
this.lastStatsUpdateTime = 0;
|
|
12
|
-
this.currentStats = null;
|
|
13
|
-
this.canvasPreviewResult = options.canvasPreviewResult;
|
|
14
|
-
this.adaptiveTracker = options.adaptiveTracker;
|
|
15
|
-
this.compositionWidth = options.compositionWidth;
|
|
16
|
-
this.compositionHeight = options.compositionHeight;
|
|
17
|
-
this.getResolutionScale = options.getResolutionScale;
|
|
18
|
-
this.isAtRest = options.isAtRest;
|
|
19
|
-
this.isExporting = options.isExporting;
|
|
20
|
-
}
|
|
21
|
-
start() {
|
|
22
|
-
if (this.animationFrame !== null) return;
|
|
23
|
-
const { refresh } = this.canvasPreviewResult;
|
|
24
|
-
const loop = async (timestamp) => {
|
|
25
|
-
if (this.animationFrame === null) return;
|
|
26
|
-
if (!this.isExporting()) try {
|
|
27
|
-
const renderStart = performance.now();
|
|
28
|
-
await refresh();
|
|
29
|
-
const renderTime = performance.now() - renderStart;
|
|
30
|
-
if (!this.isAtRest()) this.adaptiveTracker.recordFrame(renderTime, timestamp);
|
|
31
|
-
if (timestamp - this.lastStatsUpdateTime > 100) {
|
|
32
|
-
this.lastStatsUpdateTime = timestamp;
|
|
33
|
-
const currentScale = this.getResolutionScale();
|
|
34
|
-
const renderWidth = Math.floor(this.compositionWidth * currentScale);
|
|
35
|
-
const renderHeight = Math.floor(this.compositionHeight * currentScale);
|
|
36
|
-
this.updateStats(renderWidth, renderHeight, currentScale);
|
|
37
|
-
}
|
|
38
|
-
} catch (e) {
|
|
39
|
-
logger.error("Canvas stats tracking failed:", e);
|
|
40
|
-
}
|
|
41
|
-
this.animationFrame = requestAnimationFrame(loop);
|
|
42
|
-
};
|
|
43
|
-
this.animationFrame = requestAnimationFrame(loop);
|
|
44
|
-
}
|
|
45
|
-
stop() {
|
|
46
|
-
if (this.animationFrame !== null) {
|
|
47
|
-
cancelAnimationFrame(this.animationFrame);
|
|
48
|
-
this.animationFrame = null;
|
|
49
|
-
}
|
|
50
|
-
this.currentStats = null;
|
|
51
|
-
}
|
|
52
|
-
getStats() {
|
|
53
|
-
return this.currentStats;
|
|
54
|
-
}
|
|
55
|
-
supportsStat(_stat) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
updateStats(renderWidth, renderHeight, resolutionScale) {
|
|
59
|
-
const trackerStats = this.adaptiveTracker.getStats();
|
|
60
|
-
this.currentStats = {
|
|
61
|
-
fps: trackerStats.fps,
|
|
62
|
-
avgRenderTime: trackerStats.avgRenderTime,
|
|
63
|
-
headroom: trackerStats.headroom,
|
|
64
|
-
pressureState: trackerStats.pressureState,
|
|
65
|
-
pressureHistory: trackerStats.pressureHistory,
|
|
66
|
-
renderWidth,
|
|
67
|
-
renderHeight,
|
|
68
|
-
resolutionScale,
|
|
69
|
-
samplesAtCurrentScale: trackerStats.samplesAtCurrentScale,
|
|
70
|
-
canScaleUp: trackerStats.canScaleUp,
|
|
71
|
-
canScaleDown: trackerStats.canScaleDown
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
3
|
* DOM mode stats tracking strategy.
|
|
77
4
|
* Tracks FPS, resolution, CPU pressure, and frame seek time.
|
|
78
5
|
*/
|
|
@@ -170,34 +97,7 @@ var DomStatsStrategy = class {
|
|
|
170
97
|
};
|
|
171
98
|
}
|
|
172
99
|
};
|
|
173
|
-
/**
|
|
174
|
-
* Factory function to create the appropriate stats tracking strategy for a presentation mode.
|
|
175
|
-
* Returns null for modes that don't support stats tracking.
|
|
176
|
-
*/
|
|
177
|
-
function createStatsTrackingStrategy(mode, options) {
|
|
178
|
-
switch (mode) {
|
|
179
|
-
case "canvas":
|
|
180
|
-
if (!options.canvasPreviewResult || !options.getResolutionScale || !options.isAtRest || !options.isExporting) return null;
|
|
181
|
-
return new CanvasStatsStrategy({
|
|
182
|
-
canvasPreviewResult: options.canvasPreviewResult,
|
|
183
|
-
adaptiveTracker: options.adaptiveTracker,
|
|
184
|
-
compositionWidth: options.compositionWidth,
|
|
185
|
-
compositionHeight: options.compositionHeight,
|
|
186
|
-
getResolutionScale: options.getResolutionScale,
|
|
187
|
-
isAtRest: options.isAtRest,
|
|
188
|
-
isExporting: options.isExporting
|
|
189
|
-
});
|
|
190
|
-
case "dom":
|
|
191
|
-
case "original": return new DomStatsStrategy({
|
|
192
|
-
timegroup: options.timegroup,
|
|
193
|
-
adaptiveTracker: options.adaptiveTracker
|
|
194
|
-
});
|
|
195
|
-
case "clone":
|
|
196
|
-
case "computed": return null;
|
|
197
|
-
default: return null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
100
|
|
|
201
101
|
//#endregion
|
|
202
|
-
export {
|
|
102
|
+
export { DomStatsStrategy };
|
|
203
103
|
//# sourceMappingURL=statsTrackingStrategy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statsTrackingStrategy.js","names":[],"sources":["../../src/preview/statsTrackingStrategy.ts"],"sourcesContent":["/**\n * Stats tracking strategy for different presentation modes.\n * \n * Uses strategy pattern to encapsulate mode-specific stats tracking logic,\n * allowing each mode to report what stats it supports and provide its own implementation.\n */\n\nimport { logger } from \"./logger.js\";\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { AdaptiveResolutionTracker } from \"./AdaptiveResolutionTracker.js\";\nimport type { CanvasPreviewResult } from \"./renderTimegroupToCanvas.js\";\nimport type { PreviewPresentationMode } from \"./previewSettings.js\";\n\n/**\n * Stat types that can be tracked.\n */\nexport type StatType =\n | \"fps\"\n | \"renderTime\"\n | \"headroom\"\n | \"resolution\"\n | \"resolutionScale\"\n | \"cpuPressure\"\n | \"adaptiveResolution\";\n\n/**\n * Playback statistics for display.\n */\nexport interface PlaybackStats {\n fps: number;\n avgRenderTime: number | null; // null if not measurable\n headroom: number | null; // null if not applicable\n pressureState: string;\n pressureHistory: string[];\n renderWidth: number;\n renderHeight: number;\n resolutionScale: number | null; // null if not applicable\n samplesAtCurrentScale?: number; // only for adaptive resolution\n canScaleUp?: boolean; // only for adaptive resolution\n canScaleDown?: boolean; // only for adaptive resolution\n}\n\n/**\n * Strategy interface for tracking stats in different presentation modes.\n */\nexport interface StatsTrackingStrategy {\n /** Start tracking stats (called when mode is initialized and stats are enabled) */\n start(): void;\n /** Stop tracking stats (called when mode stops or stats are disabled) */\n stop(): void;\n /** Get current stats, or null if not available */\n getStats(): PlaybackStats | null;\n /** Check if this strategy supports a specific stat type */\n supportsStat(stat: StatType): boolean;\n}\n\n/**\n * Canvas mode stats tracking strategy.\n * Tracks all stats including render time, headroom, resolution scale, and adaptive resolution.\n */\nexport class CanvasStatsStrategy implements StatsTrackingStrategy {\n private canvasPreviewResult: CanvasPreviewResult;\n private adaptiveTracker: AdaptiveResolutionTracker;\n private readonly compositionWidth: number;\n private readonly compositionHeight: number;\n private getResolutionScale: () => number;\n private isAtRest: () => boolean;\n private isExporting: () => boolean;\n \n private animationFrame: number | null = null;\n private lastStatsUpdateTime = 0;\n private currentStats: PlaybackStats | null = null;\n\n constructor(options: {\n canvasPreviewResult: CanvasPreviewResult;\n adaptiveTracker: AdaptiveResolutionTracker;\n compositionWidth: number;\n compositionHeight: number;\n getResolutionScale: () => number;\n isAtRest: () => boolean;\n isExporting: () => boolean;\n }) {\n this.canvasPreviewResult = options.canvasPreviewResult;\n this.adaptiveTracker = options.adaptiveTracker;\n this.compositionWidth = options.compositionWidth;\n this.compositionHeight = options.compositionHeight;\n this.getResolutionScale = options.getResolutionScale;\n this.isAtRest = options.isAtRest;\n this.isExporting = options.isExporting;\n }\n\n start(): void {\n if (this.animationFrame !== null) return;\n \n const { refresh } = this.canvasPreviewResult;\n \n const loop = async (timestamp: number) => {\n if (this.animationFrame === null) return; // Stopped\n \n // Skip refresh during export to avoid wasting CPU\n if (!this.isExporting()) {\n try {\n // Measure actual render time\n const renderStart = performance.now();\n await refresh();\n const renderTime = performance.now() - renderStart;\n \n // Only record frame timing when in motion (playing/scrubbing)\n // This prevents inflated stats at rest and focuses tracking on actual playback\n if (!this.isAtRest()) {\n this.adaptiveTracker.recordFrame(renderTime, timestamp);\n }\n\n // Update playback stats every 100ms (10 times per second)\n if (timestamp - this.lastStatsUpdateTime > 100) {\n this.lastStatsUpdateTime = timestamp;\n // Get CURRENT resolution from the canvas result (may have changed dynamically)\n const currentScale = this.getResolutionScale();\n const renderWidth = Math.floor(this.compositionWidth * currentScale);\n const renderHeight = Math.floor(this.compositionHeight * currentScale);\n this.updateStats(renderWidth, renderHeight, currentScale);\n }\n } catch (e) {\n logger.error(\"Canvas stats tracking failed:\", e);\n }\n }\n \n this.animationFrame = requestAnimationFrame(loop);\n };\n \n this.animationFrame = requestAnimationFrame(loop);\n }\n\n stop(): void {\n if (this.animationFrame !== null) {\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = null;\n }\n this.currentStats = null;\n }\n\n getStats(): PlaybackStats | null {\n return this.currentStats;\n }\n\n supportsStat(_stat: StatType): boolean {\n // Canvas mode supports all stats\n return true;\n }\n\n private updateStats(renderWidth: number, renderHeight: number, resolutionScale: number): void {\n const trackerStats = this.adaptiveTracker.getStats();\n\n this.currentStats = {\n fps: trackerStats.fps,\n avgRenderTime: trackerStats.avgRenderTime,\n headroom: trackerStats.headroom,\n pressureState: trackerStats.pressureState,\n pressureHistory: trackerStats.pressureHistory,\n renderWidth,\n renderHeight,\n resolutionScale,\n samplesAtCurrentScale: trackerStats.samplesAtCurrentScale,\n canScaleUp: trackerStats.canScaleUp,\n canScaleDown: trackerStats.canScaleDown,\n };\n }\n}\n\n/**\n * DOM mode stats tracking strategy.\n * Tracks FPS, resolution, CPU pressure, and frame seek time.\n */\nexport class DomStatsStrategy implements StatsTrackingStrategy {\n private timegroup: EFTimegroup;\n private adaptiveTracker: AdaptiveResolutionTracker;\n \n private animationFrame: number | null = null;\n private lastFrameTime = 0;\n private frameIntervals: number[] = [];\n private lastStatsUpdateTime = 0;\n private currentStats: PlaybackStats | null = null;\n \n // Frame seek time tracking\n private seekStartTime = 0;\n private seekTimes: number[] = [];\n private frameTaskCleanup: (() => void) | null = null;\n \n private readonly ROLLING_WINDOW_SIZE = 30; // ~1 second at 30fps\n private readonly TARGET_FRAME_TIME_MS = 33.33; // 30fps target\n\n constructor(options: {\n timegroup: EFTimegroup;\n adaptiveTracker: AdaptiveResolutionTracker;\n }) {\n this.timegroup = options.timegroup;\n this.adaptiveTracker = options.adaptiveTracker;\n }\n\n start(): void {\n if (this.animationFrame !== null) return;\n \n // Track frame seek times using frameTask callback\n // This measures how long it takes for the timegroup to fully update after a seek\n const frameTaskCallback = async () => {\n if (this.seekStartTime > 0) {\n const seekTime = performance.now() - this.seekStartTime;\n this.seekTimes.push(seekTime);\n if (this.seekTimes.length > this.ROLLING_WINDOW_SIZE) {\n this.seekTimes.shift();\n }\n this.seekStartTime = 0; // Reset after recording\n }\n };\n \n this.timegroup.addFrameTask(frameTaskCallback);\n this.frameTaskCleanup = () => {\n // Note: EFTimegroup doesn't have removeFrameTask, but this is fine\n // The callback will be cleaned up when the timegroup is destroyed\n };\n \n // Track currentTimeMs changes to detect seeks\n let lastCurrentTimeMs = this.timegroup.currentTimeMs;\n const checkSeek = () => {\n const currentTimeMs = this.timegroup.currentTimeMs;\n if (currentTimeMs !== lastCurrentTimeMs) {\n // Seek detected - start timing\n this.seekStartTime = performance.now();\n lastCurrentTimeMs = currentTimeMs;\n }\n };\n \n const loop = (timestamp: number) => {\n if (this.animationFrame === null) return; // Stopped\n \n // Check for seeks\n checkSeek();\n \n // Track frame intervals for FPS calculation\n if (this.lastFrameTime > 0) {\n const interval = timestamp - this.lastFrameTime;\n this.frameIntervals.push(interval);\n if (this.frameIntervals.length > this.ROLLING_WINDOW_SIZE) {\n this.frameIntervals.shift();\n }\n }\n this.lastFrameTime = timestamp;\n \n // Update stats every 100ms (10 times per second)\n if (timestamp - this.lastStatsUpdateTime > 100) {\n this.lastStatsUpdateTime = timestamp;\n this.updateStats();\n }\n \n this.animationFrame = requestAnimationFrame(loop);\n };\n \n this.animationFrame = requestAnimationFrame(loop);\n }\n\n stop(): void {\n if (this.animationFrame !== null) {\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = null;\n }\n if (this.frameTaskCleanup) {\n this.frameTaskCleanup();\n this.frameTaskCleanup = null;\n }\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.seekStartTime = 0;\n this.seekTimes = [];\n this.currentStats = null;\n }\n\n getStats(): PlaybackStats | null {\n return this.currentStats;\n }\n\n supportsStat(stat: StatType): boolean {\n // DOM mode supports: fps, resolution, cpuPressure, renderTime (seek time), headroom\n // Does NOT support: resolutionScale, adaptiveResolution\n return stat === \"fps\" || stat === \"resolution\" || stat === \"cpuPressure\" || stat === \"renderTime\" || stat === \"headroom\";\n }\n\n private updateStats(): void {\n // Calculate FPS from frame intervals\n const avgFrameInterval = this.frameIntervals.length > 0\n ? this.frameIntervals.reduce((a, b) => a + b, 0) / this.frameIntervals.length\n : 16.67;\n const fps = avgFrameInterval > 0 ? 1000 / avgFrameInterval : 0;\n \n // Calculate average seek time (frame update time)\n const avgSeekTime = this.seekTimes.length > 0\n ? this.seekTimes.reduce((a, b) => a + b, 0) / this.seekTimes.length\n : 0;\n \n // Calculate headroom (positive = faster than target, negative = slower)\n const headroom = avgSeekTime > 0\n ? this.TARGET_FRAME_TIME_MS - avgSeekTime\n : 0;\n \n // Get CPU pressure from adaptive tracker\n const trackerStats = this.adaptiveTracker.getStats();\n \n // Calculate displayed resolution from timegroup bounding rect\n const rect = this.timegroup.getBoundingClientRect();\n const renderWidth = Math.round(rect.width);\n const renderHeight = Math.round(rect.height);\n\n this.currentStats = {\n fps,\n avgRenderTime: avgSeekTime > 0 ? avgSeekTime : null,\n headroom: avgSeekTime > 0 ? headroom : null,\n pressureState: trackerStats.pressureState,\n pressureHistory: trackerStats.pressureHistory,\n renderWidth,\n renderHeight,\n resolutionScale: null, // Not applicable in DOM mode\n };\n }\n}\n\n/**\n * Factory function to create the appropriate stats tracking strategy for a presentation mode.\n * Returns null for modes that don't support stats tracking.\n */\nexport function createStatsTrackingStrategy(\n mode: PreviewPresentationMode,\n options: {\n timegroup: EFTimegroup;\n adaptiveTracker: AdaptiveResolutionTracker;\n canvasPreviewResult?: CanvasPreviewResult | null;\n compositionWidth: number;\n compositionHeight: number;\n getResolutionScale?: () => number;\n isAtRest?: () => boolean;\n isExporting?: () => boolean;\n }\n): StatsTrackingStrategy | null {\n switch (mode) {\n case \"canvas\":\n if (!options.canvasPreviewResult || !options.getResolutionScale || !options.isAtRest || !options.isExporting) {\n return null;\n }\n return new CanvasStatsStrategy({\n canvasPreviewResult: options.canvasPreviewResult,\n adaptiveTracker: options.adaptiveTracker,\n compositionWidth: options.compositionWidth,\n compositionHeight: options.compositionHeight,\n getResolutionScale: options.getResolutionScale,\n isAtRest: options.isAtRest,\n isExporting: options.isExporting,\n });\n \n case \"dom\":\n case \"original\": // \"dom\" maps to \"original\" mode\n return new DomStatsStrategy({\n timegroup: options.timegroup,\n adaptiveTracker: options.adaptiveTracker,\n });\n \n case \"clone\":\n case \"computed\": // \"clone\" maps to \"computed\" mode\n // These modes don't support stats tracking\n return null;\n \n default:\n return null;\n }\n}\n"],"mappings":";;;;;;;AA4DA,IAAa,sBAAb,MAAkE;CAahE,YAAY,SAQT;wBAZqC;6BACV;sBACe;AAW3C,OAAK,sBAAsB,QAAQ;AACnC,OAAK,kBAAkB,QAAQ;AAC/B,OAAK,mBAAmB,QAAQ;AAChC,OAAK,oBAAoB,QAAQ;AACjC,OAAK,qBAAqB,QAAQ;AAClC,OAAK,WAAW,QAAQ;AACxB,OAAK,cAAc,QAAQ;;CAG7B,QAAc;AACZ,MAAI,KAAK,mBAAmB,KAAM;EAElC,MAAM,EAAE,YAAY,KAAK;EAEzB,MAAM,OAAO,OAAO,cAAsB;AACxC,OAAI,KAAK,mBAAmB,KAAM;AAGlC,OAAI,CAAC,KAAK,aAAa,CACrB,KAAI;IAEF,MAAM,cAAc,YAAY,KAAK;AACrC,UAAM,SAAS;IACf,MAAM,aAAa,YAAY,KAAK,GAAG;AAIvC,QAAI,CAAC,KAAK,UAAU,CAClB,MAAK,gBAAgB,YAAY,YAAY,UAAU;AAIzD,QAAI,YAAY,KAAK,sBAAsB,KAAK;AAC9C,UAAK,sBAAsB;KAE3B,MAAM,eAAe,KAAK,oBAAoB;KAC9C,MAAM,cAAc,KAAK,MAAM,KAAK,mBAAmB,aAAa;KACpE,MAAM,eAAe,KAAK,MAAM,KAAK,oBAAoB,aAAa;AACtE,UAAK,YAAY,aAAa,cAAc,aAAa;;YAEpD,GAAG;AACV,WAAO,MAAM,iCAAiC,EAAE;;AAIpD,QAAK,iBAAiB,sBAAsB,KAAK;;AAGnD,OAAK,iBAAiB,sBAAsB,KAAK;;CAGnD,OAAa;AACX,MAAI,KAAK,mBAAmB,MAAM;AAChC,wBAAqB,KAAK,eAAe;AACzC,QAAK,iBAAiB;;AAExB,OAAK,eAAe;;CAGtB,WAAiC;AAC/B,SAAO,KAAK;;CAGd,aAAa,OAA0B;AAErC,SAAO;;CAGT,AAAQ,YAAY,aAAqB,cAAsB,iBAA+B;EAC5F,MAAM,eAAe,KAAK,gBAAgB,UAAU;AAEpD,OAAK,eAAe;GAClB,KAAK,aAAa;GAClB,eAAe,aAAa;GAC5B,UAAU,aAAa;GACvB,eAAe,aAAa;GAC5B,iBAAiB,aAAa;GAC9B;GACA;GACA;GACA,uBAAuB,aAAa;GACpC,YAAY,aAAa;GACzB,cAAc,aAAa;GAC5B;;;;;;;AAQL,IAAa,mBAAb,MAA+D;CAkB7D,YAAY,SAGT;wBAjBqC;uBAChB;wBACW,EAAE;6BACP;sBACe;uBAGrB;mBACM,EAAE;0BACgB;6BAET;8BACC;AAMtC,OAAK,YAAY,QAAQ;AACzB,OAAK,kBAAkB,QAAQ;;CAGjC,QAAc;AACZ,MAAI,KAAK,mBAAmB,KAAM;EAIlC,MAAM,oBAAoB,YAAY;AACpC,OAAI,KAAK,gBAAgB,GAAG;IAC1B,MAAM,WAAW,YAAY,KAAK,GAAG,KAAK;AAC1C,SAAK,UAAU,KAAK,SAAS;AAC7B,QAAI,KAAK,UAAU,SAAS,KAAK,oBAC/B,MAAK,UAAU,OAAO;AAExB,SAAK,gBAAgB;;;AAIzB,OAAK,UAAU,aAAa,kBAAkB;AAC9C,OAAK,yBAAyB;EAM9B,IAAI,oBAAoB,KAAK,UAAU;EACvC,MAAM,kBAAkB;GACtB,MAAM,gBAAgB,KAAK,UAAU;AACrC,OAAI,kBAAkB,mBAAmB;AAEvC,SAAK,gBAAgB,YAAY,KAAK;AACtC,wBAAoB;;;EAIxB,MAAM,QAAQ,cAAsB;AAClC,OAAI,KAAK,mBAAmB,KAAM;AAGlC,cAAW;AAGX,OAAI,KAAK,gBAAgB,GAAG;IAC1B,MAAM,WAAW,YAAY,KAAK;AAClC,SAAK,eAAe,KAAK,SAAS;AAClC,QAAI,KAAK,eAAe,SAAS,KAAK,oBACpC,MAAK,eAAe,OAAO;;AAG/B,QAAK,gBAAgB;AAGrB,OAAI,YAAY,KAAK,sBAAsB,KAAK;AAC9C,SAAK,sBAAsB;AAC3B,SAAK,aAAa;;AAGpB,QAAK,iBAAiB,sBAAsB,KAAK;;AAGnD,OAAK,iBAAiB,sBAAsB,KAAK;;CAGnD,OAAa;AACX,MAAI,KAAK,mBAAmB,MAAM;AAChC,wBAAqB,KAAK,eAAe;AACzC,QAAK,iBAAiB;;AAExB,MAAI,KAAK,kBAAkB;AACzB,QAAK,kBAAkB;AACvB,QAAK,mBAAmB;;AAE1B,OAAK,gBAAgB;AACrB,OAAK,iBAAiB,EAAE;AACxB,OAAK,gBAAgB;AACrB,OAAK,YAAY,EAAE;AACnB,OAAK,eAAe;;CAGtB,WAAiC;AAC/B,SAAO,KAAK;;CAGd,aAAa,MAAyB;AAGpC,SAAO,SAAS,SAAS,SAAS,gBAAgB,SAAS,iBAAiB,SAAS,gBAAgB,SAAS;;CAGhH,AAAQ,cAAoB;EAE1B,MAAM,mBAAmB,KAAK,eAAe,SAAS,IAClD,KAAK,eAAe,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,eAAe,SACrE;EACJ,MAAM,MAAM,mBAAmB,IAAI,MAAO,mBAAmB;EAG7D,MAAM,cAAc,KAAK,UAAU,SAAS,IACxC,KAAK,UAAU,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,UAAU,SAC3D;EAGJ,MAAM,WAAW,cAAc,IAC3B,KAAK,uBAAuB,cAC5B;EAGJ,MAAM,eAAe,KAAK,gBAAgB,UAAU;EAGpD,MAAM,OAAO,KAAK,UAAU,uBAAuB;EACnD,MAAM,cAAc,KAAK,MAAM,KAAK,MAAM;EAC1C,MAAM,eAAe,KAAK,MAAM,KAAK,OAAO;AAE5C,OAAK,eAAe;GAClB;GACA,eAAe,cAAc,IAAI,cAAc;GAC/C,UAAU,cAAc,IAAI,WAAW;GACvC,eAAe,aAAa;GAC5B,iBAAiB,aAAa;GAC9B;GACA;GACA,iBAAiB;GAClB;;;;;;;AAQL,SAAgB,4BACd,MACA,SAU8B;AAC9B,SAAQ,MAAR;EACE,KAAK;AACH,OAAI,CAAC,QAAQ,uBAAuB,CAAC,QAAQ,sBAAsB,CAAC,QAAQ,YAAY,CAAC,QAAQ,YAC/F,QAAO;AAET,UAAO,IAAI,oBAAoB;IAC7B,qBAAqB,QAAQ;IAC7B,iBAAiB,QAAQ;IACzB,kBAAkB,QAAQ;IAC1B,mBAAmB,QAAQ;IAC3B,oBAAoB,QAAQ;IAC5B,UAAU,QAAQ;IAClB,aAAa,QAAQ;IACtB,CAAC;EAEJ,KAAK;EACL,KAAK,WACH,QAAO,IAAI,iBAAiB;GAC1B,WAAW,QAAQ;GACnB,iBAAiB,QAAQ;GAC1B,CAAC;EAEJ,KAAK;EACL,KAAK,WAEH,QAAO;EAET,QACE,QAAO"}
|
|
1
|
+
{"version":3,"file":"statsTrackingStrategy.js","names":[],"sources":["../../src/preview/statsTrackingStrategy.ts"],"sourcesContent":["/**\n * Stats tracking strategy for different presentation modes.\n *\n * Uses strategy pattern to encapsulate mode-specific stats tracking logic,\n * allowing each mode to report what stats it supports and provide its own implementation.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { AdaptiveResolutionTracker } from \"./AdaptiveResolutionTracker.js\";\nimport type { CanvasPreviewResult } from \"./renderTimegroupToCanvas.js\";\nimport type { PreviewPresentationMode } from \"./previewSettings.js\";\n\n/**\n * Stat types that can be tracked.\n */\nexport type StatType =\n | \"fps\"\n | \"renderTime\"\n | \"headroom\"\n | \"resolution\"\n | \"resolutionScale\"\n | \"cpuPressure\"\n | \"adaptiveResolution\";\n\n/**\n * Playback statistics for display.\n */\nexport interface PlaybackStats {\n fps: number;\n avgRenderTime: number | null; // null if not measurable\n headroom: number | null; // null if not applicable\n pressureState: string;\n pressureHistory: string[];\n renderWidth: number;\n renderHeight: number;\n resolutionScale: number | null; // null if not applicable\n samplesAtCurrentScale?: number; // only for adaptive resolution\n canScaleUp?: boolean; // only for adaptive resolution\n canScaleDown?: boolean; // only for adaptive resolution\n}\n\n/**\n * Strategy interface for tracking stats in different presentation modes.\n */\nexport interface StatsTrackingStrategy {\n /** Start tracking stats (called when mode is initialized and stats are enabled) */\n start(): void;\n /** Stop tracking stats (called when mode stops or stats are disabled) */\n stop(): void;\n /** Get current stats, or null if not available */\n getStats(): PlaybackStats | null;\n /** Check if this strategy supports a specific stat type */\n supportsStat(stat: StatType): boolean;\n /**\n * Record render timing (optional - only implemented by canvas stats).\n * Called by EFWorkbench after each canvas refresh.\n */\n recordRenderTime?(renderTimeMs: number, timestamp: number): void;\n}\n\n/**\n * Canvas mode stats tracking strategy.\n * Tracks all stats including render time, headroom, resolution scale, and adaptive resolution.\n *\n * This strategy is PASSIVE - it receives render timing from EFWorkbench rather than\n * driving its own render loop. This prevents race conditions and ensures accurate measurements.\n */\nexport class CanvasStatsStrategy implements StatsTrackingStrategy {\n private adaptiveTracker: AdaptiveResolutionTracker;\n private readonly compositionWidth: number;\n private readonly compositionHeight: number;\n private getResolutionScale: () => number;\n private isAtRest: () => boolean;\n private isExporting: () => boolean;\n\n private lastStatsUpdateTime = 0;\n private currentStats: PlaybackStats | null = null;\n\n constructor(options: {\n canvasPreviewResult: CanvasPreviewResult;\n adaptiveTracker: AdaptiveResolutionTracker;\n compositionWidth: number;\n compositionHeight: number;\n getResolutionScale: () => number;\n isAtRest: () => boolean;\n isExporting: () => boolean;\n }) {\n // Note: canvasPreviewResult no longer needed since we don't call refresh()\n this.adaptiveTracker = options.adaptiveTracker;\n this.compositionWidth = options.compositionWidth;\n this.compositionHeight = options.compositionHeight;\n this.getResolutionScale = options.getResolutionScale;\n this.isAtRest = options.isAtRest;\n this.isExporting = options.isExporting;\n }\n\n start(): void {\n // Initialize stats update time\n this.lastStatsUpdateTime = performance.now();\n }\n\n stop(): void {\n this.currentStats = null;\n }\n\n /**\n * Record render timing from EFWorkbench's render loop.\n * This is called after each successful canvas refresh.\n */\n recordRenderTime(renderTimeMs: number, timestamp: number): void {\n // Skip during export\n if (this.isExporting()) return;\n\n // Only record frame timing when in motion (playing/scrubbing)\n // This prevents inflated stats at rest and focuses tracking on actual playback\n if (!this.isAtRest()) {\n this.adaptiveTracker.recordFrame(renderTimeMs, timestamp);\n }\n\n // Update playback stats every 100ms (10 times per second)\n if (timestamp - this.lastStatsUpdateTime > 100) {\n this.lastStatsUpdateTime = timestamp;\n // Get CURRENT resolution from the canvas result (may have changed dynamically)\n const currentScale = this.getResolutionScale();\n const renderWidth = Math.floor(this.compositionWidth * currentScale);\n const renderHeight = Math.floor(this.compositionHeight * currentScale);\n this.updateStats(renderWidth, renderHeight, currentScale);\n }\n }\n\n getStats(): PlaybackStats | null {\n return this.currentStats;\n }\n\n supportsStat(_stat: StatType): boolean {\n // Canvas mode supports all stats\n return true;\n }\n\n private updateStats(\n renderWidth: number,\n renderHeight: number,\n resolutionScale: number,\n ): void {\n const trackerStats = this.adaptiveTracker.getStats();\n\n this.currentStats = {\n fps: trackerStats.fps,\n avgRenderTime: trackerStats.avgRenderTime,\n headroom: trackerStats.headroom,\n pressureState: trackerStats.pressureState,\n pressureHistory: trackerStats.pressureHistory,\n renderWidth,\n renderHeight,\n resolutionScale,\n samplesAtCurrentScale: trackerStats.samplesAtCurrentScale,\n canScaleUp: trackerStats.canScaleUp,\n canScaleDown: trackerStats.canScaleDown,\n };\n }\n}\n\n/**\n * DOM mode stats tracking strategy.\n * Tracks FPS, resolution, CPU pressure, and frame seek time.\n */\nexport class DomStatsStrategy implements StatsTrackingStrategy {\n private timegroup: EFTimegroup;\n private adaptiveTracker: AdaptiveResolutionTracker;\n\n private animationFrame: number | null = null;\n private lastFrameTime = 0;\n private frameIntervals: number[] = [];\n private lastStatsUpdateTime = 0;\n private currentStats: PlaybackStats | null = null;\n\n // Frame seek time tracking\n private seekStartTime = 0;\n private seekTimes: number[] = [];\n private frameTaskCleanup: (() => void) | null = null;\n\n private readonly ROLLING_WINDOW_SIZE = 30; // ~1 second at 30fps\n private readonly TARGET_FRAME_TIME_MS = 33.33; // 30fps target\n\n constructor(options: {\n timegroup: EFTimegroup;\n adaptiveTracker: AdaptiveResolutionTracker;\n }) {\n this.timegroup = options.timegroup;\n this.adaptiveTracker = options.adaptiveTracker;\n }\n\n start(): void {\n if (this.animationFrame !== null) return;\n\n // Track frame seek times using frameTask callback\n // This measures how long it takes for the timegroup to fully update after a seek\n const frameTaskCallback = async () => {\n if (this.seekStartTime > 0) {\n const seekTime = performance.now() - this.seekStartTime;\n this.seekTimes.push(seekTime);\n if (this.seekTimes.length > this.ROLLING_WINDOW_SIZE) {\n this.seekTimes.shift();\n }\n this.seekStartTime = 0; // Reset after recording\n }\n };\n\n this.timegroup.addFrameTask(frameTaskCallback);\n this.frameTaskCleanup = () => {\n // Note: EFTimegroup doesn't have removeFrameTask, but this is fine\n // The callback will be cleaned up when the timegroup is destroyed\n };\n\n // Track currentTimeMs changes to detect seeks\n let lastCurrentTimeMs = this.timegroup.currentTimeMs;\n const checkSeek = () => {\n const currentTimeMs = this.timegroup.currentTimeMs;\n if (currentTimeMs !== lastCurrentTimeMs) {\n // Seek detected - start timing\n this.seekStartTime = performance.now();\n lastCurrentTimeMs = currentTimeMs;\n }\n };\n\n const loop = (timestamp: number) => {\n if (this.animationFrame === null) return; // Stopped\n\n // Check for seeks\n checkSeek();\n\n // Track frame intervals for FPS calculation\n if (this.lastFrameTime > 0) {\n const interval = timestamp - this.lastFrameTime;\n this.frameIntervals.push(interval);\n if (this.frameIntervals.length > this.ROLLING_WINDOW_SIZE) {\n this.frameIntervals.shift();\n }\n }\n this.lastFrameTime = timestamp;\n\n // Update stats every 100ms (10 times per second)\n if (timestamp - this.lastStatsUpdateTime > 100) {\n this.lastStatsUpdateTime = timestamp;\n this.updateStats();\n }\n\n this.animationFrame = requestAnimationFrame(loop);\n };\n\n this.animationFrame = requestAnimationFrame(loop);\n }\n\n stop(): void {\n if (this.animationFrame !== null) {\n cancelAnimationFrame(this.animationFrame);\n this.animationFrame = null;\n }\n if (this.frameTaskCleanup) {\n this.frameTaskCleanup();\n this.frameTaskCleanup = null;\n }\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.seekStartTime = 0;\n this.seekTimes = [];\n this.currentStats = null;\n }\n\n getStats(): PlaybackStats | null {\n return this.currentStats;\n }\n\n supportsStat(stat: StatType): boolean {\n // DOM mode supports: fps, resolution, cpuPressure, renderTime (seek time), headroom\n // Does NOT support: resolutionScale, adaptiveResolution\n return (\n stat === \"fps\" ||\n stat === \"resolution\" ||\n stat === \"cpuPressure\" ||\n stat === \"renderTime\" ||\n stat === \"headroom\"\n );\n }\n\n private updateStats(): void {\n // Calculate FPS from frame intervals\n const avgFrameInterval =\n this.frameIntervals.length > 0\n ? this.frameIntervals.reduce((a, b) => a + b, 0) /\n this.frameIntervals.length\n : 16.67;\n const fps = avgFrameInterval > 0 ? 1000 / avgFrameInterval : 0;\n\n // Calculate average seek time (frame update time)\n const avgSeekTime =\n this.seekTimes.length > 0\n ? this.seekTimes.reduce((a, b) => a + b, 0) / this.seekTimes.length\n : 0;\n\n // Calculate headroom (positive = faster than target, negative = slower)\n const headroom =\n avgSeekTime > 0 ? this.TARGET_FRAME_TIME_MS - avgSeekTime : 0;\n\n // Get CPU pressure from adaptive tracker\n const trackerStats = this.adaptiveTracker.getStats();\n\n // Calculate displayed resolution from timegroup bounding rect\n const rect = this.timegroup.getBoundingClientRect();\n const renderWidth = Math.round(rect.width);\n const renderHeight = Math.round(rect.height);\n\n this.currentStats = {\n fps,\n avgRenderTime: avgSeekTime > 0 ? avgSeekTime : null,\n headroom: avgSeekTime > 0 ? headroom : null,\n pressureState: trackerStats.pressureState,\n pressureHistory: trackerStats.pressureHistory,\n renderWidth,\n renderHeight,\n resolutionScale: null, // Not applicable in DOM mode\n };\n }\n}\n\n/**\n * Factory function to create the appropriate stats tracking strategy for a presentation mode.\n * Returns null for modes that don't support stats tracking.\n */\nexport function createStatsTrackingStrategy(\n mode: PreviewPresentationMode,\n options: {\n timegroup: EFTimegroup;\n adaptiveTracker: AdaptiveResolutionTracker;\n canvasPreviewResult?: CanvasPreviewResult | null;\n compositionWidth: number;\n compositionHeight: number;\n getResolutionScale?: () => number;\n isAtRest?: () => boolean;\n isExporting?: () => boolean;\n },\n): StatsTrackingStrategy | null {\n switch (mode) {\n case \"canvas\":\n if (\n !options.canvasPreviewResult ||\n !options.getResolutionScale ||\n !options.isAtRest ||\n !options.isExporting\n ) {\n return null;\n }\n return new CanvasStatsStrategy({\n canvasPreviewResult: options.canvasPreviewResult,\n adaptiveTracker: options.adaptiveTracker,\n compositionWidth: options.compositionWidth,\n compositionHeight: options.compositionHeight,\n getResolutionScale: options.getResolutionScale,\n isAtRest: options.isAtRest,\n isExporting: options.isExporting,\n });\n\n case \"dom\":\n return new DomStatsStrategy({\n timegroup: options.timegroup,\n adaptiveTracker: options.adaptiveTracker,\n });\n\n case \"canvas\":\n return null;\n\n default:\n return null;\n }\n}\n"],"mappings":";;;;;AAsKA,IAAa,mBAAb,MAA+D;CAkB7D,YAAY,SAGT;wBAjBqC;uBAChB;wBACW,EAAE;6BACP;sBACe;uBAGrB;mBACM,EAAE;0BACgB;6BAET;8BACC;AAMtC,OAAK,YAAY,QAAQ;AACzB,OAAK,kBAAkB,QAAQ;;CAGjC,QAAc;AACZ,MAAI,KAAK,mBAAmB,KAAM;EAIlC,MAAM,oBAAoB,YAAY;AACpC,OAAI,KAAK,gBAAgB,GAAG;IAC1B,MAAM,WAAW,YAAY,KAAK,GAAG,KAAK;AAC1C,SAAK,UAAU,KAAK,SAAS;AAC7B,QAAI,KAAK,UAAU,SAAS,KAAK,oBAC/B,MAAK,UAAU,OAAO;AAExB,SAAK,gBAAgB;;;AAIzB,OAAK,UAAU,aAAa,kBAAkB;AAC9C,OAAK,yBAAyB;EAM9B,IAAI,oBAAoB,KAAK,UAAU;EACvC,MAAM,kBAAkB;GACtB,MAAM,gBAAgB,KAAK,UAAU;AACrC,OAAI,kBAAkB,mBAAmB;AAEvC,SAAK,gBAAgB,YAAY,KAAK;AACtC,wBAAoB;;;EAIxB,MAAM,QAAQ,cAAsB;AAClC,OAAI,KAAK,mBAAmB,KAAM;AAGlC,cAAW;AAGX,OAAI,KAAK,gBAAgB,GAAG;IAC1B,MAAM,WAAW,YAAY,KAAK;AAClC,SAAK,eAAe,KAAK,SAAS;AAClC,QAAI,KAAK,eAAe,SAAS,KAAK,oBACpC,MAAK,eAAe,OAAO;;AAG/B,QAAK,gBAAgB;AAGrB,OAAI,YAAY,KAAK,sBAAsB,KAAK;AAC9C,SAAK,sBAAsB;AAC3B,SAAK,aAAa;;AAGpB,QAAK,iBAAiB,sBAAsB,KAAK;;AAGnD,OAAK,iBAAiB,sBAAsB,KAAK;;CAGnD,OAAa;AACX,MAAI,KAAK,mBAAmB,MAAM;AAChC,wBAAqB,KAAK,eAAe;AACzC,QAAK,iBAAiB;;AAExB,MAAI,KAAK,kBAAkB;AACzB,QAAK,kBAAkB;AACvB,QAAK,mBAAmB;;AAE1B,OAAK,gBAAgB;AACrB,OAAK,iBAAiB,EAAE;AACxB,OAAK,gBAAgB;AACrB,OAAK,YAAY,EAAE;AACnB,OAAK,eAAe;;CAGtB,WAAiC;AAC/B,SAAO,KAAK;;CAGd,aAAa,MAAyB;AAGpC,SACE,SAAS,SACT,SAAS,gBACT,SAAS,iBACT,SAAS,gBACT,SAAS;;CAIb,AAAQ,cAAoB;EAE1B,MAAM,mBACJ,KAAK,eAAe,SAAS,IACzB,KAAK,eAAe,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAC9C,KAAK,eAAe,SACpB;EACN,MAAM,MAAM,mBAAmB,IAAI,MAAO,mBAAmB;EAG7D,MAAM,cACJ,KAAK,UAAU,SAAS,IACpB,KAAK,UAAU,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,UAAU,SAC3D;EAGN,MAAM,WACJ,cAAc,IAAI,KAAK,uBAAuB,cAAc;EAG9D,MAAM,eAAe,KAAK,gBAAgB,UAAU;EAGpD,MAAM,OAAO,KAAK,UAAU,uBAAuB;EACnD,MAAM,cAAc,KAAK,MAAM,KAAK,MAAM;EAC1C,MAAM,eAAe,KAAK,MAAM,KAAK,OAAO;AAE5C,OAAK,eAAe;GAClB;GACA,eAAe,cAAc,IAAI,cAAc;GAC/C,UAAU,cAAc,IAAI,WAAW;GACvC,eAAe,aAAa;GAC5B,iBAAiB,aAAa;GAC9B;GACA;GACA,iBAAiB;GAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerPool.js","names":["poolSize: number","testTimeout: number | null","testHandler: ((event: MessageEvent) => void) | null"],"sources":["../../../src/preview/workers/WorkerPool.ts"],"sourcesContent":["/**\n * Worker pool for parallel task execution.\n * Manages a pool of workers and distributes tasks across them.\n */\n\nimport { logger } from \"../logger.js\";\n\n// Constants\nconst WORKER_INIT_TEST_TIMEOUT_MS = 2000;\n\ninterface QueuedTask<T> {\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n task: (worker: Worker) => Promise<T>;\n}\n\nexport class WorkerPool {\n private workers: Worker[] = [];\n private availableWorkers: Worker[] = [];\n private taskQueue: QueuedTask<unknown>[] = [];\n private isTerminated = false;\n private workerUrl: string;\n
|
|
1
|
+
{"version":3,"file":"WorkerPool.js","names":["poolSize: number","testTimeout: number | null","testHandler: ((event: MessageEvent) => void) | null"],"sources":["../../../src/preview/workers/WorkerPool.ts"],"sourcesContent":["/**\n * Worker pool for parallel task execution.\n * Manages a pool of workers and distributes tasks across them.\n */\n\nimport { logger } from \"../logger.js\";\n\n// Constants\nconst WORKER_INIT_TEST_TIMEOUT_MS = 2000;\n\ninterface QueuedTask<T> {\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n task: (worker: Worker) => Promise<T>;\n}\n\nexport class WorkerPool {\n private workers: Worker[] = [];\n private availableWorkers: Worker[] = [];\n private taskQueue: QueuedTask<unknown>[] = [];\n private isTerminated = false;\n private workerUrl: string;\n\n constructor(\n workerScriptUrl: string,\n private poolSize: number = navigator.hardwareConcurrency || 4,\n ) {\n this.workerUrl = workerScriptUrl;\n\n // Check browser support first, then initialize workers\n if (this.hasBrowserSupport()) {\n this.initializeWorkers();\n }\n }\n\n /**\n * Check if browser supports workers (before initialization).\n */\n private hasBrowserSupport(): boolean {\n return (\n typeof Worker !== \"undefined\" &&\n typeof OffscreenCanvas !== \"undefined\" &&\n typeof createImageBitmap !== \"undefined\"\n );\n }\n\n private initializeWorkers(): void {\n for (let i = 0; i < this.poolSize; i++) {\n try {\n // Create worker from URL (typically a blob URL from inlined worker code)\n const worker = new Worker(this.workerUrl, { type: \"module\" });\n\n // Test if worker is responding - cleanup handler after confirmation\n let testTimeout: number | null = null;\n let testHandler: ((event: MessageEvent) => void) | null = null;\n\n const cleanupTest = () => {\n if (testTimeout !== null) {\n clearTimeout(testTimeout);\n testTimeout = null;\n }\n if (testHandler !== null) {\n worker.removeEventListener(\"message\", testHandler);\n testHandler = null;\n }\n };\n\n testTimeout = window.setTimeout(() => {\n cleanupTest();\n }, WORKER_INIT_TEST_TIMEOUT_MS);\n\n testHandler = (event: MessageEvent) => {\n // Check if this is a test response (worker startup message)\n if (\n event.data &&\n typeof event.data === \"string\" &&\n event.data.includes(\"encoderWorker\")\n ) {\n cleanupTest();\n }\n };\n worker.addEventListener(\"message\", testHandler);\n\n worker.onerror = (error) => {\n cleanupTest();\n logger.error(`[WorkerPool] Worker ${i} error:`, {\n message: error.message,\n filename: error.filename,\n lineno: error.lineno,\n colno: error.colno,\n });\n };\n worker.onmessageerror = (error) => {\n logger.error(`[WorkerPool] Worker ${i} message error:`, error);\n };\n this.workers.push(worker);\n this.availableWorkers.push(worker);\n } catch (error) {\n logger.error(\n `[WorkerPool] Failed to create worker ${i}:`,\n error instanceof Error ? error.message : String(error),\n );\n }\n }\n if (this.workers.length === 0) {\n logger.error(\n `[WorkerPool] Failed to create any workers. URL: ${this.workerUrl}`,\n );\n logger.error(`[WorkerPool] Browser support check:`, {\n Worker: typeof Worker !== \"undefined\",\n OffscreenCanvas: typeof OffscreenCanvas !== \"undefined\",\n createImageBitmap: typeof createImageBitmap !== \"undefined\",\n });\n }\n }\n\n /**\n * Get the number of workers in the pool.\n */\n get workerCount(): number {\n return this.workers.length;\n }\n\n /**\n * Check if workers are available and initialized.\n */\n isAvailable(): boolean {\n return (\n this.hasBrowserSupport() && this.workers.length > 0 && !this.isTerminated\n );\n }\n\n /**\n * Execute a task using an available worker from the pool.\n */\n async execute<T>(task: (worker: Worker) => Promise<T>): Promise<T> {\n if (this.isTerminated) {\n throw new Error(\"WorkerPool has been terminated\");\n }\n\n // If workers aren't available, this will be handled by the caller's fallback\n if (!this.isAvailable()) {\n throw new Error(\"Workers not available\");\n }\n\n return new Promise<T>((resolve, reject) => {\n this.taskQueue.push({\n resolve: resolve as (value: unknown) => void,\n reject,\n task,\n });\n this.processQueue();\n });\n }\n\n private processQueue(): void {\n // Process tasks while we have available workers and queued tasks\n while (this.availableWorkers.length > 0 && this.taskQueue.length > 0) {\n const worker = this.availableWorkers.shift();\n const queuedTask = this.taskQueue.shift();\n\n if (!worker || !queuedTask) {\n // Safety check - should not happen but prevents crashes\n break;\n }\n\n const { resolve, reject, task } = queuedTask;\n\n // Execute the task\n task(worker)\n .then((result) => {\n resolve(result);\n // Return worker to pool\n this.availableWorkers.push(worker);\n // Process next task\n this.processQueue();\n })\n .catch((error) => {\n reject(error instanceof Error ? error : new Error(String(error)));\n // Return worker to pool\n this.availableWorkers.push(worker);\n // Process next task\n this.processQueue();\n });\n }\n }\n\n /**\n * Terminate all workers and clear the task queue.\n */\n terminate(): void {\n this.isTerminated = true;\n\n // Reject all pending tasks\n for (const { reject } of this.taskQueue) {\n reject(new Error(\"WorkerPool terminated\"));\n }\n this.taskQueue = [];\n\n // Terminate all workers\n for (const worker of this.workers) {\n worker.terminate();\n }\n this.workers = [];\n this.availableWorkers = [];\n }\n}\n"],"mappings":"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"}
|
|
@@ -6,77 +6,44 @@
|
|
|
6
6
|
* from a separate file.
|
|
7
7
|
*/
|
|
8
8
|
const workerCode = `
|
|
9
|
-
/**
|
|
10
|
-
* Fast base64 encoding directly from Uint8Array.
|
|
11
|
-
*/
|
|
12
|
-
function encodeBase64Fast(bytes) {
|
|
13
|
-
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
14
|
-
let result = "";
|
|
15
|
-
let i = 0;
|
|
16
|
-
const len = bytes.length;
|
|
17
|
-
|
|
18
|
-
while (i < len - 2) {
|
|
19
|
-
const byte1 = bytes[i++];
|
|
20
|
-
const byte2 = bytes[i++];
|
|
21
|
-
const byte3 = bytes[i++];
|
|
22
|
-
|
|
23
|
-
const bitmap = (byte1 << 16) | (byte2 << 8) | byte3;
|
|
24
|
-
|
|
25
|
-
result += base64Chars.charAt((bitmap >> 18) & 63);
|
|
26
|
-
result += base64Chars.charAt((bitmap >> 12) & 63);
|
|
27
|
-
result += base64Chars.charAt((bitmap >> 6) & 63);
|
|
28
|
-
result += base64Chars.charAt(bitmap & 63);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (i < len) {
|
|
32
|
-
const byte1 = bytes[i++];
|
|
33
|
-
const bitmap = byte1 << 16;
|
|
34
|
-
|
|
35
|
-
result += base64Chars.charAt((bitmap >> 18) & 63);
|
|
36
|
-
result += base64Chars.charAt((bitmap >> 12) & 63);
|
|
37
|
-
|
|
38
|
-
if (i < len) {
|
|
39
|
-
const byte2 = bytes[i++];
|
|
40
|
-
const bitmap2 = (byte1 << 16) | (byte2 << 8);
|
|
41
|
-
result += base64Chars.charAt((bitmap2 >> 6) & 63);
|
|
42
|
-
result += "=";
|
|
43
|
-
} else {
|
|
44
|
-
result += "==";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
9
|
// Send a startup message to confirm worker is loaded
|
|
52
10
|
postMessage("encoderWorker-loaded");
|
|
53
11
|
|
|
54
12
|
// Use addEventListener for better compatibility
|
|
55
13
|
addEventListener("message", async (event) => {
|
|
56
|
-
const { taskId, bitmap, preserveAlpha } = event.data;
|
|
57
|
-
|
|
14
|
+
const { taskId, bitmap, preserveAlpha, targetWidth, targetHeight } = event.data;
|
|
15
|
+
|
|
58
16
|
try {
|
|
59
|
-
|
|
17
|
+
// Resize to target dimensions in worker (ARCHITECTURE.md §2.4)
|
|
18
|
+
const w = targetWidth || bitmap.width;
|
|
19
|
+
const h = targetHeight || bitmap.height;
|
|
20
|
+
const canvas = new OffscreenCanvas(w, h);
|
|
60
21
|
const ctx = canvas.getContext("2d");
|
|
61
|
-
|
|
22
|
+
|
|
62
23
|
if (!ctx) {
|
|
63
24
|
throw new Error("Failed to get 2d context from OffscreenCanvas");
|
|
64
25
|
}
|
|
65
|
-
|
|
66
|
-
ctx.drawImage(bitmap, 0, 0);
|
|
26
|
+
|
|
27
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
67
28
|
bitmap.close();
|
|
68
|
-
|
|
29
|
+
|
|
69
30
|
const blob = await canvas.convertToBlob({
|
|
70
31
|
type: preserveAlpha ? "image/png" : "image/jpeg",
|
|
71
32
|
quality: preserveAlpha ? undefined : 0.95,
|
|
72
33
|
});
|
|
73
|
-
|
|
34
|
+
|
|
35
|
+
// ARCHITECTURE.md §4.4: chunked String.fromCharCode + native btoa
|
|
36
|
+
// Native btoa is implemented in C++ and ~100x faster than JS-based
|
|
37
|
+
// base64 encoding for large payloads (e.g. 2MB PNG blobs).
|
|
74
38
|
const arrayBuffer = await blob.arrayBuffer();
|
|
75
39
|
const bytes = new Uint8Array(arrayBuffer);
|
|
76
|
-
|
|
40
|
+
let binary = "";
|
|
41
|
+
for (let i = 0; i < bytes.length; i += 8192) {
|
|
42
|
+
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
43
|
+
}
|
|
77
44
|
const mimeType = preserveAlpha ? "image/png" : "image/jpeg";
|
|
78
|
-
const dataUrl =
|
|
79
|
-
|
|
45
|
+
const dataUrl = "data:" + mimeType + ";base64," + btoa(binary);
|
|
46
|
+
|
|
80
47
|
postMessage({ taskId, dataUrl });
|
|
81
48
|
} catch (error) {
|
|
82
49
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -88,7 +55,7 @@ let cachedBlobUrl = null;
|
|
|
88
55
|
/**
|
|
89
56
|
* Creates a blob URL for the encoder worker.
|
|
90
57
|
* The blob URL is cached so multiple calls return the same URL.
|
|
91
|
-
*
|
|
58
|
+
*
|
|
92
59
|
* @returns The blob URL that can be passed to `new Worker(url, { type: "module" })`
|
|
93
60
|
*/
|
|
94
61
|
function getEncoderWorkerUrl() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encoderWorkerInline.js","names":["cachedBlobUrl: string | null"],"sources":["../../../src/preview/workers/encoderWorkerInline.ts"],"sourcesContent":["/**\n * Inline encoder worker - creates a blob URL from inlined worker code.\n * This approach works in any bundler environment (Vite, webpack, etc.)\n * because the worker code is embedded in the bundle rather than loaded\n * from a separate file.\n */\n\n// The worker code as a string. This is the same logic as encoderWorker.ts\n// but inlined so it can be converted to a blob URL at runtime.\nconst workerCode = `\n
|
|
1
|
+
{"version":3,"file":"encoderWorkerInline.js","names":["cachedBlobUrl: string | null"],"sources":["../../../src/preview/workers/encoderWorkerInline.ts"],"sourcesContent":["/**\n * Inline encoder worker - creates a blob URL from inlined worker code.\n * This approach works in any bundler environment (Vite, webpack, etc.)\n * because the worker code is embedded in the bundle rather than loaded\n * from a separate file.\n */\n\n// The worker code as a string. This is the same logic as encoderWorker.ts\n// but inlined so it can be converted to a blob URL at runtime.\nconst workerCode = `\n// Send a startup message to confirm worker is loaded\npostMessage(\"encoderWorker-loaded\");\n\n// Use addEventListener for better compatibility\naddEventListener(\"message\", async (event) => {\n const { taskId, bitmap, preserveAlpha, targetWidth, targetHeight } = event.data;\n\n try {\n // Resize to target dimensions in worker (ARCHITECTURE.md §2.4)\n const w = targetWidth || bitmap.width;\n const h = targetHeight || bitmap.height;\n const canvas = new OffscreenCanvas(w, h);\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n throw new Error(\"Failed to get 2d context from OffscreenCanvas\");\n }\n\n ctx.drawImage(bitmap, 0, 0, w, h);\n bitmap.close();\n\n const blob = await canvas.convertToBlob({\n type: preserveAlpha ? \"image/png\" : \"image/jpeg\",\n quality: preserveAlpha ? undefined : 0.95,\n });\n\n // ARCHITECTURE.md §4.4: chunked String.fromCharCode + native btoa\n // Native btoa is implemented in C++ and ~100x faster than JS-based\n // base64 encoding for large payloads (e.g. 2MB PNG blobs).\n const arrayBuffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(arrayBuffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += 8192) {\n binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));\n }\n const mimeType = preserveAlpha ? \"image/png\" : \"image/jpeg\";\n const dataUrl = \"data:\" + mimeType + \";base64,\" + btoa(binary);\n\n postMessage({ taskId, dataUrl });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n postMessage({ taskId, dataUrl: \"\", error: errorMessage });\n }\n});\n`;\n\n// Cache the blob URL so we only create it once\nlet cachedBlobUrl: string | null = null;\n\n/**\n * Creates a blob URL for the encoder worker.\n * The blob URL is cached so multiple calls return the same URL.\n *\n * @returns The blob URL that can be passed to `new Worker(url, { type: \"module\" })`\n */\nexport function getEncoderWorkerUrl(): string {\n if (cachedBlobUrl) {\n return cachedBlobUrl;\n }\n\n const blob = new Blob([workerCode], { type: \"application/javascript\" });\n cachedBlobUrl = URL.createObjectURL(blob);\n return cachedBlobUrl;\n}\n\n/**\n * Revokes the cached blob URL to free memory.\n * Call this when you're done with all workers (e.g., during cleanup).\n */\nexport function revokeEncoderWorkerUrl(): void {\n if (cachedBlobUrl) {\n URL.revokeObjectURL(cachedBlobUrl);\n cachedBlobUrl = null;\n }\n}\n"],"mappings":";;;;;;;AASA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDnB,IAAIA,gBAA+B;;;;;;;AAQnC,SAAgB,sBAA8B;AAC5C,KAAI,cACF,QAAO;CAGT,MAAM,OAAO,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACvE,iBAAgB,IAAI,gBAAgB,KAAK;AACzC,QAAO"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { RenderProgress, RenderToVideoOptions } from "../preview/renderTimegroupToVideo.js";
|
|
1
|
+
import { RenderProgress, RenderToVideoOptions } from "../preview/renderTimegroupToVideo.types.js";
|
|
2
|
+
import { RenderInfo } from "../getRenderInfo.js";
|
|
2
3
|
|
|
3
4
|
//#region src/render/EFRenderAPI.d.ts
|
|
4
5
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { renderTimegroupToVideo } from "../preview/renderTimegroupToVideo.js";
|
|
2
1
|
import { getRenderInfo } from "../getRenderInfo.js";
|
|
3
2
|
|
|
4
3
|
//#region src/render/EFRenderAPI.ts
|
|
@@ -15,47 +14,23 @@ function setWorkbenchRendering(rendering) {
|
|
|
15
14
|
if (workbench) workbench.rendering = rendering;
|
|
16
15
|
}
|
|
17
16
|
async function waitForTimegroupDimensions(timegroup) {
|
|
18
|
-
console.log("[EFRenderAPI] Waiting for stylesheets to load...");
|
|
19
|
-
console.log(`[EFRenderAPI] Found ${document.styleSheets.length} stylesheets`);
|
|
20
|
-
const styleLinks = Array.from(document.querySelectorAll("link[rel=\"stylesheet\"]"));
|
|
21
|
-
console.log(`[EFRenderAPI] Found ${styleLinks.length} stylesheet <link> elements`);
|
|
22
|
-
styleLinks.forEach((link, i) => {
|
|
23
|
-
const href = link.href;
|
|
24
|
-
const sheet = link.sheet;
|
|
25
|
-
console.log(`[EFRenderAPI] [${i}] ${href}`);
|
|
26
|
-
try {
|
|
27
|
-
const rulesCount = sheet ? sheet.cssRules.length : 0;
|
|
28
|
-
console.log(`[EFRenderAPI] loaded: ${!!sheet}, rules: ${rulesCount}`);
|
|
29
|
-
if (sheet && sheet.cssRules.length > 0) {
|
|
30
|
-
const firstRules = Array.from(sheet.cssRules).slice(0, 5).map((r) => r.cssText.substring(0, 100));
|
|
31
|
-
console.log(`[EFRenderAPI] first rules:`, firstRules);
|
|
32
|
-
const hasWidthClass = Array.from(sheet.cssRules).some((r) => r.cssText.includes("w-\\[1080px\\]") || r.cssText.includes("width: 1080px"));
|
|
33
|
-
console.log(`[EFRenderAPI] has w-[1080px] class: ${hasWidthClass}`);
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
console.log(`[EFRenderAPI] Error reading stylesheet rules:`, e);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
17
|
await Promise.all(Array.from(document.styleSheets).map((sheet) => {
|
|
40
18
|
if (sheet.href) {
|
|
41
19
|
const link = Array.from(document.querySelectorAll("link[rel=\"stylesheet\"]")).find((l) => l.href === sheet.href);
|
|
42
|
-
if (link && !link.sheet) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
link.addEventListener("error", resolve);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
20
|
+
if (link && !link.sheet) return new Promise((resolve) => {
|
|
21
|
+
link.addEventListener("load", resolve);
|
|
22
|
+
link.addEventListener("error", resolve);
|
|
23
|
+
});
|
|
49
24
|
}
|
|
50
25
|
return Promise.resolve();
|
|
51
26
|
}));
|
|
52
27
|
timegroup.offsetHeight;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
const rect = timegroup.getBoundingClientRect();
|
|
29
|
+
const hasOffset = timegroup.offsetWidth > 0 && timegroup.offsetHeight > 0;
|
|
30
|
+
const hasRect = rect.width > 0 && rect.height > 0;
|
|
31
|
+
const computedWidth = getComputedStyle(timegroup).width;
|
|
32
|
+
const computedHeight = getComputedStyle(timegroup).height;
|
|
33
|
+
if (!hasOffset && !hasRect && !(parseFloat(computedWidth) > 0 && parseFloat(computedHeight) > 0)) throw new Error(`Timegroup has no dimensions (${timegroup.offsetWidth}x${timegroup.offsetHeight}). Computed styles: width=${computedWidth}, height=${computedHeight}. Classes: "${timegroup.className}". \n\nTailwind CSS did not generate styles for these classes. Check that:\n1. Your Tailwind config 'content' array includes the HTML file\n2. Tailwind CSS is properly configured in your project\n3. The dev server successfully compiled CSS (check for Tailwind warnings above)`);
|
|
59
34
|
}
|
|
60
35
|
const api = {
|
|
61
36
|
async renderStreaming(options = {}) {
|
|
@@ -67,10 +42,10 @@ const api = {
|
|
|
67
42
|
await waitForTimegroupDimensions(timegroup);
|
|
68
43
|
await timegroup.waitForMediaDurations();
|
|
69
44
|
const chunkWriter = new WritableStream({ write(chunk) {
|
|
70
|
-
console.error("Writing chunk", chunk);
|
|
71
45
|
if (window.onRenderChunk) window.onRenderChunk(chunk);
|
|
72
46
|
} });
|
|
73
47
|
const onProgress = options.onProgress || window.onRenderProgress;
|
|
48
|
+
const { renderTimegroupToVideo } = await import("../preview/renderTimegroupToVideo.js");
|
|
74
49
|
await renderTimegroupToVideo(timegroup, {
|
|
75
50
|
...options,
|
|
76
51
|
customWritableStream: chunkWriter,
|
|
@@ -89,6 +64,7 @@ const api = {
|
|
|
89
64
|
await waitForTimegroupDimensions(timegroup);
|
|
90
65
|
await timegroup.waitForMediaDurations();
|
|
91
66
|
const onProgress = options.onProgress || window.onRenderProgress;
|
|
67
|
+
const { renderTimegroupToVideo } = await import("../preview/renderTimegroupToVideo.js");
|
|
92
68
|
const buffer = await renderTimegroupToVideo(timegroup, {
|
|
93
69
|
...options,
|
|
94
70
|
returnBuffer: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFRenderAPI.js","names":["api: IEFRenderAPI"],"sources":["../../src/render/EFRenderAPI.ts"],"sourcesContent":["/**\n * Window API for programmatic video rendering.\n * \n * Exposes renderTimegroupToVideo for use from Playwright/CLI.\n * Supports streaming output and custom data injection.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFWorkbench } from \"../gui/EFWorkbench.js\";\nimport { getRenderInfo, type RenderInfo } from \"../getRenderInfo.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n type RenderProgress,\n} from \"../preview/renderTimegroupToVideo.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IEFRenderAPI {\n /**\n * Render with streaming output (calls window.onRenderChunk for each chunk).\n * Use this for CLI/Playwright to avoid memory buffering.\n */\n renderStreaming(options?: RenderToVideoOptions): Promise<void>;\n\n /**\n * Render and return buffer (for shorter videos or in-browser use).\n * Returns the video as Uint8Array.\n */\n render(options?: RenderToVideoOptions): Promise<Uint8Array>;\n\n /**\n * Get render info (dimensions, duration, assets).\n * Same as the exported getRenderInfo function.\n */\n getRenderInfo(): Promise<RenderInfo>;\n\n /**\n * Check if SDK is ready for rendering.\n * Returns true if a root timegroup is found.\n */\n isReady(): boolean;\n}\n\ndeclare global {\n interface Window {\n EF_RENDER?: IEFRenderAPI;\n EF_RENDER_DATA?: Record<string, unknown>;\n onRenderChunk?: (chunk: Uint8Array) => void; // Set by Playwright\n onRenderProgress?: (progress: RenderProgress) => void; // Optional progress callback\n }\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction findRootTimegroup(): EFTimegroup | null {\n // Try to find timegroup from workbench first\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n const timegroup = workbench.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n if (timegroup) {\n return timegroup;\n }\n }\n\n // Fallback: find first root timegroup\n const rootTimegroup = document.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n return rootTimegroup;\n}\n\nfunction setWorkbenchRendering(rendering: boolean): void {\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n workbench.rendering = rendering;\n }\n}\n\nasync function waitForTimegroupDimensions(timegroup: EFTimegroup): Promise<void> {\n // Wait for all stylesheets to load first\n console.log('[EFRenderAPI] Waiting for stylesheets to load...');\n console.log(`[EFRenderAPI] Found ${document.styleSheets.length} stylesheets`);\n \n const styleLinks = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]'));\n console.log(`[EFRenderAPI] Found ${styleLinks.length} stylesheet <link> elements`);\n styleLinks.forEach((link, i) => {\n const href = (link as HTMLLinkElement).href;\n const sheet = (link as HTMLLinkElement).sheet;\n console.log(`[EFRenderAPI] [${i}] ${href}`);\n try {\n const rulesCount = sheet ? sheet.cssRules.length : 0;\n console.log(`[EFRenderAPI] loaded: ${!!sheet}, rules: ${rulesCount}`);\n if (sheet && sheet.cssRules.length > 0) {\n // Log first few rules to see what CSS is loaded\n const firstRules = Array.from(sheet.cssRules).slice(0, 5).map(r => r.cssText.substring(0, 100));\n console.log(`[EFRenderAPI] first rules:`, firstRules);\n \n // Search for the specific Tailwind classes we need\n const hasWidthClass = Array.from(sheet.cssRules).some(r => \n r.cssText.includes('w-\\\\[1080px\\\\]') || r.cssText.includes('width: 1080px')\n );\n console.log(`[EFRenderAPI] has w-[1080px] class: ${hasWidthClass}`);\n }\n } catch (e) {\n console.log(`[EFRenderAPI] Error reading stylesheet rules:`, e);\n }\n });\n \n await Promise.all(\n Array.from(document.styleSheets).map((sheet) => {\n if (sheet.href) {\n // Check if stylesheet is from a <link> tag and wait for it\n const link = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]')).find(\n (l) => (l as HTMLLinkElement).href === sheet.href\n );\n if (link && !(link as HTMLLinkElement).sheet) {\n console.log(`[EFRenderAPI] Waiting for stylesheet: ${sheet.href}`);\n return new Promise((resolve) => {\n link.addEventListener('load', resolve);\n link.addEventListener('error', resolve);\n });\n }\n }\n return Promise.resolve();\n })\n );\n \n // Force layout immediately after stylesheets load\n void timegroup.offsetHeight;\n \n if (!timegroup.offsetWidth || !timegroup.offsetHeight) {\n const computedWidth = getComputedStyle(timegroup).width;\n const computedHeight = getComputedStyle(timegroup).height;\n \n throw new Error(\n `Timegroup has no dimensions (${timegroup.offsetWidth}x${timegroup.offsetHeight}). ` +\n `Computed styles: width=${computedWidth}, height=${computedHeight}. ` +\n `Classes: \"${timegroup.className}\". ` +\n `\\n\\nTailwind CSS did not generate styles for these classes. ` +\n `Check that:\\n` +\n `1. Your Tailwind config 'content' array includes the HTML file\\n` +\n `2. Tailwind CSS is properly configured in your project\\n` +\n `3. The dev server successfully compiled CSS (check for Tailwind warnings above)`\n );\n }\n \n console.log(`[EFRenderAPI] Timegroup dimensions ready: ${timegroup.offsetWidth}x${timegroup.offsetHeight}`);\n}\n\nconst api: IEFRenderAPI = {\n async renderStreaming(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Check if window.onRenderChunk is available\n if (typeof window === \"undefined\" || !window.onRenderChunk) {\n throw new Error(\n \"window.onRenderChunk is not set. \" +\n \"Call page.exposeFunction('onRenderChunk', callback) from Playwright first.\"\n );\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n \n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Create custom writable stream that calls window.onRenderChunk\n const chunkWriter = new WritableStream<Uint8Array>({\n write(chunk: Uint8Array) {\n console.error(\"Writing chunk\", chunk);\n if (window.onRenderChunk) {\n window.onRenderChunk(chunk);\n }\n },\n });\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Render with custom stream\n await renderTimegroupToVideo(timegroup, {\n ...options,\n customWritableStream: chunkWriter,\n onProgress,\n returnBuffer: false,\n });\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async render(options: RenderToVideoOptions = {}): Promise<Uint8Array> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n \n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n const buffer = await renderTimegroupToVideo(timegroup, {\n ...options,\n returnBuffer: true,\n onProgress,\n });\n\n if (!buffer) {\n throw new Error(\"Render failed: no buffer returned\");\n }\n\n return buffer;\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async getRenderInfo(): Promise<RenderInfo> {\n return getRenderInfo();\n },\n\n isReady(): boolean {\n return findRootTimegroup() !== null;\n },\n};\n\n// Export and register on window\nif (typeof window !== \"undefined\") {\n window.EF_RENDER = api;\n}\n\nexport { api as EFRenderAPI };\nexport type { IEFRenderAPI as EFRenderAPIInterface };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFRenderAPI.js","names":["api: IEFRenderAPI"],"sources":["../../src/render/EFRenderAPI.ts"],"sourcesContent":["/**\n * Window API for programmatic video rendering.\n *\n * Exposes renderTimegroupToVideo for use from Playwright/CLI.\n * Supports streaming output and custom data injection.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFWorkbench } from \"../gui/EFWorkbench.js\";\nimport { getRenderInfo, type RenderInfo } from \"../getRenderInfo.js\";\n// Import only types - actual function loaded dynamically\nimport type {\n RenderToVideoOptions,\n RenderProgress,\n} from \"../preview/renderTimegroupToVideo.types.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IEFRenderAPI {\n /**\n * Render with streaming output (calls window.onRenderChunk for each chunk).\n * Use this for CLI/Playwright to avoid memory buffering.\n */\n renderStreaming(options?: RenderToVideoOptions): Promise<void>;\n\n /**\n * Render and return buffer (for shorter videos or in-browser use).\n * Returns the video as Uint8Array.\n */\n render(options?: RenderToVideoOptions): Promise<Uint8Array>;\n\n /**\n * Get render info (dimensions, duration, assets).\n * Same as the exported getRenderInfo function.\n */\n getRenderInfo(): Promise<RenderInfo>;\n\n /**\n * Check if SDK is ready for rendering.\n * Returns true if a root timegroup is found.\n */\n isReady(): boolean;\n}\n\ndeclare global {\n interface Window {\n EF_RENDER?: IEFRenderAPI;\n EF_RENDER_DATA?: Record<string, unknown>;\n onRenderChunk?: (chunk: Uint8Array) => void; // Set by Playwright\n onRenderProgress?: (progress: RenderProgress) => void; // Optional progress callback\n }\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction findRootTimegroup(): EFTimegroup | null {\n // Try to find timegroup from workbench first\n const workbench = document.querySelector(\n \"ef-workbench\",\n ) as EFWorkbench | null;\n if (workbench) {\n const timegroup = workbench.querySelector(\n \"ef-timegroup\",\n ) as EFTimegroup | null;\n if (timegroup) {\n return timegroup;\n }\n }\n\n // Fallback: find first root timegroup\n const rootTimegroup = document.querySelector(\n \"ef-timegroup\",\n ) as EFTimegroup | null;\n return rootTimegroup;\n}\n\nfunction setWorkbenchRendering(rendering: boolean): void {\n const workbench = document.querySelector(\n \"ef-workbench\",\n ) as EFWorkbench | null;\n if (workbench) {\n workbench.rendering = rendering;\n }\n}\n\nasync function waitForTimegroupDimensions(\n timegroup: EFTimegroup,\n): Promise<void> {\n await Promise.all(\n Array.from(document.styleSheets).map((sheet) => {\n if (sheet.href) {\n const link = Array.from(\n document.querySelectorAll('link[rel=\"stylesheet\"]'),\n ).find((l) => (l as HTMLLinkElement).href === sheet.href);\n if (link && !(link as HTMLLinkElement).sheet) {\n return new Promise((resolve) => {\n link.addEventListener(\"load\", resolve);\n link.addEventListener(\"error\", resolve);\n });\n }\n }\n return Promise.resolve();\n }),\n );\n\n // Force layout immediately after stylesheets load\n void timegroup.offsetHeight;\n\n const rect = timegroup.getBoundingClientRect();\n const hasOffset = timegroup.offsetWidth > 0 && timegroup.offsetHeight > 0;\n const hasRect = rect.width > 0 && rect.height > 0;\n const computedWidth = getComputedStyle(timegroup).width;\n const computedHeight = getComputedStyle(timegroup).height;\n const hasComputed =\n parseFloat(computedWidth) > 0 && parseFloat(computedHeight) > 0;\n\n if (!hasOffset && !hasRect && !hasComputed) {\n throw new Error(\n `Timegroup has no dimensions (${timegroup.offsetWidth}x${timegroup.offsetHeight}). ` +\n `Computed styles: width=${computedWidth}, height=${computedHeight}. ` +\n `Classes: \"${timegroup.className}\". ` +\n `\\n\\nTailwind CSS did not generate styles for these classes. ` +\n `Check that:\\n` +\n `1. Your Tailwind config 'content' array includes the HTML file\\n` +\n `2. Tailwind CSS is properly configured in your project\\n` +\n `3. The dev server successfully compiled CSS (check for Tailwind warnings above)`,\n );\n }\n}\n\nconst api: IEFRenderAPI = {\n async renderStreaming(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Check if window.onRenderChunk is available\n if (typeof window === \"undefined\" || !window.onRenderChunk) {\n throw new Error(\n \"window.onRenderChunk is not set. \" +\n \"Call page.exposeFunction('onRenderChunk', callback) from Playwright first.\",\n );\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Create custom writable stream that calls window.onRenderChunk\n const chunkWriter = new WritableStream<Uint8Array>({\n write(chunk: Uint8Array) {\n if (window.onRenderChunk) {\n window.onRenderChunk(chunk);\n }\n },\n });\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Render with custom stream\n // Dynamic import to avoid loading render utilities during module initialization\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n await renderTimegroupToVideo(timegroup, {\n ...options,\n customWritableStream: chunkWriter,\n onProgress,\n returnBuffer: false,\n });\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async render(options: RenderToVideoOptions = {}): Promise<Uint8Array> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Dynamic import to avoid loading render utilities during module initialization\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n const buffer = await renderTimegroupToVideo(timegroup, {\n ...options,\n returnBuffer: true,\n onProgress,\n });\n\n if (!buffer) {\n throw new Error(\"Render failed: no buffer returned\");\n }\n\n return buffer;\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async getRenderInfo(): Promise<RenderInfo> {\n return getRenderInfo();\n },\n\n isReady(): boolean {\n return findRootTimegroup() !== null;\n },\n};\n\n// Export and register on window\nif (typeof window !== \"undefined\") {\n window.EF_RENDER = api;\n}\n\nexport { api as EFRenderAPI };\nexport type { IEFRenderAPI as EFRenderAPIInterface };\n"],"mappings":";;;AA2DA,SAAS,oBAAwC;CAE/C,MAAM,YAAY,SAAS,cACzB,eACD;AACD,KAAI,WAAW;EACb,MAAM,YAAY,UAAU,cAC1B,eACD;AACD,MAAI,UACF,QAAO;;AAQX,QAHsB,SAAS,cAC7B,eACD;;AAIH,SAAS,sBAAsB,WAA0B;CACvD,MAAM,YAAY,SAAS,cACzB,eACD;AACD,KAAI,UACF,WAAU,YAAY;;AAI1B,eAAe,2BACb,WACe;AACf,OAAM,QAAQ,IACZ,MAAM,KAAK,SAAS,YAAY,CAAC,KAAK,UAAU;AAC9C,MAAI,MAAM,MAAM;GACd,MAAM,OAAO,MAAM,KACjB,SAAS,iBAAiB,2BAAyB,CACpD,CAAC,MAAM,MAAO,EAAsB,SAAS,MAAM,KAAK;AACzD,OAAI,QAAQ,CAAE,KAAyB,MACrC,QAAO,IAAI,SAAS,YAAY;AAC9B,SAAK,iBAAiB,QAAQ,QAAQ;AACtC,SAAK,iBAAiB,SAAS,QAAQ;KACvC;;AAGN,SAAO,QAAQ,SAAS;GACxB,CACH;AAGD,CAAK,UAAU;CAEf,MAAM,OAAO,UAAU,uBAAuB;CAC9C,MAAM,YAAY,UAAU,cAAc,KAAK,UAAU,eAAe;CACxE,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,SAAS;CAChD,MAAM,gBAAgB,iBAAiB,UAAU,CAAC;CAClD,MAAM,iBAAiB,iBAAiB,UAAU,CAAC;AAInD,KAAI,CAAC,aAAa,CAAC,WAAW,EAF5B,WAAW,cAAc,GAAG,KAAK,WAAW,eAAe,GAAG,GAG9D,OAAM,IAAI,MACR,gCAAgC,UAAU,YAAY,GAAG,UAAU,aAAa,4BACpD,cAAc,WAAW,eAAe,cACrD,UAAU,UAAU,qRAMpC;;AAIL,MAAMA,MAAoB;CACxB,MAAM,gBAAgB,UAAgC,EAAE,EAAiB;EACvE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,cAC3C,OAAM,IAAI,MACR,8GAED;AAIH,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,2BAA2B,UAAU;AAG3C,SAAM,UAAU,uBAAuB;GAGvC,MAAM,cAAc,IAAI,eAA2B,EACjD,MAAM,OAAmB;AACvB,QAAI,OAAO,cACT,QAAO,cAAc,MAAM;MAGhC,CAAC;GAGF,MAAM,aAAa,QAAQ,cAAc,OAAO;GAIhD,MAAM,EAAE,2BACN,MAAM,OAAO;AACf,SAAM,uBAAuB,WAAW;IACtC,GAAG;IACH,sBAAsB;IACtB;IACA,cAAc;IACf,CAAC;YACM;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,OAAO,UAAgC,EAAE,EAAuB;EACpE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,2BAA2B,UAAU;AAG3C,SAAM,UAAU,uBAAuB;GAGvC,MAAM,aAAa,QAAQ,cAAc,OAAO;GAGhD,MAAM,EAAE,2BACN,MAAM,OAAO;GACf,MAAM,SAAS,MAAM,uBAAuB,WAAW;IACrD,GAAG;IACH,cAAc;IACd;IACD,CAAC;AAEF,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,oCAAoC;AAGtD,UAAO;YACC;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,gBAAqC;AACzC,SAAO,eAAe;;CAGxB,UAAmB;AACjB,SAAO,mBAAmB,KAAK;;CAElC;AAGD,IAAI,OAAO,WAAW,YACpB,QAAO,YAAY"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
//#region src/render/getRenderData.ts
|
|
2
2
|
/**
|
|
3
3
|
* Get custom render data that was passed to the render process.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* @returns The render data object, or undefined if no data was provided
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
9
|
* import { getRenderData } from "@editframe/elements";
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* interface MyRenderData {
|
|
12
12
|
* userName: string;
|
|
13
13
|
* theme: "light" | "dark";
|
|
14
14
|
* }
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* const data = getRenderData<MyRenderData>();
|
|
17
17
|
* if (data) {
|
|
18
18
|
* console.log(data.userName); // "John"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getRenderData.js","names":[],"sources":["../../src/render/getRenderData.ts"],"sourcesContent":["/**\n * Helper function for compositions to read custom render data.\n
|
|
1
|
+
{"version":3,"file":"getRenderData.js","names":[],"sources":["../../src/render/getRenderData.ts"],"sourcesContent":["/**\n * Helper function for compositions to read custom render data.\n *\n * Supports both runtime data (set by Playwright/CLI via window.EF_RENDER_DATA)\n * and build-time data (set by Vite define as RENDER_DATA).\n */\n\n// Declare RENDER_DATA for TypeScript (set via Vite define at build time)\ndeclare const RENDER_DATA: unknown | undefined;\n\n/**\n * Get custom render data that was passed to the render process.\n *\n * @returns The render data object, or undefined if no data was provided\n *\n * @example\n * ```typescript\n * import { getRenderData } from \"@editframe/elements\";\n *\n * interface MyRenderData {\n * userName: string;\n * theme: \"light\" | \"dark\";\n * }\n *\n * const data = getRenderData<MyRenderData>();\n * if (data) {\n * console.log(data.userName); // \"John\"\n * }\n * ```\n */\nexport function getRenderData<T = unknown>(): T | undefined {\n // Runtime data (set by Playwright/CLI)\n if (typeof window !== \"undefined\" && window.EF_RENDER_DATA) {\n return window.EF_RENDER_DATA as T;\n }\n\n // Build-time data (set by Vite define)\n if (typeof RENDER_DATA !== \"undefined\") {\n return RENDER_DATA as T;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,gBAA4C;AAE1D,KAAI,OAAO,WAAW,eAAe,OAAO,eAC1C,QAAO,OAAO;AAIhB,KAAI,OAAO,gBAAgB,YACzB,QAAO"}
|