@editframe/elements 0.33.0-beta → 0.34.6-beta
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 +5 -3
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.94.0 → _@oxc-project_runtime@0.95.0}/helpers/decorate.js +1 -1
- package/dist/canvas/EFCanvas.d.ts +7 -4
- package/dist/canvas/EFCanvas.js +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/EFCanvasItem.js +1 -1
- package/dist/canvas/overlays/SelectionOverlay.d.ts +95 -0
- package/dist/canvas/overlays/SelectionOverlay.js +1 -1
- package/dist/canvas/selection/SelectionController.js +7 -11
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +25 -7
- package/dist/elements/EFAudio.js +31 -61
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +65 -52
- package/dist/elements/EFCaptions.js +186 -400
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +34 -6
- package/dist/elements/EFImage.js +114 -79
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +17 -17
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +41 -25
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +4 -4
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +31 -17
- 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/videoTasks/ScrubInputCache.js +17 -9
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +66 -20
- package/dist/elements/EFMedia.js +412 -30
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +4 -4
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFSourceMixin.js +43 -15
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +23 -10
- package/dist/elements/EFSurface.js +64 -22
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +8 -2
- package/dist/elements/EFTemporal.js +42 -31
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +11 -2
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFTextSegment.js +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +4 -4
- package/dist/elements/EFThumbnailStrip.js +1 -1
- package/dist/elements/EFTimegroup.d.ts +22 -8
- package/dist/elements/EFTimegroup.js +203 -115
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +57 -20
- package/dist/elements/EFVideo.js +324 -72
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +33 -7
- package/dist/elements/EFWaveform.js +103 -59
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/renderTemporalAudio.js +14 -3
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +1 -1
- package/dist/gui/Controllable.d.ts +2 -0
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFConfiguration.js +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +1 -1
- package/dist/gui/EFFilmstrip.d.ts +3 -2
- package/dist/gui/EFFilmstrip.js +1 -1
- package/dist/gui/EFFitScale.js +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.js +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayItem.js +1 -1
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.js +1 -1
- 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.d.ts +4 -4
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js +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 +1 -1
- package/dist/gui/EFWorkbench.d.ts +5 -4
- package/dist/gui/EFWorkbench.js +1 -1
- package/dist/gui/PlaybackController.d.ts +10 -2
- package/dist/gui/PlaybackController.js +52 -30
- 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 +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
- package/dist/gui/hierarchy/EFHierarchy.js +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +1 -1
- package/dist/gui/timeline/EFTimeline.d.ts +6 -2
- package/dist/gui/timeline/EFTimeline.js +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +57 -0
- package/dist/gui/timeline/EFTimelineRow.js +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +4 -4
- package/dist/gui/timeline/TrimHandles.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.d.ts +2 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +58 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +1 -1
- package/dist/gui/timeline/tracks/HTMLTrack.d.ts +13 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.d.ts +14 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +26 -0
- package/dist/gui/timeline/tracks/TextTrack.js +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +47 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +4 -12
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +81 -0
- package/dist/gui/timeline/tracks/TrackItem.js +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +25 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.d.ts +14 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +1 -1
- package/dist/gui/timeline/tracks/ensureTrackItemInit.d.ts +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.d.ts +9 -0
- package/dist/gui/tree/EFTree.d.ts +5 -4
- package/dist/gui/tree/EFTree.js +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/preview/AdaptiveResolutionTracker.js +6 -14
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +123 -0
- package/dist/preview/FrameController.js +216 -0
- package/dist/preview/FrameController.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +1 -0
- package/dist/preview/RenderContext.js +193 -0
- package/dist/preview/RenderContext.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +166 -0
- package/dist/preview/encoding/canvasEncoder.js.map +1 -0
- package/dist/preview/encoding/mainThreadEncoder.js +39 -0
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -0
- package/dist/preview/encoding/types.d.ts +1 -0
- package/dist/preview/encoding/workerEncoder.js +58 -0
- package/dist/preview/encoding/workerEncoder.js.map +1 -0
- package/dist/preview/logger.js +41 -0
- package/dist/preview/logger.js.map +1 -0
- package/dist/preview/previewTypes.js +11 -10
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderTimegroupPreview.js +259 -236
- package/dist/preview/renderTimegroupPreview.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.d.ts +5 -0
- package/dist/preview/renderTimegroupToCanvas.js +100 -489
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.d.ts +1 -0
- package/dist/preview/renderTimegroupToVideo.js +80 -22
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/inlineImages.js +56 -0
- package/dist/preview/rendering/inlineImages.js.map +1 -0
- package/dist/preview/rendering/renderToImage.d.ts +1 -0
- package/dist/preview/rendering/renderToImage.js +120 -0
- package/dist/preview/rendering/renderToImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageForeignObject.js +135 -0
- package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.d.ts +1 -0
- package/dist/preview/rendering/renderToImageNative.js +129 -0
- package/dist/preview/rendering/renderToImageNative.js.map +1 -0
- package/dist/preview/rendering/svgSerializer.js +43 -0
- package/dist/preview/rendering/svgSerializer.js.map +1 -0
- package/dist/preview/rendering/types.d.ts +2 -0
- package/dist/preview/statsTrackingStrategy.js +3 -1
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +8 -57
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +35 -0
- package/dist/render/EFRenderAPI.js +1 -0
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/sandbox/PlaybackControls.d.ts +1 -0
- package/dist/sandbox/ScenarioRunner.d.ts +1 -0
- package/dist/sandbox/defineSandbox.d.ts +1 -0
- package/dist/sandbox/index.d.ts +3 -0
- package/dist/style.css +3 -0
- package/dist/transcoding/types/index.d.ts +6 -3
- package/package.json +2 -3
- package/test/EFVideo.framegen.browsertest.ts +8 -1
- package/test/profilingPlugin.ts +1 -3
- package/test/setup.ts +23 -1
- package/dist/EF_INTERACTIVE.js +0 -7
- package/dist/EF_INTERACTIVE.js.map +0 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +0 -50
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +0 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +0 -104
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -168
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +0 -46
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +0 -49
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +0 -30
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +0 -49
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +0 -47
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +0 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -140
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +0 -1
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +0 -13
- package/dist/elements/EFMedia/shared/BufferUtils.js +0 -86
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +0 -1
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +0 -17
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +0 -90
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +0 -80
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -49
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -58
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +0 -71
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -52
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -50
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +0 -109
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +0 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +0 -12
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +0 -97
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +0 -1
- package/dist/elements/SampleBuffer.d.ts +0 -19
|
@@ -1,13 +1,8 @@
|
|
|
1
|
+
import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
|
|
1
2
|
import { EFFramegen } from "../EF_FRAMEGEN.js";
|
|
2
|
-
import { InputTask } from "./EFMedia/shared/MediaTaskUtils.js";
|
|
3
|
-
import { MediaEngine } from "../transcoding/types/index.js";
|
|
4
|
-
import { MediaBufferState } from "./EFMedia/shared/BufferUtils.js";
|
|
5
3
|
import { EFMedia } from "./EFMedia.js";
|
|
6
|
-
import { VideoBufferState } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
|
|
7
|
-
import { Task } from "@lit/task";
|
|
8
4
|
import * as lit2 from "lit";
|
|
9
5
|
import { PropertyValueMap } from "lit";
|
|
10
|
-
import * as mediabunny0 from "mediabunny";
|
|
11
6
|
import * as lit_html2 from "lit-html";
|
|
12
7
|
import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
13
8
|
|
|
@@ -26,7 +21,8 @@ interface LoadingState {
|
|
|
26
21
|
*/
|
|
27
22
|
|
|
28
23
|
declare const EFVideo_base: typeof EFMedia;
|
|
29
|
-
declare class EFVideo extends EFVideo_base {
|
|
24
|
+
declare class EFVideo extends EFVideo_base implements FrameRenderable {
|
|
25
|
+
#private;
|
|
30
26
|
static styles: lit2.CSSResult[];
|
|
31
27
|
canvasRef: lit_html_directives_ref0.Ref<HTMLCanvasElement>;
|
|
32
28
|
/**
|
|
@@ -44,18 +40,32 @@ declare class EFVideo extends EFVideo_base {
|
|
|
44
40
|
* @domAttribute "enable-video-buffering"
|
|
45
41
|
*/
|
|
46
42
|
enableVideoBuffering: boolean;
|
|
47
|
-
unifiedVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
|
|
48
|
-
videoBufferTask: Task<readonly [number], VideoBufferState>;
|
|
49
|
-
scrubVideoBufferTask: Task<readonly [any], MediaBufferState>;
|
|
50
|
-
scrubVideoInputTask: InputTask;
|
|
51
|
-
scrubVideoSeekTask: Task<readonly [number], mediabunny0.VideoSample | undefined>;
|
|
52
|
-
scrubVideoSegmentIdTask: Task<readonly [MediaEngine | undefined, number], number | undefined>;
|
|
53
|
-
scrubVideoSegmentFetchTask: Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
54
|
-
scrubVideoInitSegmentFetchTask: Task<readonly [MediaEngine | undefined], ArrayBuffer>;
|
|
55
43
|
/**
|
|
56
|
-
*
|
|
44
|
+
* Query readiness state for a given time.
|
|
45
|
+
* @implements FrameRenderable
|
|
46
|
+
*
|
|
47
|
+
* Note: The timeMs parameter is the root timegroup's time. We check against
|
|
48
|
+
* this.currentSourceTimeMs since that's what we cache in prepareFrame.
|
|
49
|
+
*/
|
|
50
|
+
getFrameState(_timeMs: number): FrameState;
|
|
51
|
+
/**
|
|
52
|
+
* Async preparation - seeks video and caches the sample.
|
|
53
|
+
* @implements FrameRenderable
|
|
54
|
+
*
|
|
55
|
+
* Note: The timeMs parameter is the root timegroup's time. We ignore it and
|
|
56
|
+
* use this.currentSourceTimeMs instead, which accounts for:
|
|
57
|
+
* - Our position within the parent timegroup (ownCurrentTimeMs)
|
|
58
|
+
* - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)
|
|
57
59
|
*/
|
|
58
|
-
|
|
60
|
+
prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Synchronous render - paints cached video sample to canvas.
|
|
63
|
+
* @implements FrameRenderable
|
|
64
|
+
*
|
|
65
|
+
* Note: The timeMs parameter is the root timegroup's time. We use
|
|
66
|
+
* this.currentSourceTimeMs to match what prepareFrame cached.
|
|
67
|
+
*/
|
|
68
|
+
renderFrame(_timeMs: number): void;
|
|
59
69
|
/**
|
|
60
70
|
* Loading state for user feedback
|
|
61
71
|
*/
|
|
@@ -68,7 +78,11 @@ declare class EFVideo extends EFVideo_base {
|
|
|
68
78
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
69
79
|
render(): lit_html2.TemplateResult<1>;
|
|
70
80
|
get canvasElement(): HTMLCanvasElement | undefined;
|
|
71
|
-
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
|
|
83
|
+
* This is a compatibility wrapper that delegates to the new system.
|
|
84
|
+
*/
|
|
85
|
+
frameTask: FrameTask;
|
|
72
86
|
/**
|
|
73
87
|
* Start a delayed loading operation for testing
|
|
74
88
|
*/
|
|
@@ -106,9 +120,9 @@ declare class EFVideo extends EFVideo_base {
|
|
|
106
120
|
private isFrameRenderingActive;
|
|
107
121
|
/**
|
|
108
122
|
* Legacy getter for fragment index task
|
|
109
|
-
* Still used by EFCaptions - maps to
|
|
123
|
+
* Still used by EFCaptions - maps to frameTask
|
|
110
124
|
*/
|
|
111
|
-
get fragmentIndexTask():
|
|
125
|
+
get fragmentIndexTask(): FrameTask;
|
|
112
126
|
/**
|
|
113
127
|
* Helper method for tests: wait for the current frame to be ready
|
|
114
128
|
* This encapsulates the complexity of ensuring the video has updated
|
|
@@ -117,6 +131,29 @@ declare class EFVideo extends EFVideo_base {
|
|
|
117
131
|
* @returns Promise that resolves when the frame is ready
|
|
118
132
|
*/
|
|
119
133
|
waitForFrameReady(): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Capture a video frame directly at a source media timestamp.
|
|
136
|
+
* Bypasses the frameTask system - designed for export/rendering.
|
|
137
|
+
* Does NOT paint to the element's internal canvas.
|
|
138
|
+
*
|
|
139
|
+
* Uses the same routing logic as unified video system:
|
|
140
|
+
* - "auto": main track for production rendering, follows normal routing otherwise
|
|
141
|
+
* - "scrub": force low-res scrub track (for thumbnails)
|
|
142
|
+
* - "main": force full-quality main track
|
|
143
|
+
*
|
|
144
|
+
* @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
|
|
145
|
+
* @param options - Capture options including quality and abort signal
|
|
146
|
+
* @returns Frame data for serialization
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
captureFrameAtSourceTime(sourceTimeMs: number, options?: {
|
|
150
|
+
quality?: "auto" | "scrub" | "main";
|
|
151
|
+
signal?: AbortSignal;
|
|
152
|
+
}): Promise<{
|
|
153
|
+
dataUrl: string;
|
|
154
|
+
width: number;
|
|
155
|
+
height: number;
|
|
156
|
+
}>;
|
|
120
157
|
/**
|
|
121
158
|
* Pre-fetch scrub segments for given timestamps.
|
|
122
159
|
* Loads 30-second segments sequentially, emitting progress events.
|
package/dist/elements/EFVideo.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { updateAnimations } from "./updateAnimations.js";
|
|
2
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
2
3
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
3
|
-
import {
|
|
4
|
+
import { withSpanSync } from "../otel/tracingHelpers.js";
|
|
5
|
+
import { PRIORITY_VIDEO, createFrameTaskWrapper } from "../preview/FrameController.js";
|
|
4
6
|
import { EFMedia } from "./EFMedia.js";
|
|
5
|
-
import { updateAnimations } from "./updateAnimations.js";
|
|
6
7
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { makeScrubVideoInputTask } from "./EFMedia/videoTasks/makeScrubVideoInputTask.js";
|
|
10
|
-
import { makeScrubVideoSeekTask } from "./EFMedia/videoTasks/makeScrubVideoSeekTask.js";
|
|
11
|
-
import { makeScrubVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js";
|
|
12
|
-
import { makeScrubVideoSegmentIdTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js";
|
|
13
|
-
import { makeUnifiedVideoSeekTask } from "./EFMedia/videoTasks/makeUnifiedVideoSeekTask.js";
|
|
14
|
-
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
|
|
15
|
-
import { Task } from "@lit/task";
|
|
8
|
+
import { MainVideoInputCache } from "./EFMedia/videoTasks/MainVideoInputCache.js";
|
|
9
|
+
import { ScrubInputCache } from "./EFMedia/videoTasks/ScrubInputCache.js";
|
|
16
10
|
import debug from "debug";
|
|
17
11
|
import { css, html } from "lit";
|
|
18
12
|
import { customElement, property, state } from "lit/decorators.js";
|
|
@@ -20,6 +14,8 @@ import { context, trace } from "@opentelemetry/api";
|
|
|
20
14
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
21
15
|
|
|
22
16
|
//#region src/elements/EFVideo.ts
|
|
17
|
+
const mainVideoInputCache = new MainVideoInputCache();
|
|
18
|
+
const scrubInputCache = new ScrubInputCache();
|
|
23
19
|
const log = debug("ef:elements:EFVideo");
|
|
24
20
|
let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
25
21
|
static {
|
|
@@ -82,68 +78,196 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
82
78
|
}
|
|
83
79
|
`];
|
|
84
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Cached video sample for the current frame.
|
|
83
|
+
* Set by prepareFrame(), consumed by renderFrame().
|
|
84
|
+
*/
|
|
85
|
+
#cachedVideoSample = void 0;
|
|
86
|
+
#cachedVideoSampleTimeMs = void 0;
|
|
87
|
+
/**
|
|
88
|
+
* Query readiness state for a given time.
|
|
89
|
+
* @implements FrameRenderable
|
|
90
|
+
*
|
|
91
|
+
* Note: The timeMs parameter is the root timegroup's time. We check against
|
|
92
|
+
* this.currentSourceTimeMs since that's what we cache in prepareFrame.
|
|
93
|
+
*/
|
|
94
|
+
getFrameState(_timeMs) {
|
|
95
|
+
const sourceTimeMs = this.currentSourceTimeMs;
|
|
96
|
+
const hasCache = this.#cachedVideoSample !== void 0 && this.#cachedVideoSampleTimeMs === sourceTimeMs;
|
|
97
|
+
return {
|
|
98
|
+
needsPreparation: !hasCache,
|
|
99
|
+
isReady: hasCache,
|
|
100
|
+
priority: PRIORITY_VIDEO
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Async preparation - seeks video and caches the sample.
|
|
105
|
+
* @implements FrameRenderable
|
|
106
|
+
*
|
|
107
|
+
* Note: The timeMs parameter is the root timegroup's time. We ignore it and
|
|
108
|
+
* use this.currentSourceTimeMs instead, which accounts for:
|
|
109
|
+
* - Our position within the parent timegroup (ownCurrentTimeMs)
|
|
110
|
+
* - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)
|
|
111
|
+
*/
|
|
112
|
+
async prepareFrame(_timeMs, signal) {
|
|
113
|
+
signal.throwIfAborted();
|
|
114
|
+
const sourceTimeMs = this.currentSourceTimeMs;
|
|
115
|
+
const mediaEngine = await this.getMediaEngine(signal);
|
|
116
|
+
if (!mediaEngine) {
|
|
117
|
+
this.#cachedVideoSample = void 0;
|
|
118
|
+
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
try {
|
|
123
|
+
const videoSample = await this.#fetchVideoSampleForFrame(mediaEngine, sourceTimeMs, signal);
|
|
124
|
+
signal.throwIfAborted();
|
|
125
|
+
this.#cachedVideoSample = videoSample;
|
|
126
|
+
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
129
|
+
console.warn(`Video seek error at ${sourceTimeMs}ms:`, error);
|
|
130
|
+
this.#cachedVideoSample = void 0;
|
|
131
|
+
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Synchronous render - paints cached video sample to canvas.
|
|
136
|
+
* @implements FrameRenderable
|
|
137
|
+
*
|
|
138
|
+
* Note: The timeMs parameter is the root timegroup's time. We use
|
|
139
|
+
* this.currentSourceTimeMs to match what prepareFrame cached.
|
|
140
|
+
*/
|
|
141
|
+
renderFrame(_timeMs) {
|
|
142
|
+
const sourceTimeMs = this.currentSourceTimeMs;
|
|
143
|
+
if (this.#cachedVideoSampleTimeMs === sourceTimeMs && this.#cachedVideoSample) {
|
|
144
|
+
const videoFrame = this.#cachedVideoSample.toVideoFrame();
|
|
145
|
+
try {
|
|
146
|
+
this.displayFrame(videoFrame, sourceTimeMs);
|
|
147
|
+
} finally {
|
|
148
|
+
videoFrame.close();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!this.parentTimegroup) updateAnimations(this);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Fetch video sample for a given time.
|
|
155
|
+
*
|
|
156
|
+
* Uses a quality routing strategy:
|
|
157
|
+
* - In production rendering: always use main (full quality) track
|
|
158
|
+
* - In preview mode: try scrub track first for faster scrubbing, fall back to main
|
|
159
|
+
* - If main track segment is already cached: use it (avoid redundant lower-quality fetch)
|
|
160
|
+
*/
|
|
161
|
+
async #fetchVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
|
|
162
|
+
const mainRendition = mediaEngine.videoRendition;
|
|
163
|
+
if (mainRendition) {
|
|
164
|
+
const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
|
|
165
|
+
if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition)) return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
166
|
+
}
|
|
167
|
+
if (this.isInProductionRenderingMode()) return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
168
|
+
if (mediaEngine.getScrubVideoRendition?.()) {
|
|
169
|
+
const scrubSample = await this.#getScrubVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
170
|
+
if (scrubSample) return scrubSample;
|
|
171
|
+
}
|
|
172
|
+
return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get scrub (low-resolution) video sample for fast preview scrubbing.
|
|
176
|
+
* Used in preview mode for faster response during timeline scrubbing.
|
|
177
|
+
*/
|
|
178
|
+
async #getScrubVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
|
|
179
|
+
const scrubRendition = mediaEngine.getScrubVideoRendition?.();
|
|
180
|
+
if (!scrubRendition) return;
|
|
181
|
+
const scrubRenditionWithSrc = {
|
|
182
|
+
...scrubRendition,
|
|
183
|
+
src: mediaEngine.src
|
|
184
|
+
};
|
|
185
|
+
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
|
|
186
|
+
if (segmentId === void 0) return;
|
|
187
|
+
const scrubInput = await scrubInputCache.getOrCreateInput(mediaEngine.src, segmentId, async () => {
|
|
188
|
+
let initSegment;
|
|
189
|
+
let mediaSegment;
|
|
190
|
+
try {
|
|
191
|
+
[initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal)]);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!initSegment || !mediaSegment) return;
|
|
197
|
+
signal.throwIfAborted();
|
|
198
|
+
const combinedBlob = new Blob([initSegment, mediaSegment]);
|
|
199
|
+
signal.throwIfAborted();
|
|
200
|
+
const arrayBuffer = await combinedBlob.arrayBuffer();
|
|
201
|
+
signal.throwIfAborted();
|
|
202
|
+
const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
|
|
203
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
204
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
205
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
206
|
+
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
if (!scrubInput) return;
|
|
210
|
+
signal.throwIfAborted();
|
|
211
|
+
const videoTrack = await scrubInput.getFirstVideoTrack();
|
|
212
|
+
if (!videoTrack) return;
|
|
213
|
+
signal.throwIfAborted();
|
|
214
|
+
return scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get main video sample for a given time.
|
|
218
|
+
*/
|
|
219
|
+
async #getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal) {
|
|
220
|
+
const videoRendition = mediaEngine.getVideoRendition?.() ?? mediaEngine.videoRendition;
|
|
221
|
+
if (!videoRendition) return;
|
|
222
|
+
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, videoRendition);
|
|
223
|
+
if (segmentId === void 0) return;
|
|
224
|
+
const mainInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
|
|
225
|
+
let initSegment;
|
|
226
|
+
let mediaSegment;
|
|
227
|
+
try {
|
|
228
|
+
[initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, signal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal)]);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
231
|
+
if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") || error.message.includes("File not found") || error.message.includes("Media segment not found") || error.message.includes("Init segment not found") || error.message.includes("Track not found"))) return;
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
if (!initSegment || !mediaSegment) return;
|
|
235
|
+
signal.throwIfAborted();
|
|
236
|
+
const combinedBlob = new Blob([initSegment, mediaSegment]);
|
|
237
|
+
signal.throwIfAborted();
|
|
238
|
+
const arrayBuffer = await combinedBlob.arrayBuffer();
|
|
239
|
+
signal.throwIfAborted();
|
|
240
|
+
const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
|
|
241
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
242
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
243
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
244
|
+
startTimeOffsetMs: videoRendition.startTimeOffsetMs
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
if (!mainInput) return;
|
|
248
|
+
signal.throwIfAborted();
|
|
249
|
+
const videoTrack = await mainInput.getFirstVideoTrack();
|
|
250
|
+
if (!videoTrack) return;
|
|
251
|
+
signal.throwIfAborted();
|
|
252
|
+
return mainInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Delayed loading state manager for user feedback
|
|
256
|
+
*/
|
|
257
|
+
#delayedLoadingState;
|
|
85
258
|
constructor() {
|
|
86
259
|
super();
|
|
87
260
|
this.canvasRef = createRef();
|
|
88
261
|
this.videoBufferDurationMs = 1e4;
|
|
89
262
|
this.maxVideoBufferFetches = 2;
|
|
90
263
|
this.enableVideoBuffering = true;
|
|
91
|
-
this.unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);
|
|
92
|
-
this.videoBufferTask = makeVideoBufferTask(this);
|
|
93
|
-
this.scrubVideoBufferTask = makeScrubVideoBufferTask(this);
|
|
94
|
-
this.scrubVideoInputTask = makeScrubVideoInputTask(this);
|
|
95
|
-
this.scrubVideoSeekTask = makeScrubVideoSeekTask(this);
|
|
96
|
-
this.scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);
|
|
97
|
-
this.scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);
|
|
98
|
-
this.scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);
|
|
99
264
|
this.loadingState = {
|
|
100
265
|
isLoading: false,
|
|
101
266
|
operation: null,
|
|
102
267
|
message: ""
|
|
103
268
|
};
|
|
104
|
-
this.frameTask =
|
|
105
|
-
|
|
106
|
-
args: () => [this.desiredSeekTimeMs],
|
|
107
|
-
onError: (error) => {
|
|
108
|
-
this.frameTask.taskComplete.catch(() => {});
|
|
109
|
-
if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
|
|
110
|
-
if (error instanceof Error && !error.message.includes("Video rendition unavailable") && !error.message.includes("No valid media source") && !error.message.includes("Sample not found for time")) console.error("frameTask error", error);
|
|
111
|
-
},
|
|
112
|
-
onComplete: () => {},
|
|
113
|
-
task: async ([_desiredSeekTimeMs], { signal }) => {
|
|
114
|
-
const t0 = performance.now();
|
|
115
|
-
await withSpan("video.frameTask", {
|
|
116
|
-
elementId: this.id || "unknown",
|
|
117
|
-
desiredSeekTimeMs: _desiredSeekTimeMs,
|
|
118
|
-
src: this.src || "none"
|
|
119
|
-
}, void 0, async (span) => {
|
|
120
|
-
const t1 = performance.now();
|
|
121
|
-
span.setAttribute("preworkMs", t1 - t0);
|
|
122
|
-
this.unifiedVideoSeekTask.run().catch(() => {});
|
|
123
|
-
const t2 = performance.now();
|
|
124
|
-
span.setAttribute("seekRunMs", t2 - t1);
|
|
125
|
-
try {
|
|
126
|
-
await this.unifiedVideoSeekTask.taskComplete;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
129
|
-
signal?.throwIfAborted();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
const t3 = performance.now();
|
|
135
|
-
span.setAttribute("seekAwaitMs", t3 - t2);
|
|
136
|
-
signal?.throwIfAborted();
|
|
137
|
-
const t4 = performance.now();
|
|
138
|
-
this.paint(_desiredSeekTimeMs, span);
|
|
139
|
-
const t5 = performance.now();
|
|
140
|
-
span.setAttribute("paintMs", t5 - t4);
|
|
141
|
-
if (!this.parentTimegroup) updateAnimations(this);
|
|
142
|
-
span.setAttribute("totalFrameMs", t5 - t0);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
this.delayedLoadingState = new DelayedLoadingState(250, (isLoading, message) => {
|
|
269
|
+
this.frameTask = createFrameTaskWrapper(this, { getTimeMs: () => this.desiredSeekTimeMs });
|
|
270
|
+
this.#delayedLoadingState = new DelayedLoadingState(250, (isLoading, message) => {
|
|
147
271
|
this.setLoadingState(isLoading, null, message);
|
|
148
272
|
});
|
|
149
273
|
}
|
|
@@ -176,13 +300,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
176
300
|
* Start a delayed loading operation for testing
|
|
177
301
|
*/
|
|
178
302
|
startDelayedLoading(operationId, message, options = {}) {
|
|
179
|
-
this
|
|
303
|
+
this.#delayedLoadingState.startLoading(operationId, message, options);
|
|
180
304
|
}
|
|
181
305
|
/**
|
|
182
306
|
* Clear a delayed loading operation for testing
|
|
183
307
|
*/
|
|
184
308
|
clearDelayedLoading(operationId) {
|
|
185
|
-
this
|
|
309
|
+
this.#delayedLoadingState.clearLoading(operationId);
|
|
186
310
|
}
|
|
187
311
|
/**
|
|
188
312
|
* Set loading state for user feedback
|
|
@@ -212,7 +336,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
212
336
|
span.setAttribute("modeCheckMs", t1 - t0);
|
|
213
337
|
try {
|
|
214
338
|
const t2 = performance.now();
|
|
215
|
-
const videoSample = this
|
|
339
|
+
const videoSample = this.#cachedVideoSample;
|
|
216
340
|
span.setAttribute("hasVideoSample", !!videoSample);
|
|
217
341
|
span.setAttribute("valueAccessMs", t2 - t1);
|
|
218
342
|
if (videoSample) {
|
|
@@ -230,7 +354,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
230
354
|
}
|
|
231
355
|
}
|
|
232
356
|
} catch (error) {
|
|
233
|
-
console.warn("
|
|
357
|
+
console.warn("Video pipeline error:", error);
|
|
234
358
|
}
|
|
235
359
|
if (!isProductionRendering) {
|
|
236
360
|
const isInRenderClone = !!this.closest(".ef-render-clone-container");
|
|
@@ -339,11 +463,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
339
463
|
}
|
|
340
464
|
/**
|
|
341
465
|
* Legacy getter for fragment index task
|
|
342
|
-
* Still used by EFCaptions - maps to
|
|
466
|
+
* Still used by EFCaptions - maps to frameTask
|
|
343
467
|
*/
|
|
344
468
|
get fragmentIndexTask() {
|
|
345
|
-
return this.
|
|
469
|
+
return this.frameTask;
|
|
346
470
|
}
|
|
471
|
+
#pendingFrameReadyTime = null;
|
|
472
|
+
#pendingFrameReadyPromise = null;
|
|
347
473
|
/**
|
|
348
474
|
* Helper method for tests: wait for the current frame to be ready
|
|
349
475
|
* This encapsulates the complexity of ensuring the video has updated
|
|
@@ -354,6 +480,19 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
354
480
|
async waitForFrameReady() {
|
|
355
481
|
const currentTime = this.currentSourceTimeMs;
|
|
356
482
|
if (this.desiredSeekTimeMs !== currentTime) this.desiredSeekTimeMs = currentTime;
|
|
483
|
+
if (this.#pendingFrameReadyTime === currentTime && this.#pendingFrameReadyPromise) return this.#pendingFrameReadyPromise;
|
|
484
|
+
this.#pendingFrameReadyTime = currentTime;
|
|
485
|
+
this.#pendingFrameReadyPromise = this.#doWaitForFrameReady(currentTime);
|
|
486
|
+
try {
|
|
487
|
+
await this.#pendingFrameReadyPromise;
|
|
488
|
+
} finally {
|
|
489
|
+
if (this.#pendingFrameReadyTime === currentTime) {
|
|
490
|
+
this.#pendingFrameReadyTime = null;
|
|
491
|
+
this.#pendingFrameReadyPromise = null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async #doWaitForFrameReady(_targetTimeMs) {
|
|
357
496
|
await this.updateComplete;
|
|
358
497
|
try {
|
|
359
498
|
await this.frameTask.run();
|
|
@@ -363,6 +502,119 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
363
502
|
}
|
|
364
503
|
}
|
|
365
504
|
/**
|
|
505
|
+
* Capture a video frame directly at a source media timestamp.
|
|
506
|
+
* Bypasses the frameTask system - designed for export/rendering.
|
|
507
|
+
* Does NOT paint to the element's internal canvas.
|
|
508
|
+
*
|
|
509
|
+
* Uses the same routing logic as unified video system:
|
|
510
|
+
* - "auto": main track for production rendering, follows normal routing otherwise
|
|
511
|
+
* - "scrub": force low-res scrub track (for thumbnails)
|
|
512
|
+
* - "main": force full-quality main track
|
|
513
|
+
*
|
|
514
|
+
* @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
|
|
515
|
+
* @param options - Capture options including quality and abort signal
|
|
516
|
+
* @returns Frame data for serialization
|
|
517
|
+
* @public
|
|
518
|
+
*/
|
|
519
|
+
async captureFrameAtSourceTime(sourceTimeMs, options = {}) {
|
|
520
|
+
const { quality = "auto", signal } = options;
|
|
521
|
+
signal?.throwIfAborted();
|
|
522
|
+
const mediaEngine = await this.getMediaEngine(signal);
|
|
523
|
+
signal?.throwIfAborted();
|
|
524
|
+
if (!mediaEngine) throw new Error("No media engine available for frame capture");
|
|
525
|
+
const useMainTrack = quality === "main" || quality === "auto" && this.isInProductionRenderingMode();
|
|
526
|
+
let videoSample;
|
|
527
|
+
const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
|
|
528
|
+
signal?.throwIfAborted();
|
|
529
|
+
if (useMainTrack) {
|
|
530
|
+
const videoRendition = mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;
|
|
531
|
+
if (!videoRendition) throw new Error("No video rendition available");
|
|
532
|
+
const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, videoRendition);
|
|
533
|
+
if (segmentId === void 0) throw new Error(`Cannot compute segment ID for time ${sourceTimeMs}ms`);
|
|
534
|
+
const seekingInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
|
|
535
|
+
const fetchSignal = signal ?? new AbortController().signal;
|
|
536
|
+
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, fetchSignal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, fetchSignal)]);
|
|
537
|
+
if (!initSegment || !mediaSegment) return;
|
|
538
|
+
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
539
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
540
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
541
|
+
startTimeOffsetMs: videoRendition.startTimeOffsetMs
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
signal?.throwIfAborted();
|
|
545
|
+
if (!seekingInput) throw new Error(`Failed to fetch video segments for time ${sourceTimeMs}ms`);
|
|
546
|
+
const videoTrack = await seekingInput.getFirstVideoTrack();
|
|
547
|
+
signal?.throwIfAborted();
|
|
548
|
+
if (!videoTrack) throw new Error("No video track found in segment");
|
|
549
|
+
videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
|
|
550
|
+
signal?.throwIfAborted();
|
|
551
|
+
} else {
|
|
552
|
+
const scrubRendition = mediaEngine.getScrubVideoRendition?.();
|
|
553
|
+
if (!scrubRendition) return this.captureFrameAtSourceTime(sourceTimeMs, {
|
|
554
|
+
quality: "main",
|
|
555
|
+
signal
|
|
556
|
+
});
|
|
557
|
+
const scrubRenditionWithSrc = {
|
|
558
|
+
...scrubRendition,
|
|
559
|
+
src: mediaEngine.src
|
|
560
|
+
};
|
|
561
|
+
const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, scrubRenditionWithSrc);
|
|
562
|
+
if (segmentId === void 0) throw new Error(`Cannot compute scrub segment ID for time ${sourceTimeMs}ms`);
|
|
563
|
+
const seekingInput = await scrubInputCache.getOrCreateInput(mediaEngine.src, segmentId, async () => {
|
|
564
|
+
const scrubFetchSignal = signal ?? new AbortController().signal;
|
|
565
|
+
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, scrubFetchSignal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, scrubFetchSignal)]);
|
|
566
|
+
if (!initSegment || !mediaSegment) return;
|
|
567
|
+
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
568
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
569
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
570
|
+
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
signal?.throwIfAborted();
|
|
574
|
+
if (!seekingInput) return this.captureFrameAtSourceTime(sourceTimeMs, {
|
|
575
|
+
quality: "main",
|
|
576
|
+
signal
|
|
577
|
+
});
|
|
578
|
+
const videoTrack = await seekingInput.getFirstVideoTrack();
|
|
579
|
+
signal?.throwIfAborted();
|
|
580
|
+
if (!videoTrack) return this.captureFrameAtSourceTime(sourceTimeMs, {
|
|
581
|
+
quality: "main",
|
|
582
|
+
signal
|
|
583
|
+
});
|
|
584
|
+
videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
|
|
585
|
+
signal?.throwIfAborted();
|
|
586
|
+
}
|
|
587
|
+
if (!videoSample) throw new Error(`No video sample found at ${sourceTimeMs}ms`);
|
|
588
|
+
const videoFrame = videoSample.toVideoFrame();
|
|
589
|
+
try {
|
|
590
|
+
signal?.throwIfAborted();
|
|
591
|
+
const canvas = new OffscreenCanvas(videoFrame.codedWidth, videoFrame.codedHeight);
|
|
592
|
+
const ctx = canvas.getContext("2d");
|
|
593
|
+
if (!ctx) throw new Error("Failed to get 2d context from OffscreenCanvas");
|
|
594
|
+
ctx.drawImage(videoFrame, 0, 0);
|
|
595
|
+
signal?.throwIfAborted();
|
|
596
|
+
const blob = await canvas.convertToBlob({
|
|
597
|
+
type: "image/jpeg",
|
|
598
|
+
quality: .92
|
|
599
|
+
});
|
|
600
|
+
signal?.throwIfAborted();
|
|
601
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
602
|
+
const reader = new FileReader();
|
|
603
|
+
reader.onload = () => resolve(reader.result);
|
|
604
|
+
reader.onerror = reject;
|
|
605
|
+
reader.readAsDataURL(blob);
|
|
606
|
+
});
|
|
607
|
+
signal?.throwIfAborted();
|
|
608
|
+
return {
|
|
609
|
+
dataUrl,
|
|
610
|
+
width: videoFrame.codedWidth,
|
|
611
|
+
height: videoFrame.codedHeight
|
|
612
|
+
};
|
|
613
|
+
} finally {
|
|
614
|
+
videoFrame.close();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
366
618
|
* Pre-fetch scrub segments for given timestamps.
|
|
367
619
|
* Loads 30-second segments sequentially, emitting progress events.
|
|
368
620
|
* This ensures scrub track is cached for fast thumbnail generation.
|
|
@@ -373,7 +625,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
373
625
|
* @public
|
|
374
626
|
*/
|
|
375
627
|
async prefetchScrubSegments(timestamps, onProgress) {
|
|
376
|
-
const mediaEngine = await this.
|
|
628
|
+
const mediaEngine = await this.getMediaEngine();
|
|
377
629
|
if (!mediaEngine) {
|
|
378
630
|
log("prefetchScrubSegments: no media engine available");
|
|
379
631
|
return;
|
|
@@ -444,7 +696,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
444
696
|
* @public
|
|
445
697
|
*/
|
|
446
698
|
async prefetchMainVideoSegments(timestamps, onProgress) {
|
|
447
|
-
const mediaEngine = await this.
|
|
699
|
+
const mediaEngine = await this.getMediaEngine();
|
|
448
700
|
if (!mediaEngine) {
|
|
449
701
|
log("prefetchMainVideoSegments: no media engine available");
|
|
450
702
|
return;
|
|
@@ -492,7 +744,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
492
744
|
*/
|
|
493
745
|
disconnectedCallback() {
|
|
494
746
|
super.disconnectedCallback();
|
|
495
|
-
this
|
|
747
|
+
this.#delayedLoadingState.clearAllLoading();
|
|
496
748
|
}
|
|
497
749
|
didBecomeRoot() {
|
|
498
750
|
super.didBecomeRoot();
|