@editframe/elements 0.37.3-beta → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +7 -10
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +8 -4
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +4 -4
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +3 -1
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.js +267 -145
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
- package/dist/preview/renderTimegroupToVideo.js +85 -105
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.js +1 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +21 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
package/dist/elements/EFVideo.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { updateAnimations } from "./updateAnimations.js";
|
|
2
|
-
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
3
1
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
4
2
|
import { withSpanSync } from "../otel/tracingHelpers.js";
|
|
5
|
-
import { PRIORITY_VIDEO
|
|
3
|
+
import { PRIORITY_VIDEO } from "../preview/FrameController.js";
|
|
4
|
+
import { updateAnimations } from "./updateAnimations.js";
|
|
5
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
6
6
|
import { EFMedia } from "./EFMedia.js";
|
|
7
7
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
8
8
|
import { MainVideoInputCache } from "./EFMedia/videoTasks/MainVideoInputCache.js";
|
|
9
9
|
import { ScrubInputCache } from "./EFMedia/videoTasks/ScrubInputCache.js";
|
|
10
10
|
import debug from "debug";
|
|
11
11
|
import { css, html } from "lit";
|
|
12
|
-
import { customElement,
|
|
12
|
+
import { customElement, state } from "lit/decorators.js";
|
|
13
13
|
import { context, trace } from "@opentelemetry/api";
|
|
14
14
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
15
15
|
|
|
@@ -17,21 +17,44 @@ import { createRef, ref } from "lit/directives/ref.js";
|
|
|
17
17
|
const mainVideoInputCache = new MainVideoInputCache();
|
|
18
18
|
const scrubInputCache = new ScrubInputCache();
|
|
19
19
|
const log = debug("ef:elements:EFVideo");
|
|
20
|
+
var VideoSeekTask = class {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.value = void 0;
|
|
23
|
+
this.task = void 0;
|
|
24
|
+
this.taskComplete = Promise.resolve(void 0);
|
|
25
|
+
}
|
|
26
|
+
#resolve;
|
|
27
|
+
begin() {
|
|
28
|
+
this.taskComplete = new Promise((resolve) => {
|
|
29
|
+
this.#resolve = resolve;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
complete(sample) {
|
|
33
|
+
this.value = sample;
|
|
34
|
+
this.#resolve?.(sample);
|
|
35
|
+
}
|
|
36
|
+
abort() {
|
|
37
|
+
this.#resolve?.(void 0);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
20
40
|
let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
21
41
|
static {
|
|
22
42
|
this.styles = [css`
|
|
23
43
|
:host {
|
|
24
44
|
display: block;
|
|
25
45
|
position: relative;
|
|
46
|
+
object-fit: contain;
|
|
47
|
+
object-position: center;
|
|
26
48
|
}
|
|
27
49
|
canvas {
|
|
28
50
|
overflow: hidden;
|
|
29
51
|
position: static;
|
|
30
52
|
width: 100%;
|
|
31
53
|
height: 100%;
|
|
54
|
+
object-fit: inherit;
|
|
55
|
+
object-position: inherit;
|
|
32
56
|
margin: 0;
|
|
33
57
|
padding: 0;
|
|
34
|
-
overflow: hidden;
|
|
35
58
|
border: none;
|
|
36
59
|
outline: none;
|
|
37
60
|
box-shadow: none;
|
|
@@ -85,9 +108,29 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
85
108
|
#cachedVideoSample = void 0;
|
|
86
109
|
#cachedVideoSampleTimeMs = void 0;
|
|
87
110
|
/**
|
|
111
|
+
* Quality upgrade intent tracking.
|
|
112
|
+
* Tracks what upgrade tasks were last submitted to avoid redundant scheduler calls.
|
|
113
|
+
*/
|
|
114
|
+
#upgradeState = null;
|
|
115
|
+
/**
|
|
116
|
+
* Standalone upgrade controller for elements without a timegroup.
|
|
117
|
+
*/
|
|
118
|
+
#standaloneUpgradeController = null;
|
|
119
|
+
/**
|
|
120
|
+
* Current rendition being displayed (for observability).
|
|
121
|
+
*/
|
|
122
|
+
#currentRenditionId = void 0;
|
|
123
|
+
/**
|
|
124
|
+
* Get the current rendition being displayed.
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
get currentRenditionId() {
|
|
128
|
+
return this.#currentRenditionId;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
88
131
|
* Query readiness state for a given time.
|
|
89
132
|
* @implements FrameRenderable
|
|
90
|
-
*
|
|
133
|
+
*
|
|
91
134
|
* Note: The timeMs parameter is the root timegroup's time. We check against
|
|
92
135
|
* this.currentSourceTimeMs since that's what we cache in prepareFrame.
|
|
93
136
|
*/
|
|
@@ -103,7 +146,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
103
146
|
/**
|
|
104
147
|
* Async preparation - seeks video and caches the sample.
|
|
105
148
|
* @implements FrameRenderable
|
|
106
|
-
*
|
|
149
|
+
*
|
|
107
150
|
* Note: The timeMs parameter is the root timegroup's time. We ignore it and
|
|
108
151
|
* use this.currentSourceTimeMs instead, which accounts for:
|
|
109
152
|
* - Our position within the parent timegroup (ownCurrentTimeMs)
|
|
@@ -111,11 +154,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
111
154
|
*/
|
|
112
155
|
async prepareFrame(_timeMs, signal) {
|
|
113
156
|
signal.throwIfAborted();
|
|
157
|
+
this.unifiedVideoSeekTask.begin();
|
|
114
158
|
const sourceTimeMs = this.currentSourceTimeMs;
|
|
115
159
|
const mediaEngine = await this.getMediaEngine(signal);
|
|
116
160
|
if (!mediaEngine) {
|
|
117
161
|
this.#cachedVideoSample = void 0;
|
|
118
162
|
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
163
|
+
this.unifiedVideoSeekTask.complete(void 0);
|
|
119
164
|
return;
|
|
120
165
|
}
|
|
121
166
|
signal.throwIfAborted();
|
|
@@ -124,18 +169,23 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
124
169
|
signal.throwIfAborted();
|
|
125
170
|
this.#cachedVideoSample = videoSample;
|
|
126
171
|
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
172
|
+
this.unifiedVideoSeekTask.complete(videoSample);
|
|
127
173
|
} catch (error) {
|
|
128
|
-
if (error instanceof DOMException && error.name === "AbortError")
|
|
174
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
175
|
+
this.unifiedVideoSeekTask.abort();
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
129
178
|
console.warn(`Video seek error at ${sourceTimeMs}ms:`, error);
|
|
130
179
|
this.#cachedVideoSample = void 0;
|
|
131
180
|
this.#cachedVideoSampleTimeMs = sourceTimeMs;
|
|
181
|
+
this.unifiedVideoSeekTask.complete(void 0);
|
|
132
182
|
}
|
|
133
183
|
}
|
|
134
184
|
/**
|
|
135
185
|
* Synchronous render - paints cached video sample to canvas.
|
|
136
186
|
* @implements FrameRenderable
|
|
137
|
-
*
|
|
138
|
-
* Note: The timeMs parameter is the root timegroup's time. We use
|
|
187
|
+
*
|
|
188
|
+
* Note: The timeMs parameter is the root timegroup's time. We use
|
|
139
189
|
* this.currentSourceTimeMs to match what prepareFrame cached.
|
|
140
190
|
*/
|
|
141
191
|
renderFrame(_timeMs) {
|
|
@@ -152,7 +202,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
152
202
|
}
|
|
153
203
|
/**
|
|
154
204
|
* Fetch video sample for a given time.
|
|
155
|
-
*
|
|
205
|
+
*
|
|
156
206
|
* Uses a quality routing strategy:
|
|
157
207
|
* - In production rendering: always use main (full quality) track
|
|
158
208
|
* - In preview mode: try scrub track first for faster scrubbing, fall back to main
|
|
@@ -162,13 +212,24 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
162
212
|
const mainRendition = mediaEngine.videoRendition;
|
|
163
213
|
if (mainRendition) {
|
|
164
214
|
const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
|
|
165
|
-
if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition))
|
|
215
|
+
if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition)) {
|
|
216
|
+
this.#currentRenditionId = "main";
|
|
217
|
+
return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (this.isInProductionRenderingMode()) {
|
|
221
|
+
this.#currentRenditionId = "main";
|
|
222
|
+
return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
166
223
|
}
|
|
167
|
-
if (this.isInProductionRenderingMode()) return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
168
224
|
if (mediaEngine.getScrubVideoRendition?.()) {
|
|
169
225
|
const scrubSample = await this.#getScrubVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
170
|
-
if (scrubSample)
|
|
226
|
+
if (scrubSample) {
|
|
227
|
+
this.#currentRenditionId = "scrub";
|
|
228
|
+
this.#maybeScheduleQualityUpgrade(mediaEngine, desiredSeekTimeMs);
|
|
229
|
+
return scrubSample;
|
|
230
|
+
}
|
|
171
231
|
}
|
|
232
|
+
this.#currentRenditionId = "main";
|
|
172
233
|
return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);
|
|
173
234
|
}
|
|
174
235
|
/**
|
|
@@ -188,7 +249,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
188
249
|
let initSegment;
|
|
189
250
|
let mediaSegment;
|
|
190
251
|
try {
|
|
191
|
-
|
|
252
|
+
const initP = mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal);
|
|
253
|
+
const mediaP = mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal);
|
|
254
|
+
initP.catch(() => {});
|
|
255
|
+
mediaP.catch(() => {});
|
|
256
|
+
[initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
|
|
192
257
|
} catch (error) {
|
|
193
258
|
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
194
259
|
return;
|
|
@@ -225,7 +290,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
225
290
|
let initSegment;
|
|
226
291
|
let mediaSegment;
|
|
227
292
|
try {
|
|
228
|
-
|
|
293
|
+
const initP = mediaEngine.fetchInitSegment(videoRendition, signal);
|
|
294
|
+
const mediaP = mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal);
|
|
295
|
+
initP.catch(() => {});
|
|
296
|
+
mediaP.catch(() => {});
|
|
297
|
+
[initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
|
|
229
298
|
} catch (error) {
|
|
230
299
|
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
231
300
|
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;
|
|
@@ -249,7 +318,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
249
318
|
const videoTrack = await mainInput.getFirstVideoTrack();
|
|
250
319
|
if (!videoTrack) return;
|
|
251
320
|
signal.throwIfAborted();
|
|
252
|
-
return mainInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
321
|
+
return await mainInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
253
322
|
}
|
|
254
323
|
/**
|
|
255
324
|
* Delayed loading state manager for user feedback
|
|
@@ -258,21 +327,25 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
258
327
|
constructor() {
|
|
259
328
|
super();
|
|
260
329
|
this.canvasRef = createRef();
|
|
261
|
-
this.
|
|
262
|
-
this.maxVideoBufferFetches = 2;
|
|
263
|
-
this.enableVideoBuffering = true;
|
|
330
|
+
this.unifiedVideoSeekTask = new VideoSeekTask();
|
|
264
331
|
this.loadingState = {
|
|
265
332
|
isLoading: false,
|
|
266
333
|
operation: null,
|
|
267
334
|
message: ""
|
|
268
335
|
};
|
|
269
|
-
this.frameTask = createFrameTaskWrapper(this, { getTimeMs: () => this.desiredSeekTimeMs });
|
|
270
336
|
this.#delayedLoadingState = new DelayedLoadingState(250, (isLoading, message) => {
|
|
271
337
|
this.setLoadingState(isLoading, null, message);
|
|
272
338
|
});
|
|
273
339
|
}
|
|
274
340
|
updated(changedProperties) {
|
|
275
341
|
super.updated(changedProperties);
|
|
342
|
+
if (changedProperties.has("src") || changedProperties.has("fileId")) this.#invalidateUpgradeState("src-change");
|
|
343
|
+
if ([
|
|
344
|
+
"_trimStartMs",
|
|
345
|
+
"_trimEndMs",
|
|
346
|
+
"_sourceInMs",
|
|
347
|
+
"_sourceOutMs"
|
|
348
|
+
].some((prop) => changedProperties.has(prop))) this.#invalidateUpgradeState("bounds-change");
|
|
276
349
|
}
|
|
277
350
|
render() {
|
|
278
351
|
return html`
|
|
@@ -320,7 +393,6 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
320
393
|
}
|
|
321
394
|
/**
|
|
322
395
|
* Paint the current video frame to canvas
|
|
323
|
-
* Called by frameTask after seek is complete
|
|
324
396
|
*/
|
|
325
397
|
paint(seekToMs, parentSpan) {
|
|
326
398
|
const parentContext = parentSpan ? trace.setSpan(context.active(), parentSpan) : void 0;
|
|
@@ -356,14 +428,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
356
428
|
} catch (error) {
|
|
357
429
|
console.warn("Video pipeline error:", error);
|
|
358
430
|
}
|
|
359
|
-
if (!isProductionRendering) {
|
|
360
|
-
const isInRenderClone = !!this.closest(".ef-render-clone-container");
|
|
361
|
-
if (isInRenderClone) span.setAttribute("renderClone", true);
|
|
362
|
-
if (!isInRenderClone && (!this.rootTimegroup || this.rootTimegroup.currentTimeMs === 0 && this.desiredSeekTimeMs === 0)) {
|
|
363
|
-
span.setAttribute("skipped", "preview-initialization");
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
} else {
|
|
431
|
+
if (!isProductionRendering) {} else {
|
|
367
432
|
if (!this.rootTimegroup) {
|
|
368
433
|
span.setAttribute("skipped", "no-root-timegroup");
|
|
369
434
|
return;
|
|
@@ -382,7 +447,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
382
447
|
*/
|
|
383
448
|
clearCanvas() {
|
|
384
449
|
if (!this.canvasElement) return;
|
|
385
|
-
const ctx = this.canvasElement.getContext("2d");
|
|
450
|
+
const ctx = this.canvasElement.getContext("2d", { willReadFrequently: true });
|
|
386
451
|
if (ctx) ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);
|
|
387
452
|
}
|
|
388
453
|
/**
|
|
@@ -408,22 +473,26 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
408
473
|
}
|
|
409
474
|
const t1 = performance.now();
|
|
410
475
|
span.setAttribute("getCanvasMs", Math.round((t1 - t0) * 100) / 100);
|
|
411
|
-
const ctx = this.canvasElement.getContext("2d");
|
|
476
|
+
const ctx = this.canvasElement.getContext("2d", { willReadFrequently: true });
|
|
412
477
|
const t2 = performance.now();
|
|
413
478
|
span.setAttribute("getCtxMs", Math.round((t2 - t1) * 100) / 100);
|
|
414
479
|
if (!ctx) {
|
|
415
480
|
log("trace: displayFrame aborted - no canvas context");
|
|
416
481
|
throw new Error(`Frame display failed: Unable to get 2D canvas context at time ${seekToMs}ms. This may indicate a browser compatibility issue or canvas corruption.`);
|
|
417
482
|
}
|
|
483
|
+
const frameWidth = frame.displayWidth;
|
|
484
|
+
const frameHeight = frame.displayHeight;
|
|
418
485
|
let resized = false;
|
|
419
|
-
if (
|
|
420
|
-
if (this.canvasElement.width
|
|
486
|
+
if (frameWidth && frameHeight) {
|
|
487
|
+
if (frameWidth > this.canvasElement.width || frameHeight > this.canvasElement.height) {
|
|
488
|
+
const newWidth = Math.max(this.canvasElement.width, frameWidth);
|
|
489
|
+
const newHeight = Math.max(this.canvasElement.height, frameHeight);
|
|
421
490
|
log("trace: updating canvas dimensions", {
|
|
422
|
-
width:
|
|
423
|
-
height:
|
|
491
|
+
width: newWidth,
|
|
492
|
+
height: newHeight
|
|
424
493
|
});
|
|
425
|
-
this.canvasElement.width =
|
|
426
|
-
this.canvasElement.height =
|
|
494
|
+
this.canvasElement.width = newWidth;
|
|
495
|
+
this.canvasElement.height = newHeight;
|
|
427
496
|
resized = true;
|
|
428
497
|
const t3 = performance.now();
|
|
429
498
|
span.setAttribute("resizeMs", Math.round((t3 - t2) * 100) / 100);
|
|
@@ -462,78 +531,41 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
462
531
|
return (this.rootTimegroup?.currentTimeMs || 0) >= renderStartTime;
|
|
463
532
|
}
|
|
464
533
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*/
|
|
468
|
-
get fragmentIndexTask() {
|
|
469
|
-
return this.frameTask;
|
|
470
|
-
}
|
|
471
|
-
#pendingFrameReadyTime = null;
|
|
472
|
-
#pendingFrameReadyPromise = null;
|
|
473
|
-
/**
|
|
474
|
-
* Helper method for tests: wait for the current frame to be ready
|
|
475
|
-
* This encapsulates the complexity of ensuring the video has updated
|
|
476
|
-
* and its frameTask has completed.
|
|
534
|
+
* Get a decoded VideoFrame at a specific source media timestamp.
|
|
535
|
+
* Returns a standard WebCodecs VideoFrame — caller MUST call .close() when done.
|
|
477
536
|
*
|
|
478
|
-
*
|
|
479
|
-
*/
|
|
480
|
-
async waitForFrameReady() {
|
|
481
|
-
const currentTime = this.currentSourceTimeMs;
|
|
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) {
|
|
496
|
-
await this.updateComplete;
|
|
497
|
-
try {
|
|
498
|
-
await this.frameTask.run();
|
|
499
|
-
} catch (error) {
|
|
500
|
-
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;
|
|
501
|
-
throw error;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
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:
|
|
537
|
+
* Uses the same routing logic as the unified video system:
|
|
510
538
|
* - "auto": main track for production rendering, follows normal routing otherwise
|
|
511
539
|
* - "scrub": force low-res scrub track (for thumbnails)
|
|
512
540
|
* - "main": force full-quality main track
|
|
513
|
-
*
|
|
541
|
+
*
|
|
514
542
|
* @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
|
|
515
|
-
* @param options -
|
|
516
|
-
* @returns
|
|
543
|
+
* @param options - Quality and abort signal
|
|
544
|
+
* @returns VideoFrame that the caller must close()
|
|
517
545
|
* @public
|
|
518
546
|
*/
|
|
519
|
-
async
|
|
520
|
-
const { quality = "auto", signal } = options;
|
|
521
|
-
signal
|
|
547
|
+
async getVideoFrameAtSourceTime(sourceTimeMs, options = {}) {
|
|
548
|
+
const { quality = "auto", signal: providedSignal } = options;
|
|
549
|
+
const signal = providedSignal ?? new AbortController().signal;
|
|
550
|
+
signal.throwIfAborted();
|
|
522
551
|
const mediaEngine = await this.getMediaEngine(signal);
|
|
523
|
-
signal
|
|
552
|
+
signal.throwIfAborted();
|
|
524
553
|
if (!mediaEngine) throw new Error("No media engine available for frame capture");
|
|
525
554
|
const useMainTrack = quality === "main" || quality === "auto" && this.isInProductionRenderingMode();
|
|
526
555
|
let videoSample;
|
|
527
556
|
const { BufferedSeekingInput } = await import("./EFMedia/BufferedSeekingInput.js");
|
|
528
|
-
signal
|
|
557
|
+
signal.throwIfAborted();
|
|
529
558
|
if (useMainTrack) {
|
|
530
559
|
const videoRendition = mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;
|
|
531
560
|
if (!videoRendition) throw new Error("No video rendition available");
|
|
532
561
|
const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, videoRendition);
|
|
533
562
|
if (segmentId === void 0) throw new Error(`Cannot compute segment ID for time ${sourceTimeMs}ms`);
|
|
534
563
|
const seekingInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
|
|
535
|
-
const
|
|
536
|
-
const
|
|
564
|
+
const initP = mediaEngine.fetchInitSegment(videoRendition, signal);
|
|
565
|
+
const mediaP = mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal);
|
|
566
|
+
initP.catch(() => {});
|
|
567
|
+
mediaP.catch(() => {});
|
|
568
|
+
const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
|
|
537
569
|
if (!initSegment || !mediaSegment) return;
|
|
538
570
|
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
539
571
|
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
@@ -541,16 +573,16 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
541
573
|
startTimeOffsetMs: videoRendition.startTimeOffsetMs
|
|
542
574
|
});
|
|
543
575
|
});
|
|
544
|
-
signal
|
|
576
|
+
signal.throwIfAborted();
|
|
545
577
|
if (!seekingInput) throw new Error(`Failed to fetch video segments for time ${sourceTimeMs}ms`);
|
|
546
578
|
const videoTrack = await seekingInput.getFirstVideoTrack();
|
|
547
|
-
signal
|
|
579
|
+
signal.throwIfAborted();
|
|
548
580
|
if (!videoTrack) throw new Error("No video track found in segment");
|
|
549
581
|
videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
|
|
550
|
-
signal
|
|
582
|
+
signal.throwIfAborted();
|
|
551
583
|
} else {
|
|
552
584
|
const scrubRendition = mediaEngine.getScrubVideoRendition?.();
|
|
553
|
-
if (!scrubRendition) return this.
|
|
585
|
+
if (!scrubRendition) return this.getVideoFrameAtSourceTime(sourceTimeMs, {
|
|
554
586
|
quality: "main",
|
|
555
587
|
signal
|
|
556
588
|
});
|
|
@@ -561,8 +593,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
561
593
|
const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, scrubRenditionWithSrc);
|
|
562
594
|
if (segmentId === void 0) throw new Error(`Cannot compute scrub segment ID for time ${sourceTimeMs}ms`);
|
|
563
595
|
const seekingInput = await scrubInputCache.getOrCreateInput(mediaEngine.src, segmentId, async () => {
|
|
564
|
-
const
|
|
565
|
-
const
|
|
596
|
+
const initP = mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal);
|
|
597
|
+
const mediaP = mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal);
|
|
598
|
+
initP.catch(() => {});
|
|
599
|
+
mediaP.catch(() => {});
|
|
600
|
+
const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
|
|
566
601
|
if (!initSegment || !mediaSegment) return;
|
|
567
602
|
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
568
603
|
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
@@ -570,41 +605,59 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
570
605
|
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
571
606
|
});
|
|
572
607
|
});
|
|
573
|
-
signal
|
|
574
|
-
if (!seekingInput) return this.
|
|
608
|
+
signal.throwIfAborted();
|
|
609
|
+
if (!seekingInput) return this.getVideoFrameAtSourceTime(sourceTimeMs, {
|
|
575
610
|
quality: "main",
|
|
576
611
|
signal
|
|
577
612
|
});
|
|
578
613
|
const videoTrack = await seekingInput.getFirstVideoTrack();
|
|
579
|
-
signal
|
|
580
|
-
if (!videoTrack) return this.
|
|
614
|
+
signal.throwIfAborted();
|
|
615
|
+
if (!videoTrack) return this.getVideoFrameAtSourceTime(sourceTimeMs, {
|
|
581
616
|
quality: "main",
|
|
582
617
|
signal
|
|
583
618
|
});
|
|
584
619
|
videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);
|
|
585
|
-
signal
|
|
620
|
+
signal.throwIfAborted();
|
|
586
621
|
}
|
|
587
622
|
if (!videoSample) throw new Error(`No video sample found at ${sourceTimeMs}ms`);
|
|
588
|
-
|
|
623
|
+
return videoSample.toVideoFrame();
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Capture a video frame directly at a source media timestamp.
|
|
627
|
+
* Designed for export/rendering.
|
|
628
|
+
* Does NOT paint to the element's internal canvas.
|
|
629
|
+
*
|
|
630
|
+
* Uses the same routing logic as unified video system:
|
|
631
|
+
* - "auto": main track for production rendering, follows normal routing otherwise
|
|
632
|
+
* - "scrub": force low-res scrub track (for thumbnails)
|
|
633
|
+
* - "main": force full-quality main track
|
|
634
|
+
*
|
|
635
|
+
* @param sourceTimeMs - Timestamp in source media coordinates (not timeline)
|
|
636
|
+
* @param options - Capture options including quality and abort signal
|
|
637
|
+
* @returns Frame data for serialization
|
|
638
|
+
* @public
|
|
639
|
+
*/
|
|
640
|
+
async captureFrameAtSourceTime(sourceTimeMs, options = {}) {
|
|
641
|
+
const videoFrame = await this.getVideoFrameAtSourceTime(sourceTimeMs, options);
|
|
589
642
|
try {
|
|
590
|
-
signal?.throwIfAborted();
|
|
643
|
+
options.signal?.throwIfAborted();
|
|
591
644
|
const canvas = new OffscreenCanvas(videoFrame.codedWidth, videoFrame.codedHeight);
|
|
592
645
|
const ctx = canvas.getContext("2d");
|
|
593
646
|
if (!ctx) throw new Error("Failed to get 2d context from OffscreenCanvas");
|
|
594
647
|
ctx.drawImage(videoFrame, 0, 0);
|
|
595
|
-
signal?.throwIfAborted();
|
|
648
|
+
options.signal?.throwIfAborted();
|
|
596
649
|
const blob = await canvas.convertToBlob({
|
|
597
650
|
type: "image/jpeg",
|
|
598
651
|
quality: .92
|
|
599
652
|
});
|
|
600
|
-
signal?.throwIfAborted();
|
|
653
|
+
options.signal?.throwIfAborted();
|
|
601
654
|
const dataUrl = await new Promise((resolve, reject) => {
|
|
602
655
|
const reader = new FileReader();
|
|
603
656
|
reader.onload = () => resolve(reader.result);
|
|
604
657
|
reader.onerror = reject;
|
|
605
658
|
reader.readAsDataURL(blob);
|
|
606
659
|
});
|
|
607
|
-
signal?.throwIfAborted();
|
|
660
|
+
options.signal?.throwIfAborted();
|
|
608
661
|
return {
|
|
609
662
|
dataUrl,
|
|
610
663
|
width: videoFrame.codedWidth,
|
|
@@ -621,11 +674,12 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
621
674
|
*
|
|
622
675
|
* @param timestamps - Array of timestamps (in ms) that will be captured
|
|
623
676
|
* @param onProgress - Optional callback for loading progress
|
|
677
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
624
678
|
* @returns Promise that resolves when all segments are cached
|
|
625
679
|
* @public
|
|
626
680
|
*/
|
|
627
|
-
async prefetchScrubSegments(timestamps, onProgress) {
|
|
628
|
-
const mediaEngine = await this.getMediaEngine();
|
|
681
|
+
async prefetchScrubSegments(timestamps, onProgress, signal) {
|
|
682
|
+
const mediaEngine = await this.getMediaEngine(signal);
|
|
629
683
|
if (!mediaEngine) {
|
|
630
684
|
log("prefetchScrubSegments: no media engine available");
|
|
631
685
|
return;
|
|
@@ -666,8 +720,9 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
666
720
|
bubbles: true,
|
|
667
721
|
composed: true
|
|
668
722
|
}));
|
|
723
|
+
const fetchSignal = signal ?? new AbortController().signal;
|
|
669
724
|
try {
|
|
670
|
-
await mediaEngine.fetchMediaSegment(firstSegmentId, scrubRenditionWithSrc);
|
|
725
|
+
await mediaEngine.fetchMediaSegment(firstSegmentId, scrubRenditionWithSrc, fetchSignal);
|
|
671
726
|
log(`prefetchScrubSegments: scrub track loaded`);
|
|
672
727
|
} catch (error) {
|
|
673
728
|
log(`prefetchScrubSegments: failed to load scrub track`, error);
|
|
@@ -687,57 +742,85 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
687
742
|
log(`prefetchScrubSegments: complete`);
|
|
688
743
|
}
|
|
689
744
|
/**
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
* @param timestamps - Array of timestamps (in ms) that will be captured
|
|
694
|
-
* @param onProgress - Optional callback for loading progress
|
|
695
|
-
* @returns Promise that resolves when all segments are cached
|
|
696
|
-
* @public
|
|
745
|
+
* Maybe schedule quality upgrade tasks for this element.
|
|
746
|
+
* Called when returning a scrub sample - checks if state has changed and submits tasks.
|
|
697
747
|
*/
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
if (!
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
748
|
+
#maybeScheduleQualityUpgrade(mediaEngine, sourceTimeMs) {
|
|
749
|
+
const mainRendition = mediaEngine.videoRendition;
|
|
750
|
+
if (!mainRendition) return;
|
|
751
|
+
const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, mainRendition);
|
|
752
|
+
if (segmentId === void 0) return;
|
|
753
|
+
const startTimeMs = this.startTimeMs;
|
|
754
|
+
if (!(this.#upgradeState === null || this.#upgradeState.segmentId !== segmentId || this.#upgradeState.startTimeMs !== startTimeMs)) return;
|
|
755
|
+
const segments = this.#computeLookaheadSegments(mediaEngine, sourceTimeMs, mainRendition);
|
|
756
|
+
if (segments.length === 0) return;
|
|
757
|
+
const tasks = segments.map((seg) => ({
|
|
758
|
+
key: `${this.id}:${seg.segmentId}:${mainRendition.id}`,
|
|
759
|
+
fetch: async (signal) => {
|
|
760
|
+
await mediaEngine.fetchInitSegment(mainRendition, signal);
|
|
761
|
+
await mediaEngine.fetchMediaSegment(seg.segmentId, mainRendition, signal);
|
|
762
|
+
},
|
|
763
|
+
deadlineMs: seg.deadlineMs,
|
|
764
|
+
owner: this.id
|
|
765
|
+
}));
|
|
766
|
+
const scheduler = this.rootTimegroup?.qualityUpgradeScheduler;
|
|
767
|
+
if (scheduler) scheduler.replaceForOwner(this.id, tasks);
|
|
768
|
+
else this.#fetchStandalone(tasks);
|
|
769
|
+
this.#upgradeState = {
|
|
770
|
+
sourceTimeMs,
|
|
771
|
+
segmentId,
|
|
772
|
+
startTimeMs,
|
|
773
|
+
submittedKeys: new Set(tasks.map((t) => t.key))
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Compute lookahead segments with deadlines in timeline space.
|
|
778
|
+
*/
|
|
779
|
+
#computeLookaheadSegments(mediaEngine, currentSourceTimeMs, rendition, maxLookahead = 5) {
|
|
780
|
+
const results = [];
|
|
781
|
+
const playheadMs = this.rootTimegroup?.currentTimeMs ?? 0;
|
|
782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
783
|
+
let probeTimeMs = currentSourceTimeMs;
|
|
784
|
+
while (seen.size < maxLookahead) {
|
|
785
|
+
const segmentId = mediaEngine.computeSegmentId(probeTimeMs, rendition);
|
|
786
|
+
if (segmentId === void 0) break;
|
|
787
|
+
if (seen.has(segmentId)) break;
|
|
788
|
+
seen.add(segmentId);
|
|
789
|
+
if (!mediaEngine.isSegmentCached(segmentId, rendition)) {
|
|
790
|
+
const deadlineMs = playheadMs + (probeTimeMs - currentSourceTimeMs);
|
|
791
|
+
results.push({
|
|
792
|
+
segmentId,
|
|
793
|
+
deadlineMs
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
const thisDuration = rendition.segmentDurationsMs?.[segmentId - 1] ?? rendition.segmentDurationMs ?? 2e3;
|
|
797
|
+
probeTimeMs += thisDuration;
|
|
739
798
|
}
|
|
740
|
-
|
|
799
|
+
return results;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Standalone mode: fetch tasks sequentially without scheduler.
|
|
803
|
+
*/
|
|
804
|
+
#fetchStandalone(tasks) {
|
|
805
|
+
this.#standaloneUpgradeController?.abort();
|
|
806
|
+
this.#standaloneUpgradeController = new AbortController();
|
|
807
|
+
const signal = this.#standaloneUpgradeController.signal;
|
|
808
|
+
(async () => {
|
|
809
|
+
for (const task of tasks) {
|
|
810
|
+
if (signal.aborted) break;
|
|
811
|
+
try {
|
|
812
|
+
await task.fetch(signal);
|
|
813
|
+
} catch {}
|
|
814
|
+
}
|
|
815
|
+
if (!signal.aborted) this.playbackController?.runThrottledFrameTask();
|
|
816
|
+
})().catch(() => {});
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Invalidate upgrade state and optionally cancel queued tasks.
|
|
820
|
+
*/
|
|
821
|
+
#invalidateUpgradeState(reason) {
|
|
822
|
+
if (reason === "src-change" || reason === "disconnect") this.rootTimegroup?.qualityUpgradeScheduler?.cancelForOwner(this.id);
|
|
823
|
+
this.#upgradeState = null;
|
|
741
824
|
}
|
|
742
825
|
/**
|
|
743
826
|
* Clean up resources when component is disconnected
|
|
@@ -745,6 +828,9 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
745
828
|
disconnectedCallback() {
|
|
746
829
|
super.disconnectedCallback();
|
|
747
830
|
this.#delayedLoadingState.clearAllLoading();
|
|
831
|
+
this.#invalidateUpgradeState("disconnect");
|
|
832
|
+
this.#standaloneUpgradeController?.abort();
|
|
833
|
+
this.#standaloneUpgradeController = null;
|
|
748
834
|
}
|
|
749
835
|
didBecomeRoot() {
|
|
750
836
|
super.didBecomeRoot();
|
|
@@ -766,19 +852,20 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
766
852
|
height: canvas.height
|
|
767
853
|
};
|
|
768
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Render this video element to an MP4 using the direct video-to-video fast path.
|
|
857
|
+
* Bypasses DOM serialization — decodes frames directly and re-encodes to MP4.
|
|
858
|
+
* Respects trim, CSS filter, and opacity.
|
|
859
|
+
*
|
|
860
|
+
* @param options - Rendering options (fps, codec, bitrate, etc.)
|
|
861
|
+
* @returns Promise resolving to video buffer (if returnBuffer), or undefined
|
|
862
|
+
* @public
|
|
863
|
+
*/
|
|
864
|
+
async renderToVideo(options) {
|
|
865
|
+
const { renderVideoToVideo } = await import("../preview/renderVideoToVideo.js");
|
|
866
|
+
return renderVideoToVideo(this, options);
|
|
867
|
+
}
|
|
769
868
|
};
|
|
770
|
-
__decorate([property({
|
|
771
|
-
type: Number,
|
|
772
|
-
attribute: "video-buffer-duration"
|
|
773
|
-
})], EFVideo.prototype, "videoBufferDurationMs", void 0);
|
|
774
|
-
__decorate([property({
|
|
775
|
-
type: Number,
|
|
776
|
-
attribute: "max-video-buffer-fetches"
|
|
777
|
-
})], EFVideo.prototype, "maxVideoBufferFetches", void 0);
|
|
778
|
-
__decorate([property({
|
|
779
|
-
type: Boolean,
|
|
780
|
-
attribute: "enable-video-buffering"
|
|
781
|
-
})], EFVideo.prototype, "enableVideoBuffering", void 0);
|
|
782
869
|
__decorate([state()], EFVideo.prototype, "loadingState", void 0);
|
|
783
870
|
EFVideo = __decorate([customElement("ef-video")], EFVideo);
|
|
784
871
|
|