@editframe/elements 0.37.3-beta → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +2 -2
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +5 -8
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +2 -2
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +2 -2
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +2 -2
- package/dist/gui/EFOverlayLayer.d.ts +2 -2
- package/dist/gui/EFPause.d.ts +2 -2
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +2 -2
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +2 -2
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +2 -2
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +6 -2
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +2 -2
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +2 -2
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +2 -2
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +2 -2
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +2 -2
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +2 -2
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
- package/dist/preview/renderTimegroupToCanvas.js +321 -146
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
- package/dist/preview/renderTimegroupToVideo.js +94 -106
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.d.ts +56 -0
- package/dist/preview/renderers.js +13 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.d.ts +13 -0
- package/dist/preview/rendering/inlineImages.js +7 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.d.ts +8 -0
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +45 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/tsdown.config.ts +6 -1
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -43,6 +43,7 @@ declare const EFMedia_base: (new (...args: any[]) => EFSourceMixinInterface) & (
|
|
|
43
43
|
declare class EFMedia extends EFMedia_base {
|
|
44
44
|
#private;
|
|
45
45
|
get efContext(): ControllableInterface | null;
|
|
46
|
+
shouldAutoReady(): boolean;
|
|
46
47
|
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
47
48
|
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
48
49
|
/**
|
|
@@ -58,6 +59,7 @@ declare class EFMedia extends EFMedia_base {
|
|
|
58
59
|
get requiredTracks(): "audio" | "video" | "both";
|
|
59
60
|
static get observedAttributes(): string[];
|
|
60
61
|
static styles: lit0.CSSResult[];
|
|
62
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
61
63
|
/**
|
|
62
64
|
* Duration in milliseconds for audio buffering ahead of current time
|
|
63
65
|
* @domAttribute "audio-buffer-duration"
|
|
@@ -98,7 +100,7 @@ declare class EFMedia extends EFMedia_base {
|
|
|
98
100
|
* @domAttribute "interpolate-frequencies"
|
|
99
101
|
*/
|
|
100
102
|
interpolateFrequencies: boolean;
|
|
101
|
-
getFreqWeights(): Float32Array
|
|
103
|
+
getFreqWeights(): Float32Array<ArrayBufferLike>;
|
|
102
104
|
getShouldInterpolateFrequencies(): boolean;
|
|
103
105
|
getUrlGenerator(): UrlGenerator;
|
|
104
106
|
/**
|
|
@@ -108,17 +110,17 @@ declare class EFMedia extends EFMedia_base {
|
|
|
108
110
|
mediaEngineTask: AsyncValue<MediaEngine>;
|
|
109
111
|
/**
|
|
110
112
|
* Get or create the MediaEngine for this element.
|
|
111
|
-
* Uses caching based on src/
|
|
113
|
+
* Uses caching based on src/fileId to avoid redundant fetches.
|
|
112
114
|
*/
|
|
113
115
|
getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined>;
|
|
114
116
|
/**
|
|
115
117
|
* Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility
|
|
116
118
|
*/
|
|
117
|
-
frequencyDataTask: AsyncValue<Uint8Array | null>;
|
|
119
|
+
frequencyDataTask: AsyncValue<Uint8Array<ArrayBufferLike> | null>;
|
|
118
120
|
/**
|
|
119
121
|
* Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility
|
|
120
122
|
*/
|
|
121
|
-
byteTimeDomainTask: AsyncValue<Uint8Array | null>;
|
|
123
|
+
byteTimeDomainTask: AsyncValue<Uint8Array<ArrayBufferLike> | null>;
|
|
122
124
|
/**
|
|
123
125
|
* Get frequency data for audio visualization at a given time.
|
|
124
126
|
*/
|
|
@@ -134,11 +136,15 @@ declare class EFMedia extends EFMedia_base {
|
|
|
134
136
|
audioSeekTask: AsyncValue<any>;
|
|
135
137
|
audioBufferTask: AsyncValue<any>;
|
|
136
138
|
/**
|
|
137
|
-
* The unique identifier for the media
|
|
138
|
-
* This property can be set programmatically or via the "
|
|
139
|
-
*
|
|
139
|
+
* The unique identifier for the media file.
|
|
140
|
+
* This property can be set programmatically or via the "file-id" attribute.
|
|
141
|
+
* The "asset-id" attribute is also supported for backward compatibility.
|
|
142
|
+
* @domAttribute "file-id"
|
|
140
143
|
*/
|
|
141
|
-
|
|
144
|
+
fileId: string | null;
|
|
145
|
+
/** @deprecated Use fileId instead */
|
|
146
|
+
get assetId(): string | null;
|
|
147
|
+
set assetId(value: string | null);
|
|
142
148
|
get intrinsicDurationMs(): number | undefined;
|
|
143
149
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
144
150
|
get hasOwnDuration(): boolean;
|
package/dist/elements/EFMedia.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { efContext } from "../gui/efContext.js";
|
|
2
|
+
import { withSpan } from "../otel/tracingHelpers.js";
|
|
1
3
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
2
4
|
import { EFTemporal } from "./EFTemporal.js";
|
|
3
|
-
import { efContext } from "../gui/efContext.js";
|
|
4
5
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
5
|
-
import { withSpan } from "../otel/tracingHelpers.js";
|
|
6
6
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
|
|
7
7
|
import { LRUCache } from "../utils/LRUCache.js";
|
|
8
8
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
@@ -161,12 +161,15 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
161
161
|
this.audioInputTask = new AsyncValue();
|
|
162
162
|
this.audioSeekTask = new AsyncValue();
|
|
163
163
|
this.audioBufferTask = new AsyncValue();
|
|
164
|
-
this.
|
|
164
|
+
this.fileId = null;
|
|
165
165
|
this._desiredSeekTimeMs = 0;
|
|
166
166
|
}
|
|
167
167
|
get efContext() {
|
|
168
168
|
return this.rootTimegroup ?? this;
|
|
169
169
|
}
|
|
170
|
+
shouldAutoReady() {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
170
173
|
static {
|
|
171
174
|
this.VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
172
175
|
}
|
|
@@ -179,7 +182,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
179
182
|
* - "audio" - Only needs audio track (e.g., EFAudio)
|
|
180
183
|
* - "video" - Only needs video track
|
|
181
184
|
* - "both" - Needs both tracks (default for backwards compatibility)
|
|
182
|
-
*
|
|
185
|
+
*
|
|
183
186
|
* This is used during media engine creation to skip validation
|
|
184
187
|
* of tracks that won't be used, avoiding unnecessary network requests.
|
|
185
188
|
*/
|
|
@@ -194,6 +197,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
194
197
|
"fft-decay",
|
|
195
198
|
"fft-gain",
|
|
196
199
|
"interpolate-frequencies",
|
|
200
|
+
"file-id",
|
|
197
201
|
"asset-id",
|
|
198
202
|
"audio-buffer-duration",
|
|
199
203
|
"max-audio-buffer-fetches",
|
|
@@ -211,6 +215,13 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
211
215
|
}
|
|
212
216
|
`];
|
|
213
217
|
}
|
|
218
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
219
|
+
if (name === "asset-id") {
|
|
220
|
+
this.fileId = newValue;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
224
|
+
}
|
|
214
225
|
getFreqWeights() {
|
|
215
226
|
if (freqWeightsCache.has(this.fftSize)) return freqWeightsCache.get(this.fftSize);
|
|
216
227
|
const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
|
|
@@ -238,37 +249,47 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
238
249
|
#mediaEngineSrcKey = null;
|
|
239
250
|
/**
|
|
240
251
|
* Get or create the MediaEngine for this element.
|
|
241
|
-
* Uses caching based on src/
|
|
252
|
+
* Uses caching based on src/fileId to avoid redundant fetches.
|
|
242
253
|
*/
|
|
243
254
|
async getMediaEngine(signal) {
|
|
244
|
-
const srcKey = `${this.src}|${this.
|
|
245
|
-
if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine)
|
|
255
|
+
const srcKey = `${this.src}|${this.fileId}`;
|
|
256
|
+
if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {
|
|
257
|
+
this.setContentReadyState("ready");
|
|
258
|
+
return this.#mediaEngine;
|
|
259
|
+
}
|
|
246
260
|
if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) return this.#mediaEnginePromise;
|
|
247
261
|
this.#mediaEngineSrcKey = srcKey;
|
|
248
262
|
this.mediaEngineTask.startPending();
|
|
249
|
-
this
|
|
263
|
+
this.setContentReadyState("loading");
|
|
264
|
+
const loadPromise = this.#loadMediaEngine(signal);
|
|
265
|
+
this.#mediaEnginePromise = loadPromise;
|
|
266
|
+
return loadPromise;
|
|
267
|
+
}
|
|
268
|
+
async #loadMediaEngine(signal) {
|
|
250
269
|
try {
|
|
251
|
-
this.#mediaEngine = await this.#
|
|
270
|
+
this.#mediaEngine = await this.#createMediaEngine(signal);
|
|
252
271
|
this.#mediaEngineError = void 0;
|
|
253
272
|
if (this.#mediaEngine) {
|
|
254
273
|
this.mediaEngineTask.setValue(this.#mediaEngine);
|
|
255
274
|
this.#handleMediaEngineComplete();
|
|
256
|
-
|
|
275
|
+
this.setContentReadyState("ready");
|
|
276
|
+
} else this.setContentReadyState("idle");
|
|
257
277
|
return this.#mediaEngine;
|
|
258
278
|
} catch (error) {
|
|
259
279
|
this.#mediaEngineError = error instanceof Error ? error : new Error(String(error));
|
|
260
280
|
this.mediaEngineTask.setError(this.#mediaEngineError);
|
|
281
|
+
this.setContentReadyState("error");
|
|
261
282
|
if (!(error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.message === "No valid media source" || error.message.includes("File not found") || error.message.includes("404") || error.message.includes("Failed to fetch")))) console.error("Media engine error:", error);
|
|
262
283
|
return;
|
|
263
284
|
}
|
|
264
285
|
}
|
|
265
286
|
async #createMediaEngine(signal) {
|
|
266
|
-
const { src,
|
|
287
|
+
const { src, fileId, apiHost, requiredTracks } = this;
|
|
267
288
|
const urlGenerator = this.getUrlGenerator();
|
|
268
|
-
if (
|
|
269
|
-
if (!apiHost) throw new Error("API host is required for
|
|
270
|
-
const {
|
|
271
|
-
return
|
|
289
|
+
if (fileId !== null && fileId !== void 0 && fileId.trim() !== "") {
|
|
290
|
+
if (!apiHost) throw new Error("API host is required for file-id mode");
|
|
291
|
+
const { FileMediaEngine } = await import("./EFMedia/FileMediaEngine.js");
|
|
292
|
+
return await FileMediaEngine.fetchByFileId(this, urlGenerator, fileId, apiHost, requiredTracks, signal);
|
|
272
293
|
}
|
|
273
294
|
if (!src || typeof src !== "string" || src.trim() === "") return;
|
|
274
295
|
const lowerSrc = src.toLowerCase();
|
|
@@ -501,12 +522,22 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
501
522
|
}
|
|
502
523
|
return smoothedData;
|
|
503
524
|
}
|
|
525
|
+
/** @deprecated Use fileId instead */
|
|
526
|
+
get assetId() {
|
|
527
|
+
return this.fileId;
|
|
528
|
+
}
|
|
529
|
+
set assetId(value) {
|
|
530
|
+
this.fileId = value;
|
|
531
|
+
}
|
|
504
532
|
get intrinsicDurationMs() {
|
|
505
533
|
return this.#mediaEngine?.durationMs;
|
|
506
534
|
}
|
|
507
535
|
updated(changedProperties) {
|
|
508
536
|
super.updated(changedProperties);
|
|
509
|
-
if (changedProperties.has("src") || changedProperties.has("
|
|
537
|
+
if (changedProperties.has("src") || changedProperties.has("fileId")) {
|
|
538
|
+
this.getMediaEngine().catch(() => {});
|
|
539
|
+
if (changedProperties.get("src") !== void 0 || changedProperties.get("fileId") !== void 0) this.emitContentChange("source");
|
|
540
|
+
}
|
|
510
541
|
const newCurrentSourceTimeMs = this.currentSourceTimeMs;
|
|
511
542
|
if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) this.executeSeek(newCurrentSourceTimeMs);
|
|
512
543
|
if (changedProperties.has("ownCurrentTimeMs")) this.executeSeek(this.currentSourceTimeMs);
|
|
@@ -516,6 +547,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
516
547
|
"_sourceInMs",
|
|
517
548
|
"_sourceOutMs"
|
|
518
549
|
].some((prop) => changedProperties.has(prop))) {
|
|
550
|
+
this.emitContentChange("bounds");
|
|
519
551
|
if (this.parentTimegroup) {
|
|
520
552
|
this.parentTimegroup.requestUpdate("durationMs");
|
|
521
553
|
this.parentTimegroup.requestUpdate("currentTime");
|
|
@@ -556,8 +588,9 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
556
588
|
durationMs: toMs - fromMs,
|
|
557
589
|
src: this.src || "none"
|
|
558
590
|
}, void 0, async () => {
|
|
591
|
+
const effectiveSignal = signal ?? new AbortController().signal;
|
|
559
592
|
const { fetchAudioSpanningTime } = await import("./EFMedia/shared/AudioSpanUtils.js");
|
|
560
|
-
return fetchAudioSpanningTime(this, fromMs, toMs,
|
|
593
|
+
return fetchAudioSpanningTime(this, fromMs, toMs, effectiveSignal);
|
|
561
594
|
});
|
|
562
595
|
}
|
|
563
596
|
/**
|
|
@@ -632,9 +665,9 @@ __decorate([property({
|
|
|
632
665
|
})], EFMedia.prototype, "interpolateFrequencies", void 0);
|
|
633
666
|
__decorate([property({
|
|
634
667
|
type: String,
|
|
635
|
-
attribute: "
|
|
668
|
+
attribute: "file-id",
|
|
636
669
|
reflect: true
|
|
637
|
-
})], EFMedia.prototype, "
|
|
670
|
+
})], EFMedia.prototype, "fileId", void 0);
|
|
638
671
|
__decorate([state()], EFMedia.prototype, "_desiredSeekTimeMs", void 0);
|
|
639
672
|
|
|
640
673
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","url","JitMediaEngine","#frequencyDataCache","#analyzeFrequencies","#timeDomainDataCache","#analyzeTimeDomain"],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { MediaEngine } from \"../transcoding/types/index.ts\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { LRUCache } from \"../utils/LRUCache.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * Duplicated here to avoid circular imports from EFTemporal.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n return Array.from(element.children);\n};\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n const children = getChildrenIncludingSlotted(element);\n for (const child of children) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\n/**\n * Simple async value wrapper that mimics Lit Task interface.\n * Used for backwards compatibility with code expecting task-like objects.\n */\nexport class AsyncValue<T> {\n #value: T | undefined = undefined;\n #error: Error | undefined = undefined;\n #status: \"initial\" | \"pending\" | \"complete\" | \"error\" = \"initial\";\n #promise: Promise<T | undefined> = Promise.resolve(undefined);\n #resolvePromise: ((value: T | undefined) => void) | undefined;\n\n // Use properties instead of getters to avoid TypeScript declaration generation bug\n get value(): T | undefined {\n return this.#value;\n }\n\n get error(): Error | undefined {\n return this.#error;\n }\n\n get status(): number {\n // Match TaskStatus enum: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n switch (this.#status) {\n case \"initial\": return 0;\n case \"pending\": return 1;\n case \"complete\": return 2;\n case \"error\": return 3;\n }\n }\n\n get taskComplete(): Promise<T | undefined> {\n return this.#promise;\n }\n\n /**\n * Set the value (marks status as complete)\n */\n setValue(value: T): void {\n this.#value = value;\n this.#error = undefined;\n this.#status = \"complete\";\n this.#resolvePromise?.(value);\n }\n\n /**\n * Set an error (marks status as error)\n */\n setError(error: Error): void {\n this.#error = error;\n this.#value = undefined;\n this.#status = \"error\";\n // Don't reject - just resolve with undefined to match old behavior\n this.#resolvePromise?.(undefined);\n }\n\n /**\n * Start a new async operation\n */\n startPending(): void {\n this.#status = \"pending\";\n this.#promise = new Promise((resolve) => {\n this.#resolvePromise = resolve;\n });\n // Prevent unhandled rejection warnings\n this.#promise.catch(() => {});\n }\n\n /**\n * Run an async function and update status accordingly\n */\n async run(fn: () => Promise<T>): Promise<T | undefined> {\n this.startPending();\n try {\n const result = await fn();\n this.setValue(result);\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.setError(error);\n } else {\n this.setError(new Error(String(error)));\n }\n return undefined;\n }\n }\n}\n\n// Audio analysis helper functions\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n let zeroCount = 0;\n let cutoffIndex = totalBins;\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1;\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0;\n } else {\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n * \n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n getFreqWeights() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper method for backwards compatibility\n getShouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n getUrlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n // ============================================================================\n // Media Engine - replaced task with async method + cached wrapper\n // ============================================================================\n\n #mediaEngine: MediaEngine | undefined = undefined;\n #mediaEnginePromise: Promise<MediaEngine | undefined> | undefined = undefined;\n #mediaEngineError: Error | undefined = undefined;\n #mediaEngineSrcKey: string | null = null;\n\n /**\n * Async wrapper that mimics Task interface for backwards compatibility.\n * Code expecting mediaEngineTask.value, .taskComplete, .error, .status will still work.\n */\n mediaEngineTask = new AsyncValue<MediaEngine>();\n\n /**\n * Get or create the MediaEngine for this element.\n * Uses caching based on src/assetId to avoid redundant fetches.\n */\n async getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const srcKey = `${this.src}|${this.assetId}`;\n \n // Return cached if src hasn't changed\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {\n return this.#mediaEngine;\n }\n\n // If already loading for this src, wait for it\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) {\n return this.#mediaEnginePromise;\n }\n\n // Start new load\n this.#mediaEngineSrcKey = srcKey;\n this.mediaEngineTask.startPending();\n\n this.#mediaEnginePromise = this.#createMediaEngine(signal);\n \n try {\n this.#mediaEngine = await this.#mediaEnginePromise;\n this.#mediaEngineError = undefined;\n if (this.#mediaEngine) {\n this.mediaEngineTask.setValue(this.#mediaEngine);\n this.#handleMediaEngineComplete();\n }\n return this.#mediaEngine;\n } catch (error) {\n this.#mediaEngineError = error instanceof Error ? error : new Error(String(error));\n this.mediaEngineTask.setError(this.#mediaEngineError);\n \n // Don't throw for expected errors\n const isExpectedError = error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"404\") ||\n error.message.includes(\"Failed to fetch\")\n );\n \n if (!isExpectedError) {\n console.error(\"Media engine error:\", error);\n }\n \n return undefined;\n }\n }\n\n async #createMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const { src, assetId, apiHost, requiredTracks } = this;\n const urlGenerator = this.getUrlGenerator();\n\n // Check for AssetID mode first\n if (assetId !== null && assetId !== undefined && assetId.trim() !== \"\") {\n if (!apiHost) {\n throw new Error(\"API host is required for AssetID mode\");\n }\n const { AssetIdMediaEngine } = await import(\"./EFMedia/AssetIdMediaEngine.js\");\n return AssetIdMediaEngine.fetchByAssetId(\n this,\n urlGenerator,\n assetId,\n apiHost,\n requiredTracks,\n signal,\n );\n }\n\n // Check for null/undefined/empty/whitespace src\n if (!src || typeof src !== \"string\" || src.trim() === \"\") {\n return undefined;\n }\n\n const lowerSrc = src.toLowerCase();\n const isRemoteUrl = lowerSrc.startsWith(\"http://\") || lowerSrc.startsWith(\"https://\");\n \n // Check configuration for explicit engine preference\n const configuration = this.closest(\"ef-configuration\");\n \n // \"jit\" mode: Force JitMediaEngine for all sources (including local files)\n if (configuration?.mediaEngine === \"jit\") {\n let manifestSrc = src;\n if (!isRemoteUrl && configuration.apiHost) {\n const baseUrl = configuration.apiHost.replace(/\\/$/, \"\");\n const normalizedPath = src.replace(/^\\.\\//, \"/src/\");\n manifestSrc = `${baseUrl}${normalizedPath}`;\n }\n const url = urlGenerator.generateManifestUrl(manifestSrc);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n \n // \"local\" mode: Force AssetMediaEngine for all sources\n if (configuration?.mediaEngine === \"local\") {\n const { AssetMediaEngine } = await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);\n }\n \n // \"cloud\" mode (default): AssetMediaEngine for local paths, JitMediaEngine for remote URLs\n if (!isRemoteUrl) {\n const { AssetMediaEngine } = await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);\n }\n\n // Default: Use JitMediaEngine for remote URLs (transcoding service)\n const url = urlGenerator.generateManifestUrl(src);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n\n #handleMediaEngineComplete(): void {\n // Update self synchronously\n this.requestUpdate(\"intrinsicDurationMs\");\n this.requestUpdate(\"ownCurrentTimeMs\");\n \n // Defer updates to parent/root timegroup\n if (this.rootTimegroup) {\n queueMicrotask(() => {\n this.rootTimegroup?.requestUpdate(\"ownCurrentTimeMs\");\n this.rootTimegroup?.requestUpdate(\"durationMs\");\n });\n }\n }\n\n // ============================================================================\n // Audio Analysis - replaced tasks with async methods + cached wrappers\n // ============================================================================\n\n #frequencyDataCache = new LRUCache<string, Uint8Array>(100);\n #timeDomainDataCache = new LRUCache<string, Uint8Array>(100);\n\n /**\n * Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility\n */\n frequencyDataTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility\n */\n byteTimeDomainTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Get frequency data for audio visualization at a given time.\n */\n async getFrequencyData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;\n const cached = this.#frequencyDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeFrequencies(timeMs, signal);\n if (result) {\n this.#frequencyDataCache.set(cacheKey, result);\n this.frequencyDataTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n /**\n * Get time domain data for audio visualization at a given time.\n */\n async getTimeDomainData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.fftSize}:${timeMs}`;\n const cached = this.#timeDomainDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeTimeDomain(timeMs, signal);\n if (result) {\n this.#timeDomainDataCache.set(cacheKey, result);\n this.byteTimeDomainTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n async #analyzeFrequencies(currentTimeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n \n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n \n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(this.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Apply frequency weights\n smoothedData.forEach((value, i) => {\n const freqWeight = this.getFreqWeights()[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));\n return this.getShouldInterpolateFrequencies() ? processFFTData(slicedData) : slicedData;\n }\n\n async #analyzeTimeDomain(currentTimeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n \n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n \n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(this.fftSize);\n analyser.getByteTimeDomainData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Use RMS calculation to preserve waveform shape\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let sumSquares = 0;\n framesData.forEach((frame: Uint8Array) => {\n const value = (frame[i] ?? 128) - 128;\n sumSquares += value * value;\n });\n const rms = Math.sqrt(sumSquares / framesData.length);\n smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));\n }\n\n return smoothedData;\n }\n\n // ============================================================================\n // Removed task properties - these are kept as stubs for backwards compatibility\n // ============================================================================\n\n // These tasks are no longer used but kept for API compatibility\n audioSegmentIdTask = new AsyncValue<number | undefined>();\n audioInitSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioInputTask = new AsyncValue<any>();\n audioSeekTask = new AsyncValue<any>();\n audioBufferTask = new AsyncValue<any>();\n\n /**\n * The unique identifier for the media asset.\n * This property can be set programmatically or via the \"asset-id\" attribute.\n * @domAttribute \"asset-id\"\n */\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n assetId: string | null = null;\n\n get intrinsicDurationMs(): number | undefined {\n return this.#mediaEngine?.durationMs;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Trigger media engine load when src or assetId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"assetId\")) {\n // Start loading media engine asynchronously\n this.getMediaEngine().catch(() => {});\n }\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration(): boolean {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs(): number {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this._desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n const { fetchAudioSpanningTime } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n return fetchAudioSpanningTime(this, fromMs, toMs, signal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.#mediaEngine) {\n return;\n }\n \n try {\n await this.getMediaEngine(signal);\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")\n );\n \n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n \n // For task abort (element disconnected), silently return\n if (isAbortError) {\n return;\n }\n \n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#loadMediaEngine","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","url","JitMediaEngine","#frequencyDataCache","#analyzeFrequencies","#timeDomainDataCache","#analyzeTimeDomain"],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { MediaEngine } from \"../transcoding/types/index.ts\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { LRUCache } from \"../utils/LRUCache.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * Duplicated here to avoid circular imports from EFTemporal.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll(\"slot\");\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== \"SLOT\") {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n return Array.from(element.children);\n};\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n const children = getChildrenIncludingSlotted(element);\n for (const child of children) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\n/**\n * Simple async value wrapper that mimics Lit Task interface.\n * Used for backwards compatibility with code expecting task-like objects.\n */\nexport class AsyncValue<T> {\n #value: T | undefined = undefined;\n #error: Error | undefined = undefined;\n #status: \"initial\" | \"pending\" | \"complete\" | \"error\" = \"initial\";\n #promise: Promise<T | undefined> = Promise.resolve(undefined);\n #resolvePromise: ((value: T | undefined) => void) | undefined;\n\n // Use properties instead of getters to avoid TypeScript declaration generation bug\n get value(): T | undefined {\n return this.#value;\n }\n\n get error(): Error | undefined {\n return this.#error;\n }\n\n get status(): number {\n // Match TaskStatus enum: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n switch (this.#status) {\n case \"initial\":\n return 0;\n case \"pending\":\n return 1;\n case \"complete\":\n return 2;\n case \"error\":\n return 3;\n }\n }\n\n get taskComplete(): Promise<T | undefined> {\n return this.#promise;\n }\n\n /**\n * Set the value (marks status as complete)\n */\n setValue(value: T): void {\n this.#value = value;\n this.#error = undefined;\n this.#status = \"complete\";\n this.#resolvePromise?.(value);\n }\n\n /**\n * Set an error (marks status as error)\n */\n setError(error: Error): void {\n this.#error = error;\n this.#value = undefined;\n this.#status = \"error\";\n // Don't reject - just resolve with undefined to match old behavior\n this.#resolvePromise?.(undefined);\n }\n\n /**\n * Start a new async operation\n */\n startPending(): void {\n this.#status = \"pending\";\n this.#promise = new Promise((resolve) => {\n this.#resolvePromise = resolve;\n });\n // Prevent unhandled rejection warnings\n this.#promise.catch(() => {});\n }\n\n /**\n * Run an async function and update status accordingly\n */\n async run(fn: () => Promise<T>): Promise<T | undefined> {\n this.startPending();\n try {\n const result = await fn();\n this.setValue(result);\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.setError(error);\n } else {\n this.setError(new Error(String(error)));\n }\n return undefined;\n }\n }\n}\n\n// Audio analysis helper functions\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n let zeroCount = 0;\n let cutoffIndex = totalBins;\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1;\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0;\n } else {\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n override shouldAutoReady(): boolean {\n return false;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n *\n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"file-id\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ): void {\n if (name === \"asset-id\") {\n this.fileId = newValue;\n return;\n }\n super.attributeChangedCallback(name, oldValue, newValue);\n }\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n getFreqWeights() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper method for backwards compatibility\n getShouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n getUrlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n // ============================================================================\n // Media Engine - replaced task with async method + cached wrapper\n // ============================================================================\n\n #mediaEngine: MediaEngine | undefined = undefined;\n #mediaEnginePromise: Promise<MediaEngine | undefined> | undefined = undefined;\n #mediaEngineError: Error | undefined = undefined;\n #mediaEngineSrcKey: string | null = null;\n\n /**\n * Async wrapper that mimics Task interface for backwards compatibility.\n * Code expecting mediaEngineTask.value, .taskComplete, .error, .status will still work.\n */\n mediaEngineTask = new AsyncValue<MediaEngine>();\n\n /**\n * Get or create the MediaEngine for this element.\n * Uses caching based on src/fileId to avoid redundant fetches.\n */\n async getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const srcKey = `${this.src}|${this.fileId}`;\n\n // Return cached if src hasn't changed\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {\n this.setContentReadyState(\"ready\");\n return this.#mediaEngine;\n }\n\n // If already loading for this src, wait for it\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) {\n return this.#mediaEnginePromise;\n }\n\n // Start new load\n this.#mediaEngineSrcKey = srcKey;\n this.mediaEngineTask.startPending();\n this.setContentReadyState(\"loading\");\n\n // Store the handled promise so that concurrent callers at the cache check\n // (line above) get a resolved promise, not a raw rejecting one.\n const loadPromise = this.#loadMediaEngine(signal);\n this.#mediaEnginePromise = loadPromise;\n return loadPromise;\n }\n\n async #loadMediaEngine(\n signal?: AbortSignal,\n ): Promise<MediaEngine | undefined> {\n try {\n this.#mediaEngine = await this.#createMediaEngine(signal);\n this.#mediaEngineError = undefined;\n if (this.#mediaEngine) {\n this.mediaEngineTask.setValue(this.#mediaEngine);\n this.#handleMediaEngineComplete();\n this.setContentReadyState(\"ready\");\n } else {\n // No engine (empty/invalid src) — return to idle\n this.setContentReadyState(\"idle\");\n }\n return this.#mediaEngine;\n } catch (error) {\n this.#mediaEngineError =\n error instanceof Error ? error : new Error(String(error));\n this.mediaEngineTask.setError(this.#mediaEngineError);\n this.setContentReadyState(\"error\");\n\n // Don't throw for expected errors\n const isExpectedError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"404\") ||\n error.message.includes(\"Failed to fetch\")));\n\n if (!isExpectedError) {\n console.error(\"Media engine error:\", error);\n }\n\n return undefined;\n }\n }\n\n async #createMediaEngine(\n signal?: AbortSignal,\n ): Promise<MediaEngine | undefined> {\n const { src, fileId, apiHost, requiredTracks } = this;\n const urlGenerator = this.getUrlGenerator();\n\n // Check for file-id mode first\n if (fileId !== null && fileId !== undefined && fileId.trim() !== \"\") {\n if (!apiHost) {\n throw new Error(\"API host is required for file-id mode\");\n }\n const { FileMediaEngine } = await import(\"./EFMedia/FileMediaEngine.js\");\n const engine = await FileMediaEngine.fetchByFileId(\n this,\n urlGenerator,\n fileId,\n apiHost,\n requiredTracks,\n signal,\n );\n return engine;\n }\n\n // Check for null/undefined/empty/whitespace src\n if (!src || typeof src !== \"string\" || src.trim() === \"\") {\n return undefined;\n }\n\n const lowerSrc = src.toLowerCase();\n const isRemoteUrl =\n lowerSrc.startsWith(\"http://\") || lowerSrc.startsWith(\"https://\");\n\n // Check configuration for explicit engine preference\n const configuration = this.closest(\"ef-configuration\");\n\n // \"jit\" mode: Force JitMediaEngine for all sources (including local files)\n if (configuration?.mediaEngine === \"jit\") {\n let manifestSrc = src;\n if (!isRemoteUrl && configuration.apiHost) {\n const baseUrl = configuration.apiHost.replace(/\\/$/, \"\");\n const normalizedPath = src.replace(/^\\.\\//, \"/src/\");\n manifestSrc = `${baseUrl}${normalizedPath}`;\n }\n const url = urlGenerator.generateManifestUrl(manifestSrc);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n\n // \"local\" mode: Force AssetMediaEngine for all sources\n if (configuration?.mediaEngine === \"local\") {\n const { AssetMediaEngine } =\n await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(\n this,\n urlGenerator,\n src,\n requiredTracks,\n signal,\n );\n }\n\n // \"cloud\" mode (default): AssetMediaEngine for local paths, JitMediaEngine for remote URLs\n if (!isRemoteUrl) {\n const { AssetMediaEngine } =\n await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(\n this,\n urlGenerator,\n src,\n requiredTracks,\n signal,\n );\n }\n\n // Default: Use JitMediaEngine for remote URLs (transcoding service)\n const url = urlGenerator.generateManifestUrl(src);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n\n #handleMediaEngineComplete(): void {\n // Update self synchronously\n this.requestUpdate(\"intrinsicDurationMs\");\n this.requestUpdate(\"ownCurrentTimeMs\");\n\n // Defer updates to parent/root timegroup\n if (this.rootTimegroup) {\n queueMicrotask(() => {\n this.rootTimegroup?.requestUpdate(\"ownCurrentTimeMs\");\n this.rootTimegroup?.requestUpdate(\"durationMs\");\n });\n }\n }\n\n // ============================================================================\n // Audio Analysis - replaced tasks with async methods + cached wrappers\n // ============================================================================\n\n #frequencyDataCache = new LRUCache<string, Uint8Array>(100);\n #timeDomainDataCache = new LRUCache<string, Uint8Array>(100);\n\n /**\n * Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility\n */\n frequencyDataTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility\n */\n byteTimeDomainTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Get frequency data for audio visualization at a given time.\n */\n async getFrequencyData(\n timeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;\n const cached = this.#frequencyDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeFrequencies(timeMs, signal);\n if (result) {\n this.#frequencyDataCache.set(cacheKey, result);\n this.frequencyDataTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n /**\n * Get time domain data for audio visualization at a given time.\n */\n async getTimeDomainData(\n timeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.fftSize}:${timeMs}`;\n const cached = this.#timeDomainDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeTimeDomain(timeMs, signal);\n if (result) {\n this.#timeDomainDataCache.set(cacheKey, result);\n this.byteTimeDomainTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n async #analyzeFrequencies(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs =\n currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Apply frequency weights\n smoothedData.forEach((value, i) => {\n const freqWeight = this.getFreqWeights()[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n const slicedData = smoothedData.slice(\n 0,\n Math.floor(smoothedData.length / 2),\n );\n return this.getShouldInterpolateFrequencies()\n ? processFFTData(slicedData)\n : slicedData;\n }\n\n async #analyzeTimeDomain(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs =\n currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize);\n analyser.getByteTimeDomainData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Use RMS calculation to preserve waveform shape\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let sumSquares = 0;\n framesData.forEach((frame: Uint8Array) => {\n const value = (frame[i] ?? 128) - 128;\n sumSquares += value * value;\n });\n const rms = Math.sqrt(sumSquares / framesData.length);\n smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));\n }\n\n return smoothedData;\n }\n\n // ============================================================================\n // Removed task properties - these are kept as stubs for backwards compatibility\n // ============================================================================\n\n // These tasks are no longer used but kept for API compatibility\n audioSegmentIdTask = new AsyncValue<number | undefined>();\n audioInitSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioInputTask = new AsyncValue<any>();\n audioSeekTask = new AsyncValue<any>();\n audioBufferTask = new AsyncValue<any>();\n\n /**\n * The unique identifier for the media file.\n * This property can be set programmatically or via the \"file-id\" attribute.\n * The \"asset-id\" attribute is also supported for backward compatibility.\n * @domAttribute \"file-id\"\n */\n @property({ type: String, attribute: \"file-id\", reflect: true })\n fileId: string | null = null;\n\n /** @deprecated Use fileId instead */\n get assetId(): string | null {\n return this.fileId;\n }\n set assetId(value: string | null) {\n this.fileId = value;\n }\n\n get intrinsicDurationMs(): number | undefined {\n return this.#mediaEngine?.durationMs;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Trigger media engine load when src or fileId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"fileId\")) {\n this.getMediaEngine().catch(() => {});\n // Source identity changed — cached renderable output is stale\n if (\n changedProperties.get(\"src\") !== undefined ||\n changedProperties.get(\"fileId\") !== undefined\n ) {\n this.emitContentChange(\"source\");\n }\n }\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n this.emitContentChange(\"bounds\");\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration(): boolean {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs(): number {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this._desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n // Create a default signal if not provided (public API convenience)\n const effectiveSignal = signal ?? new AbortController().signal;\n const { fetchAudioSpanningTime } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n return fetchAudioSpanningTime(this, fromMs, toMs, effectiveSignal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.#mediaEngine) {\n return;\n }\n\n try {\n await this.getMediaEngine(signal);\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")));\n\n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n\n // For task abort (element disconnected), silently return\n if (isAbortError) {\n return;\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,mCAAmB,IAAI,KAA2B;;;;;AAQxD,MAAM,+BAA+B,YAAgC;AACnE,KAAI,QAAQ,YAAY;EACtB,MAAM,QAAQ,QAAQ,WAAW,iBAAiB,OAAO;AACzD,MAAI,MAAM,SAAS,GAAG;GACpB,MAAMA,mBAA8B,EAAE;AACtC,QAAK,MAAM,QAAQ,MACjB,kBAAiB,KAAK,GAAG,KAAK,kBAAkB,CAAC;AAEnD,QAAK,MAAM,SAAS,QAAQ,WAAW,SACrC,KAAI,MAAM,YAAY,OACpB,kBAAiB,KAAK,MAAM;AAGhC,UAAO;;;AAGX,QAAO,MAAM,KAAK,QAAQ,SAAS;;AAGrC,MAAa,wBACX,SACA,SAAoB,EAAE,KACnB;CACH,MAAM,WAAW,4BAA4B,QAAQ;AACrD,MAAK,MAAM,SAAS,SAClB,KAAI,iBAAiB,QACnB,QAAO,KAAK,MAAM;KAElB,sBAAqB,OAAO,OAAO;AAGvC,QAAO;;;;;;AAWT,IAAa,aAAb,MAA2B;CACzB,SAAwB;CACxB,SAA4B;CAC5B,UAAwD;CACxD,WAAmC,QAAQ,QAAQ,OAAU;CAC7D;CAGA,IAAI,QAAuB;AACzB,SAAO,MAAKC;;CAGd,IAAI,QAA2B;AAC7B,SAAO,MAAKC;;CAGd,IAAI,SAAiB;AAEnB,UAAQ,MAAKC,QAAb;GACE,KAAK,UACH,QAAO;GACT,KAAK,UACH,QAAO;GACT,KAAK,WACH,QAAO;GACT,KAAK,QACH,QAAO;;;CAIb,IAAI,eAAuC;AACzC,SAAO,MAAKC;;;;;CAMd,SAAS,OAAgB;AACvB,QAAKH,QAAS;AACd,QAAKC,QAAS;AACd,QAAKC,SAAU;AACf,QAAKE,iBAAkB,MAAM;;;;;CAM/B,SAAS,OAAoB;AAC3B,QAAKH,QAAS;AACd,QAAKD,QAAS;AACd,QAAKE,SAAU;AAEf,QAAKE,iBAAkB,OAAU;;;;;CAMnC,eAAqB;AACnB,QAAKF,SAAU;AACf,QAAKC,UAAW,IAAI,SAAS,YAAY;AACvC,SAAKC,iBAAkB;IACvB;AAEF,QAAKD,QAAS,YAAY,GAAG;;;;;CAM/B,MAAM,IAAI,IAA8C;AACtD,OAAK,cAAc;AACnB,MAAI;GACF,MAAM,SAAS,MAAM,IAAI;AACzB,QAAK,SAAS,OAAO;AACrB,UAAO;WACA,OAAO;AACd,OAAI,iBAAiB,MACnB,MAAK,SAAS,MAAM;OAEpB,MAAK,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;AAEzC;;;;AAMN,MAAM,eAAe;AAErB,SAAS,eACP,SACA,uBAAuB,IACX;CACZ,MAAM,YAAY,QAAQ;CAC1B,MAAM,qBAAqB,KAAK,MAAM,YAAY,qBAAqB;CAEvE,IAAI,YAAY;CAChB,IAAI,cAAc;AAElB,MAAK,IAAI,IAAI,YAAY,GAAG,KAAK,GAAG,IAClC,KAAI,QAAQ,MAAM,KAChB;UAEI,aAAa,oBAAoB;AACnC,gBAAc,IAAI;AAClB;;AAKN,KAAI,cAAc,mBAChB,QAAO;CAIT,MAAM,gBAAgB,gBADL,QAAQ,MAAM,GAAG,YAAY,EACE,QAAQ,OAAO;CAE/D,MAAM,wBAAwB,KAAK,MAAM,YAAY,GAAI;AACzD,MAAK,IAAI,IAAI,uBAAuB,IAAI,WAAW,KAAK;EACtD,MAAM,uBACH,IAAI,0BAA0B,YAAY,yBAAyB;EACtE,MAAM,oBAAoB,KAAK,IAAI,GAAG,IAAI,oBAAoB;AAC9D,gBAAc,KAAK,KAAK,OAAO,cAAc,MAAM,KAAK,kBAAkB;;AAG5E,QAAO;;AAGT,SAAS,gBAAgB,MAAkB,YAAgC;CACzE,MAAM,YAAY,IAAI,WAAW,WAAW;CAC5C,MAAM,aAAa,KAAK;AAExB,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;EACnC,MAAM,QAAS,KAAK,aAAa,MAAO,aAAa;EACrD,MAAM,QAAQ,KAAK,MAAM,MAAM;EAC/B,MAAM,WAAW,QAAQ;AAEzB,MAAI,SAAS,aAAa,EACxB,WAAU,KAAK,KAAK,aAAa,MAAM;MAEvC,WAAU,KAAK,KAAK,OACjB,KAAK,UAAU,MAAM,IAAI,aAAa,KAAK,QAAQ,MAAM,KAAK,SAChE;;AAIL,QAAO;;AAGT,IAAa,UAAb,cAA6B,aAC3B,cAAc,WAAW,WAAW,WAAW,CAAC,EAAE,EAChD,WAAW,iBACZ,CAAC,CACH,CAAC;;;+BA2EwB;+BAOA;8BAOD;cAWhB;iBAOG;kBAOC;iBAOD;gCAWe;yBA8CP,IAAI,YAAyB;2BA8K3B,IAAI,YAA+B;4BAKlC,IAAI,YAA+B;4BA+TnC,IAAI,YAAgC;mCAC7B,IAAI,YAAqC;+BAC7C,IAAI,YAAqC;wBAChD,IAAI,YAAiB;uBACtB,IAAI,YAAiB;yBACnB,IAAI,YAAiB;gBASf;4BAiFK;;CAlwB7B,IACI,YAA0C;AAC5C,SAAO,KAAK,iBAAiB;;CAG/B,AAAS,kBAA2B;AAClC,SAAO;;;kCAIkC;;;kCACA;;;;;;;;;;;;CAY3C,IAAI,iBAA6C;AAC/C,SAAO;;CAGT,WAAW,qBAAqB;AAG9B,SAAO;GACL,GAFuB,MAAM,sBAAsB,EAAE;GAGrD;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;;;gBAGa,CACd,GAAG;;;;;;MAOJ;;CAED,yBACE,MACA,UACA,UACM;AACN,MAAI,SAAS,YAAY;AACvB,QAAK,SAAS;AACd;;AAEF,QAAM,yBAAyB,MAAM,UAAU,SAAS;;CAoE1D,iBAAiB;AACf,MAAI,iBAAiB,IAAI,KAAK,QAAQ,CAEpC,QAAO,iBAAiB,IAAI,KAAK,QAAQ;EAG3C,MAAM,UAAU,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC,KAAK,GAAG,MAAM;GAC/D,MAAM,YAAa,IAAI,OAAS,KAAK;AACrC,OAAI,YAAY,GAAI,QAAO;AAC3B,OAAI,YAAY,IAAK,QAAO;AAC5B,OAAI,YAAY,IAAK,QAAO;AAC5B,OAAI,YAAY,IAAM,QAAO;AAC7B,OAAI,YAAY,IAAM,QAAO;AAC7B,OAAI,YAAY,IAAM,QAAO;AAC7B,UAAO;IACP;AAEF,mBAAiB,IAAI,KAAK,SAAS,QAAQ;AAC3C,SAAO;;CAIT,kCAAkC;AAChC,SAAO,KAAK;;CAGd,kBAAkB;AAChB,SAAO,IAAI,mBAAmB,KAAK,WAAW,GAAG;;CAOnD,eAAwC;CACxC,sBAAoE;CACpE,oBAAuC;CACvC,qBAAoC;;;;;CAYpC,MAAM,eAAe,QAAwD;EAC3E,MAAM,SAAS,GAAG,KAAK,IAAI,GAAG,KAAK;AAGnC,MAAI,MAAKE,sBAAuB,UAAU,MAAKC,aAAc;AAC3D,QAAK,qBAAqB,QAAQ;AAClC,UAAO,MAAKA;;AAId,MAAI,MAAKD,sBAAuB,UAAU,MAAKE,mBAC7C,QAAO,MAAKA;AAId,QAAKF,oBAAqB;AAC1B,OAAK,gBAAgB,cAAc;AACnC,OAAK,qBAAqB,UAAU;EAIpC,MAAM,cAAc,MAAKG,gBAAiB,OAAO;AACjD,QAAKD,qBAAsB;AAC3B,SAAO;;CAGT,OAAMC,gBACJ,QACkC;AAClC,MAAI;AACF,SAAKF,cAAe,MAAM,MAAKG,kBAAmB,OAAO;AACzD,SAAKC,mBAAoB;AACzB,OAAI,MAAKJ,aAAc;AACrB,SAAK,gBAAgB,SAAS,MAAKA,YAAa;AAChD,UAAKK,2BAA4B;AACjC,SAAK,qBAAqB,QAAQ;SAGlC,MAAK,qBAAqB,OAAO;AAEnC,UAAO,MAAKL;WACL,OAAO;AACd,SAAKI,mBACH,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAC3D,QAAK,gBAAgB,SAAS,MAAKA,iBAAkB;AACrD,QAAK,qBAAqB,QAAQ;AAWlC,OAAI,EAPD,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,YAAY,2BACjB,MAAM,QAAQ,SAAS,iBAAiB,IACxC,MAAM,QAAQ,SAAS,MAAM,IAC7B,MAAM,QAAQ,SAAS,kBAAkB,GAG7C,SAAQ,MAAM,uBAAuB,MAAM;AAG7C;;;CAIJ,OAAMD,kBACJ,QACkC;EAClC,MAAM,EAAE,KAAK,QAAQ,SAAS,mBAAmB;EACjD,MAAM,eAAe,KAAK,iBAAiB;AAG3C,MAAI,WAAW,QAAQ,WAAW,UAAa,OAAO,MAAM,KAAK,IAAI;AACnE,OAAI,CAAC,QACH,OAAM,IAAI,MAAM,wCAAwC;GAE1D,MAAM,EAAE,oBAAoB,MAAM,OAAO;AASzC,UARe,MAAM,gBAAgB,cACnC,MACA,cACA,QACA,SACA,gBACA,OACD;;AAKH,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,KAAK,GACpD;EAGF,MAAM,WAAW,IAAI,aAAa;EAClC,MAAM,cACJ,SAAS,WAAW,UAAU,IAAI,SAAS,WAAW,WAAW;EAGnE,MAAM,gBAAgB,KAAK,QAAQ,mBAAmB;AAGtD,MAAI,eAAe,gBAAgB,OAAO;GACxC,IAAI,cAAc;AAClB,OAAI,CAAC,eAAe,cAAc,QAGhC,eAAc,GAFE,cAAc,QAAQ,QAAQ,OAAO,GAAG,GACjC,IAAI,QAAQ,SAAS,QAAQ;GAGtD,MAAMG,QAAM,aAAa,oBAAoB,YAAY;GACzD,MAAM,EAAE,qCAAmB,MAAM,OAAO;AACxC,UAAOC,iBAAe,MAAM,MAAM,cAAcD,OAAK,OAAO;;AAI9D,MAAI,eAAe,gBAAgB,SAAS;GAC1C,MAAM,EAAE,qBACN,MAAM,OAAO;AACf,UAAO,iBAAiB,MACtB,MACA,cACA,KACA,gBACA,OACD;;AAIH,MAAI,CAAC,aAAa;GAChB,MAAM,EAAE,qBACN,MAAM,OAAO;AACf,UAAO,iBAAiB,MACtB,MACA,cACA,KACA,gBACA,OACD;;EAIH,MAAM,MAAM,aAAa,oBAAoB,IAAI;EACjD,MAAM,EAAE,mBAAmB,MAAM,OAAO;AACxC,SAAO,eAAe,MAAM,MAAM,cAAc,KAAK,OAAO;;CAG9D,6BAAmC;AAEjC,OAAK,cAAc,sBAAsB;AACzC,OAAK,cAAc,mBAAmB;AAGtC,MAAI,KAAK,cACP,sBAAqB;AACnB,QAAK,eAAe,cAAc,mBAAmB;AACrD,QAAK,eAAe,cAAc,aAAa;IAC/C;;CAQN,sBAAsB,IAAI,SAA6B,IAAI;CAC3D,uBAAuB,IAAI,SAA6B,IAAI;;;;CAe5D,MAAM,iBACJ,QACA,QAC4B;AAC5B,MAAI,SAAS,EAAG,QAAO;EAEvB,MAAM,WAAW,GAAG,KAAK,iCAAiC,CAAC,GAAG,KAAK,QAAQ,GAAG,KAAK,SAAS,GAAG,KAAK,QAAQ,GAAG;EAC/G,MAAM,SAAS,MAAKE,mBAAoB,IAAI,SAAS;AACrD,MAAI,OAAQ,QAAO;AAEnB,MAAI;GACF,MAAM,SAAS,MAAM,MAAKC,mBAAoB,QAAQ,OAAO;AAC7D,OAAI,QAAQ;AACV,UAAKD,mBAAoB,IAAI,UAAU,OAAO;AAC9C,SAAK,kBAAkB,SAAS,OAAO;;AAEzC,UAAO;WACA,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,UAAO;;;;;;CAOX,MAAM,kBACJ,QACA,QAC4B;AAC5B,MAAI,SAAS,EAAG,QAAO;EAEvB,MAAM,WAAW,GAAG,KAAK,QAAQ,GAAG;EACpC,MAAM,SAAS,MAAKE,oBAAqB,IAAI,SAAS;AACtD,MAAI,OAAQ,QAAO;AAEnB,MAAI;GACF,MAAM,SAAS,MAAM,MAAKC,kBAAmB,QAAQ,OAAO;AAC5D,OAAI,QAAQ;AACV,UAAKD,oBAAqB,IAAI,UAAU,OAAO;AAC/C,SAAK,mBAAmB,SAAS,OAAO;;AAE1C,UAAO;WACA,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,UAAO;;;CAIX,OAAMD,mBACJ,eACA,QAC4B;EAC5B,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,UAAQ,gBAAgB;AAExB,MAAI,CAAC,aAAa,eAChB,QAAO;EAIT,MAAM,kBAAkB,MAAO;EAC/B,MAAM,kBACJ,iBAAiB,KAAK,WAAW,KAAK;EACxC,MAAM,SAAS,KAAK,IAAI,GAAG,gBAAgB;EAC3C,MAAM,UAAU,gBAAgB;EAChC,MAAM,kBAAkB,KAAK,uBAAuB;EACpD,MAAM,OACJ,kBAAkB,IAAI,KAAK,IAAI,SAAS,gBAAgB,GAAG;AAE7D,MAAI,UAAU,KACZ,QAAO;EAGT,MAAM,EAAE,wBAAwB,mBAC9B,MAAM,OAAO;EAEf,IAAI;AACJ,MAAI;AACF,eAAY,MAAM,eAAe,MAAM,QAAQ,MAAM,OAAQ;WACtD,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,UAAO;;AAGT,MAAI,CAAC,WAAW,QAAQ,UAAU,KAAK,OAAO,IAC5C,QAAO;EAIT,MAAM,mBAAmB,IAAI,oBAAoB,GAAG,MAAO,KAAM;EACjE,MAAM,cAAc,MAAM,UAAU,KAAK,aAAa;AACtD,UAAQ,gBAAgB;EAExB,IAAI;AACJ,MAAI;AACF,iBAAc,MAAM,iBAAiB,gBAAgB,YAAY;AACjE,WAAQ,gBAAgB;UAClB;AACN,UAAO;;EAGT,MAAM,gBAAgB,UAAU;EAEhC,MAAM,aAAa,MAAM,QAAQ,IAC/B,MAAM,KAAK,EAAE,QAAQ,KAAK,UAAU,EAAE,OAAO,GAAG,MAAM;GACpD,MAAM,cAAc,KAAK,MAAO;GAChC,MAAM,YAAY,KAAK,IACrB,IACC,gBAAgB,cAAc,iBAAiB,IACjD;GAGD,MAAM,eAAe,IAAI,oBAAoB,GADhC,OAAQ,IACiC,KAAM;GAC5D,MAAM,WAAW,aAAa,gBAAgB;AAC9C,YAAS,UAAU,KAAK;AACxB,YAAS,cAAc;AACvB,YAAS,cAAc;GAEvB,MAAM,WAAW,aAAa,YAAY;AAC1C,YAAS,KAAK,QAAQ,KAAK;GAE3B,MAAM,SAAS,aAAa,oBAAoB;AAChD,UAAO,OAAO;AACd,UAAO,UAAU,QAAQ;AACzB,UAAO,EAAE,QAAQ;GAEjB,MAAM,oBAAoB,aAAa,oBAAoB;AAC3D,qBAAkB,SAAS;AAE3B,qBAAkB,QAAQ,OAAO;AACjC,UAAO,QAAQ,SAAS;AACxB,YAAS,QAAQ,SAAS;AAC1B,YAAS,QAAQ,aAAa,YAAY;AAE1C,qBAAkB,MAAM,GAAG,WAAW,IAAI,GAAG;AAE7C,OAAI;AACF,UAAM,aAAa,gBAAgB;AACnC,YAAQ,gBAAgB;IAExB,MAAM,YAAY,IAAI,WAAW,KAAK,UAAU,EAAE;AAClD,aAAS,qBAAqB,UAAU;AACxC,WAAO;aACC;AACR,sBAAkB,YAAY;AAC9B,aAAS,YAAY;;IAEvB,CACH;EAED,MAAM,cAAc,WAAW,IAAI,UAAU;EAG7C,MAAM,eAAe,IAAI,WAAW,YAAY;AAChD,OAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,IAAI,cAAc;GAClB,IAAI,YAAY;AAEhB,cAAW,SAAS,OAAmB,eAAuB;IAC5D,MAAM,cAAc,gBAAgB;AACpC,oBAAgB,MAAM,MAAM,KAAK;AACjC,iBAAa;KACb;AAEF,gBAAa,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,cAAc,UAAU,CAAC;;AAItE,eAAa,SAAS,OAAO,MAAM;GACjC,MAAM,aAAa,KAAK,gBAAgB,CAAC,MAAM;AAC/C,gBAAa,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ,WAAW,CAAC;IAC/D;EAGF,MAAM,aAAa,aAAa,MAC9B,GACA,KAAK,MAAM,aAAa,SAAS,EAAE,CACpC;AACD,SAAO,KAAK,iCAAiC,GACzC,eAAe,WAAW,GAC1B;;CAGN,OAAME,kBACJ,eACA,QAC4B;EAC5B,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,UAAQ,gBAAgB;AAExB,MAAI,CAAC,aAAa,eAChB,QAAO;EAGT,MAAM,kBAAkB,MAAO;EAC/B,MAAM,kBACJ,iBAAiB,KAAK,WAAW,KAAK;EACxC,MAAM,SAAS,KAAK,IAAI,GAAG,gBAAgB;EAC3C,MAAM,UAAU,gBAAgB;EAChC,MAAM,kBAAkB,KAAK,uBAAuB;EACpD,MAAM,OACJ,kBAAkB,IAAI,KAAK,IAAI,SAAS,gBAAgB,GAAG;AAE7D,MAAI,UAAU,KACZ,QAAO;EAGT,MAAM,EAAE,wBAAwB,mBAC9B,MAAM,OAAO;EAEf,IAAI;AACJ,MAAI;AACF,eAAY,MAAM,eAAe,MAAM,QAAQ,MAAM,OAAQ;WACtD,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,UAAO;;AAGT,MAAI,CAAC,WAAW,QAAQ,UAAU,KAAK,OAAO,IAC5C,QAAO;EAGT,MAAM,mBAAmB,IAAI,oBAAoB,GAAG,MAAO,KAAM;EACjE,MAAM,cAAc,MAAM,UAAU,KAAK,aAAa;AACtD,UAAQ,gBAAgB;EAExB,IAAI;AACJ,MAAI;AACF,iBAAc,MAAM,iBAAiB,gBAAgB,YAAY;AACjE,WAAQ,gBAAgB;UAClB;AACN,UAAO;;EAGT,MAAM,gBAAgB,UAAU;EAEhC,MAAM,aAAa,MAAM,QAAQ,IAC/B,MAAM,KAAK,EAAE,QAAQ,KAAK,UAAU,EAAE,OAAO,GAAG,MAAM;GACpD,MAAM,cAAc,KAAK,MAAO;GAChC,MAAM,YAAY,KAAK,IACrB,IACC,gBAAgB,cAAc,iBAAiB,IACjD;GAGD,MAAM,eAAe,IAAI,oBAAoB,GADhC,OAAQ,IACiC,KAAM;GAC5D,MAAM,WAAW,aAAa,gBAAgB;AAC9C,YAAS,UAAU,KAAK;AACxB,YAAS,cAAc;AACvB,YAAS,cAAc;GAEvB,MAAM,WAAW,aAAa,YAAY;AAC1C,YAAS,KAAK,QAAQ,KAAK;GAE3B,MAAM,SAAS,aAAa,oBAAoB;AAChD,UAAO,OAAO;AACd,UAAO,UAAU,QAAQ;AACzB,UAAO,EAAE,QAAQ;GAEjB,MAAM,oBAAoB,aAAa,oBAAoB;AAC3D,qBAAkB,SAAS;AAE3B,qBAAkB,QAAQ,OAAO;AACjC,UAAO,QAAQ,SAAS;AACxB,YAAS,QAAQ,SAAS;AAC1B,YAAS,QAAQ,aAAa,YAAY;AAE1C,qBAAkB,MAAM,GAAG,WAAW,IAAI,GAAG;AAE7C,OAAI;AACF,UAAM,aAAa,gBAAgB;AACnC,YAAQ,gBAAgB;IAExB,MAAM,YAAY,IAAI,WAAW,KAAK,QAAQ;AAC9C,aAAS,sBAAsB,UAAU;AACzC,WAAO;aACC;AACR,sBAAkB,YAAY;AAC9B,aAAS,YAAY;;IAEvB,CACH;EAED,MAAM,cAAc,WAAW,IAAI,UAAU;EAG7C,MAAM,eAAe,IAAI,WAAW,YAAY;AAChD,OAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,IAAI,aAAa;AACjB,cAAW,SAAS,UAAsB;IACxC,MAAM,SAAS,MAAM,MAAM,OAAO;AAClC,kBAAc,QAAQ;KACtB;GACF,MAAM,MAAM,KAAK,KAAK,aAAa,WAAW,OAAO;AACrD,gBAAa,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,IAAI,CAAC,CAAC;;AAGrE,SAAO;;;CAyBT,IAAI,UAAyB;AAC3B,SAAO,KAAK;;CAEd,IAAI,QAAQ,OAAsB;AAChC,OAAK,SAAS;;CAGhB,IAAI,sBAA0C;AAC5C,SAAO,MAAKX,aAAc;;CAG5B,AAAU,QACR,mBACM;AACN,QAAM,QAAQ,kBAAkB;AAGhC,MAAI,kBAAkB,IAAI,MAAM,IAAI,kBAAkB,IAAI,SAAS,EAAE;AACnE,QAAK,gBAAgB,CAAC,YAAY,GAAG;AAErC,OACE,kBAAkB,IAAI,MAAM,KAAK,UACjC,kBAAkB,IAAI,SAAS,KAAK,OAEpC,MAAK,kBAAkB,SAAS;;EAKpC,MAAM,yBAAyB,KAAK;AACpC,MAAI,2BAA2B,KAAK,kBAClC,MAAK,YAAY,uBAAuB;AAG1C,MAAI,kBAAkB,IAAI,mBAAmB,CAC3C,MAAK,YAAY,KAAK,oBAAoB;AAe5C,MAX+B;GAC7B;GACA;GACA;GACA;GACD,CAEgD,MAAM,SACrD,kBAAkB,IAAI,KAAK,CAC5B,EAEsB;AACrB,QAAK,kBAAkB,SAAS;AAChC,OAAI,KAAK,iBAAiB;AACxB,SAAK,gBAAgB,cAAc,aAAa;AAChD,SAAK,gBAAgB,cAAc,cAAc;IAGjD,IAAI,SAAS,KAAK;AAClB,WAAO,QAAQ;AACb,SAAI,eAAe,OAAO,EAAE;AAC1B,aAAO,cACL,IAAI,YAAY,0BAA0B,EACxC,QAAQ,EAAE,QAAQ,MAAM,EACzB,CAAC,CACH;AACD;;AAEF,cAAS,OAAO;;;;;CAMxB,IAAI,iBAA0B;AAC5B,SAAO;;CAMT,IAAI,oBAA4B;AAC9B,SAAO,KAAK;;CAGd,IAAI,kBAAkB,OAAe;AACnC,MAAI,KAAK,uBAAuB,MAC9B,MAAK,qBAAqB;;CAI9B,MAAgB,YAAY,UAAkB;AAG5C,OAAK,qBAAqB;;;;;;;CAQ5B,MAAM,uBACJ,QACA,MACA,QACgC;AAChC,SAAO,SACL,gCACA;GACE,WAAW,KAAK,MAAM;GACtB,SAAS,KAAK,QAAQ,aAAa;GACnC;GACA;GACA,YAAY,OAAO;GACnB,KAAK,KAAK,OAAO;GAClB,EACD,QACA,YAAY;GAEV,MAAM,kBAAkB,UAAU,IAAI,iBAAiB,CAAC;GACxD,MAAM,EAAE,2BACN,MAAM,OAAO;AACf,UAAO,uBAAuB,MAAM,QAAQ,MAAM,gBAAgB;IAErE;;;;;;CAOH,MAAM,sBAAsB,QAAqC;AAC/D,MAAI,MAAKA,YACP;AAGF,MAAI;AACF,SAAM,KAAK,eAAe,OAAO;WAC1B,OAAO;GAEd,MAAM,eACH,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACd,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,6BAA6B;AAG1D,OAAI,QAAQ,QACV,OAAM;AAIR,OAAI,aACF;AAIF,SAAM;;;;;;;;CASV,mBAA8B;AAC5B,SAAO,CAAC,KAAK;;;;;;;CAQf,MAAM,YAAY,QAAgB,MAAoC;AACpE,SAAO,oBAAoB,MAAM,QAAQ,KAAK;;;YAr2B/C,QAAQ,EAAE,SAAS,WAAW,CAAC;YAyE/B,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAyB,CAAC;YAO9D,SAAS;CAAE,MAAM;CAAQ,WAAW;CAA4B,CAAC;YAOjE,SAAS;CAAE,MAAM;CAAS,WAAW;CAA0B,CAAC;YAOhE,SAAS;CACR,MAAM;CACN,WAAW;CACX,SAAS;CACV,CAAC;YAOD,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAY,SAAS;CAAM,CAAC;YAOhE,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAa,SAAS;CAAM,CAAC;YAOjE,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAY,SAAS;CAAM,CAAC;YAOhE,SAAS;CACR,MAAM;CACN,WAAW;CACX,SAAS;CACV,CAAC;YA8iBD,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAW,SAAS;CAAM,CAAC;YAiF/D,OAAO"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as lit28 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html26 from "lit-html";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/EFPanZoom.d.ts
|
|
6
6
|
interface PanZoomTransform {
|
|
@@ -89,7 +89,7 @@ declare class EFPanZoom extends LitElement {
|
|
|
89
89
|
* @param padding - Padding factor (0-1), e.g., 0.1 = 10% padding on each side. Default: 0.05
|
|
90
90
|
*/
|
|
91
91
|
fitToContent(padding?: number): void;
|
|
92
|
-
render():
|
|
92
|
+
render(): lit_html26.TemplateResult<1>;
|
|
93
93
|
}
|
|
94
94
|
//#endregion
|
|
95
95
|
export { EFPanZoom, PanZoomTransform };
|
|
@@ -244,7 +244,7 @@ let EFPanZoom = class EFPanZoom$1 extends LitElement {
|
|
|
244
244
|
const availableHeight = containerRect.height * (1 - 2 * padding);
|
|
245
245
|
const scaleX = availableWidth / contentWidth;
|
|
246
246
|
const scaleY = availableHeight / contentHeight;
|
|
247
|
-
const newScale = Math.min(scaleX, scaleY
|
|
247
|
+
const newScale = Math.min(scaleX, scaleY);
|
|
248
248
|
const scaledWidth = contentWidth * newScale;
|
|
249
249
|
const scaledHeight = contentHeight * newScale;
|
|
250
250
|
const newX = (containerRect.width - scaledWidth) / 2;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFPanZoom.js","names":["EFPanZoom","panZoom: Element | null"],"sources":["../../src/elements/EFPanZoom.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { provide } from \"@lit/context\";\nimport { panZoomTransformContext } from \"../gui/panZoomTransformContext.js\";\n\nexport interface PanZoomTransform {\n x: number;\n y: number;\n scale: number;\n}\n\n@customElement(\"ef-pan-zoom\")\nexport class EFPanZoom extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n overflow: hidden;\n position: relative;\n touch-action: none;\n }\n .content-wrapper {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n transform-origin: 0 0;\n }\n `,\n ];\n\n @property({ type: Number, reflect: true })\n x = 0;\n\n @property({ type: Number, reflect: true })\n y = 0;\n\n @property({ type: Number, reflect: true })\n scale = 1;\n\n /**\n * When true, automatically fits content to view on first render.\n * Centers content and scales it to fit within the container with padding.\n */\n @property({ type: Boolean, attribute: \"auto-fit\" })\n autoFit = false;\n\n @provide({ context: panZoomTransformContext })\n panZoomTransform: PanZoomTransform = { x: 0, y: 0, scale: 1 };\n\n private _isDragging = false;\n private _dragStartPointerPos: { x: number; y: number } | null = null;\n private _dragStartTransform: PanZoomTransform | null = null;\n private _capturedPointerId: number | null = null;\n\n /**\n * Document-level wheel handler in capture phase to prevent browser navigation.\n * This prevents back/forward navigation on two-finger swipe gestures.\n * We use capture phase to catch events before they bubble, but only prevent default\n * (not stop propagation) so the normal wheel handler can still process them.\n */\n private _onDocumentWheelCapture = (e: WheelEvent) => {\n // Check if event is over this panzoom element or its children\n let panZoom: Element | null = null;\n if (e.target instanceof Element) {\n panZoom = e.target.closest(\"ef-pan-zoom\");\n // Also check if target is an overlay sibling (selection overlay, etc.)\n // Overlays have pointer-events: none but can still be the event target\n if (!panZoom && e.target.closest(\"ef-canvas-selection-overlay\")) {\n // Event is over selection overlay - check if it's over this panzoom's area\n const rect = this.getBoundingClientRect();\n if (\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom\n ) {\n panZoom = this;\n }\n }\n }\n if (panZoom === this) {\n // Prevent browser navigation gestures (back/forward on swipe)\n // Don't stop propagation - let the normal wheel handler process the event\n e.preventDefault();\n }\n };\n\n connectedCallback() {\n super.connectedCallback();\n // Add document-level capture listener to prevent browser navigation\n document.addEventListener(\"wheel\", this._onDocumentWheelCapture, {\n passive: false,\n capture: true,\n });\n // Add element-level event listeners\n this.addEventListener(\"wheel\", this._onWheel, { passive: false });\n this.addEventListener(\"pointerdown\", this._onPointerDown);\n this.addEventListener(\"pointermove\", this._onPointerMove);\n this.addEventListener(\"pointerup\", this._onPointerUp);\n this.addEventListener(\"pointercancel\", this._onPointerUp);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n // Remove document-level capture listener\n document.removeEventListener(\"wheel\", this._onDocumentWheelCapture, {\n capture: true,\n });\n // Remove element-level event listeners\n this.removeEventListener(\"wheel\", this._onWheel);\n this.removeEventListener(\"pointerdown\", this._onPointerDown);\n this.removeEventListener(\"pointermove\", this._onPointerMove);\n this.removeEventListener(\"pointerup\", this._onPointerUp);\n this.removeEventListener(\"pointercancel\", this._onPointerUp);\n // Clean up pointer capture if dragging\n if (this._isDragging && this._capturedPointerId !== null) {\n try {\n this.releasePointerCapture(this._capturedPointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n }\n }\n\n private _updateTransform(updates: Partial<PanZoomTransform>) {\n const newTransform = {\n x: updates.x !== undefined ? updates.x : this.x,\n y: updates.y !== undefined ? updates.y : this.y,\n scale:\n updates.scale !== undefined\n ? Math.max(0.1, Math.min(5, updates.scale))\n : this.scale,\n };\n\n const changed =\n newTransform.x !== this.x ||\n newTransform.y !== this.y ||\n newTransform.scale !== this.scale;\n\n if (changed) {\n this.x = newTransform.x;\n this.y = newTransform.y;\n this.scale = newTransform.scale;\n\n // Update context for overlay components\n this.panZoomTransform = { ...newTransform };\n\n this.dispatchEvent(\n new CustomEvent<PanZoomTransform>(\"transform-changed\", {\n detail: { ...newTransform },\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n if (e.button !== 0) return;\n\n this._isDragging = true;\n this._capturedPointerId = e.pointerId;\n this._dragStartPointerPos = { x: e.clientX, y: e.clientY };\n this._dragStartTransform = {\n x: this.x,\n y: this.y,\n scale: this.scale,\n };\n\n try {\n this.setPointerCapture(e.pointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n };\n\n private _onPointerMove = (e: PointerEvent) => {\n if (\n !this._isDragging ||\n !this._dragStartPointerPos ||\n !this._dragStartTransform\n )\n return;\n\n const deltaX = e.clientX - this._dragStartPointerPos.x;\n const deltaY = e.clientY - this._dragStartPointerPos.y;\n\n this._updateTransform({\n x: this._dragStartTransform.x - deltaX,\n y: this._dragStartTransform.y - deltaY,\n });\n };\n\n private _onPointerUp = (e: PointerEvent) => {\n if (!this._isDragging) return;\n if (this._capturedPointerId !== null) {\n try {\n this.releasePointerCapture(e.pointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n }\n\n this._isDragging = false;\n this._capturedPointerId = null;\n this._dragStartPointerPos = null;\n this._dragStartTransform = null;\n };\n\n private _onWheel = (e: WheelEvent) => {\n // Always prevent default to prevent browser navigation (back/forward on swipe)\n // This is critical for full-page app interfaces\n e.preventDefault();\n e.stopPropagation();\n\n const isZoom = e.metaKey || e.ctrlKey;\n\n if (isZoom) {\n const containerRect = this.getBoundingClientRect();\n const pointerX = e.clientX - containerRect.left;\n const pointerY = e.clientY - containerRect.top;\n\n const currentX = this.x;\n const currentY = this.y;\n const currentScale = this.scale;\n\n const canvasX = (pointerX - currentX) / currentScale;\n const canvasY = (pointerY - currentY) / currentScale;\n\n const delta = e.deltaY > 0 ? 0.95 : 1.05;\n const newScale = Math.max(0.1, Math.min(5, currentScale * delta));\n\n const newX = pointerX - canvasX * newScale;\n const newY = pointerY - canvasY * newScale;\n\n this._updateTransform({\n x: newX,\n y: newY,\n scale: newScale,\n });\n } else {\n const deltaX = -e.deltaX;\n const deltaY = -e.deltaY;\n\n this._updateTransform({\n x: this.x + deltaX,\n y: this.y + deltaY,\n });\n }\n };\n\n firstUpdated(changedProperties: Map<PropertyKey, unknown>) {\n super.firstUpdated(changedProperties);\n // Initialize context with current transform\n this.panZoomTransform = { x: this.x, y: this.y, scale: this.scale };\n\n // Auto-fit content if enabled (use RAF to ensure content is rendered)\n if (this.autoFit) {\n requestAnimationFrame(() => {\n this.fitToContent();\n });\n }\n }\n\n /**\n * Convert screen coordinates (e.g., mouse event clientX/clientY) to canvas coordinates.\n * This handles all pan/zoom transformations automatically.\n *\n * @param screenX - X coordinate in screen space (e.g., event.clientX)\n * @param screenY - Y coordinate in screen space (e.g., event.clientY)\n * @returns Object with x, y in canvas coordinate space\n *\n * @example\n * handleClick(e: MouseEvent) {\n * const canvasPos = panZoom.screenToCanvas(e.clientX, e.clientY);\n * console.log(`Clicked at canvas position: ${canvasPos.x}, ${canvasPos.y}`);\n * }\n */\n screenToCanvas(screenX: number, screenY: number): { x: number; y: number } {\n const rect = this.getBoundingClientRect();\n return {\n x: (screenX - rect.left - this.x) / this.scale,\n y: (screenY - rect.top - this.y) / this.scale,\n };\n }\n\n /**\n * Convert canvas coordinates to screen coordinates.\n * Useful for positioning overlays or tooltips relative to canvas elements.\n *\n * @param canvasX - X coordinate in canvas space\n * @param canvasY - Y coordinate in canvas space\n * @returns Object with x, y in screen coordinate space\n *\n * @example\n * const screenPos = panZoom.canvasToScreen(element.x, element.y);\n * tooltip.style.left = `${screenPos.x}px`;\n * tooltip.style.top = `${screenPos.y}px`;\n */\n canvasToScreen(canvasX: number, canvasY: number): { x: number; y: number } {\n const rect = this.getBoundingClientRect();\n return {\n x: rect.left + canvasX * this.scale + this.x,\n y: rect.top + canvasY * this.scale + this.y,\n };\n }\n\n /**\n * Reset the pan-zoom transform to its default values (x: 0, y: 0, scale: 1).\n * This method can be called programmatically to reset the view.\n *\n * @example\n * const panZoomRef = useRef(null);\n * <button onClick={() => panZoomRef.current.reset()}>Reset View</button>\n */\n reset(): void {\n this._updateTransform({ x: 0, y: 0, scale: 1 });\n }\n\n /**\n * Fit content to the container, centering it and scaling to fit.\n * Uses a padding factor to leave some margin around the content.\n *\n * @param padding - Padding factor (0-1), e.g., 0.1 = 10% padding on each side. Default: 0.05\n */\n fitToContent(padding = 0.05): void {\n const containerRect = this.getBoundingClientRect();\n if (containerRect.width === 0 || containerRect.height === 0) return;\n\n // Find the first child element to measure\n const contentWrapper = this.shadowRoot?.querySelector(\".content-wrapper\");\n const slottedContent = contentWrapper\n ?.querySelector(\"slot\")\n ?.assignedElements()[0] as HTMLElement | undefined;\n\n if (!slottedContent) return;\n\n // Get content dimensions\n const contentRect = slottedContent.getBoundingClientRect();\n const contentWidth = contentRect.width / this.scale;\n const contentHeight = contentRect.height / this.scale;\n\n if (contentWidth === 0 || contentHeight === 0) return;\n\n // Calculate available space with padding\n const availableWidth = containerRect.width * (1 - 2 * padding);\n const availableHeight = containerRect.height * (1 - 2 * padding);\n\n // Calculate scale to fit\n const scaleX = availableWidth / contentWidth;\n const scaleY = availableHeight / contentHeight;\n const newScale = Math.min(scaleX, scaleY, 1); // Don't scale up beyond 1:1\n\n // Calculate position to center\n const scaledWidth = contentWidth * newScale;\n const scaledHeight = contentHeight * newScale;\n const newX = (containerRect.width - scaledWidth) / 2;\n const newY = (containerRect.height - scaledHeight) / 2;\n\n this._updateTransform({\n x: newX,\n y: newY,\n scale: newScale,\n });\n }\n\n render() {\n return html`\n <div\n class=\"content-wrapper\"\n style=\"transform: translate(${this.x}px, ${this.y}px) scale(${this.scale});\"\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFPanZoom.js","names":["EFPanZoom","panZoom: Element | null"],"sources":["../../src/elements/EFPanZoom.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { provide } from \"@lit/context\";\nimport { panZoomTransformContext } from \"../gui/panZoomTransformContext.js\";\n\nexport interface PanZoomTransform {\n x: number;\n y: number;\n scale: number;\n}\n\n@customElement(\"ef-pan-zoom\")\nexport class EFPanZoom extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n overflow: hidden;\n position: relative;\n touch-action: none;\n }\n .content-wrapper {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n transform-origin: 0 0;\n }\n `,\n ];\n\n @property({ type: Number, reflect: true })\n x = 0;\n\n @property({ type: Number, reflect: true })\n y = 0;\n\n @property({ type: Number, reflect: true })\n scale = 1;\n\n /**\n * When true, automatically fits content to view on first render.\n * Centers content and scales it to fit within the container with padding.\n */\n @property({ type: Boolean, attribute: \"auto-fit\" })\n autoFit = false;\n\n @provide({ context: panZoomTransformContext })\n panZoomTransform: PanZoomTransform = { x: 0, y: 0, scale: 1 };\n\n private _isDragging = false;\n private _dragStartPointerPos: { x: number; y: number } | null = null;\n private _dragStartTransform: PanZoomTransform | null = null;\n private _capturedPointerId: number | null = null;\n\n /**\n * Document-level wheel handler in capture phase to prevent browser navigation.\n * This prevents back/forward navigation on two-finger swipe gestures.\n * We use capture phase to catch events before they bubble, but only prevent default\n * (not stop propagation) so the normal wheel handler can still process them.\n */\n private _onDocumentWheelCapture = (e: WheelEvent) => {\n // Check if event is over this panzoom element or its children\n let panZoom: Element | null = null;\n if (e.target instanceof Element) {\n panZoom = e.target.closest(\"ef-pan-zoom\");\n // Also check if target is an overlay sibling (selection overlay, etc.)\n // Overlays have pointer-events: none but can still be the event target\n if (!panZoom && e.target.closest(\"ef-canvas-selection-overlay\")) {\n // Event is over selection overlay - check if it's over this panzoom's area\n const rect = this.getBoundingClientRect();\n if (\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom\n ) {\n panZoom = this;\n }\n }\n }\n if (panZoom === this) {\n // Prevent browser navigation gestures (back/forward on swipe)\n // Don't stop propagation - let the normal wheel handler process the event\n e.preventDefault();\n }\n };\n\n connectedCallback() {\n super.connectedCallback();\n // Add document-level capture listener to prevent browser navigation\n document.addEventListener(\"wheel\", this._onDocumentWheelCapture, {\n passive: false,\n capture: true,\n });\n // Add element-level event listeners\n this.addEventListener(\"wheel\", this._onWheel, { passive: false });\n this.addEventListener(\"pointerdown\", this._onPointerDown);\n this.addEventListener(\"pointermove\", this._onPointerMove);\n this.addEventListener(\"pointerup\", this._onPointerUp);\n this.addEventListener(\"pointercancel\", this._onPointerUp);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n // Remove document-level capture listener\n document.removeEventListener(\"wheel\", this._onDocumentWheelCapture, {\n capture: true,\n });\n // Remove element-level event listeners\n this.removeEventListener(\"wheel\", this._onWheel);\n this.removeEventListener(\"pointerdown\", this._onPointerDown);\n this.removeEventListener(\"pointermove\", this._onPointerMove);\n this.removeEventListener(\"pointerup\", this._onPointerUp);\n this.removeEventListener(\"pointercancel\", this._onPointerUp);\n // Clean up pointer capture if dragging\n if (this._isDragging && this._capturedPointerId !== null) {\n try {\n this.releasePointerCapture(this._capturedPointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n }\n }\n\n private _updateTransform(updates: Partial<PanZoomTransform>) {\n const newTransform = {\n x: updates.x !== undefined ? updates.x : this.x,\n y: updates.y !== undefined ? updates.y : this.y,\n scale:\n updates.scale !== undefined\n ? Math.max(0.1, Math.min(5, updates.scale))\n : this.scale,\n };\n\n const changed =\n newTransform.x !== this.x ||\n newTransform.y !== this.y ||\n newTransform.scale !== this.scale;\n\n if (changed) {\n this.x = newTransform.x;\n this.y = newTransform.y;\n this.scale = newTransform.scale;\n\n // Update context for overlay components\n this.panZoomTransform = { ...newTransform };\n\n this.dispatchEvent(\n new CustomEvent<PanZoomTransform>(\"transform-changed\", {\n detail: { ...newTransform },\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n if (e.button !== 0) return;\n\n this._isDragging = true;\n this._capturedPointerId = e.pointerId;\n this._dragStartPointerPos = { x: e.clientX, y: e.clientY };\n this._dragStartTransform = {\n x: this.x,\n y: this.y,\n scale: this.scale,\n };\n\n try {\n this.setPointerCapture(e.pointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n };\n\n private _onPointerMove = (e: PointerEvent) => {\n if (\n !this._isDragging ||\n !this._dragStartPointerPos ||\n !this._dragStartTransform\n )\n return;\n\n const deltaX = e.clientX - this._dragStartPointerPos.x;\n const deltaY = e.clientY - this._dragStartPointerPos.y;\n\n this._updateTransform({\n x: this._dragStartTransform.x - deltaX,\n y: this._dragStartTransform.y - deltaY,\n });\n };\n\n private _onPointerUp = (e: PointerEvent) => {\n if (!this._isDragging) return;\n if (this._capturedPointerId !== null) {\n try {\n this.releasePointerCapture(e.pointerId);\n } catch (err) {\n // Ignore pointer capture errors (e.g., in test environments)\n }\n }\n\n this._isDragging = false;\n this._capturedPointerId = null;\n this._dragStartPointerPos = null;\n this._dragStartTransform = null;\n };\n\n private _onWheel = (e: WheelEvent) => {\n // Always prevent default to prevent browser navigation (back/forward on swipe)\n // This is critical for full-page app interfaces\n e.preventDefault();\n e.stopPropagation();\n\n const isZoom = e.metaKey || e.ctrlKey;\n\n if (isZoom) {\n const containerRect = this.getBoundingClientRect();\n const pointerX = e.clientX - containerRect.left;\n const pointerY = e.clientY - containerRect.top;\n\n const currentX = this.x;\n const currentY = this.y;\n const currentScale = this.scale;\n\n const canvasX = (pointerX - currentX) / currentScale;\n const canvasY = (pointerY - currentY) / currentScale;\n\n const delta = e.deltaY > 0 ? 0.95 : 1.05;\n const newScale = Math.max(0.1, Math.min(5, currentScale * delta));\n\n const newX = pointerX - canvasX * newScale;\n const newY = pointerY - canvasY * newScale;\n\n this._updateTransform({\n x: newX,\n y: newY,\n scale: newScale,\n });\n } else {\n const deltaX = -e.deltaX;\n const deltaY = -e.deltaY;\n\n this._updateTransform({\n x: this.x + deltaX,\n y: this.y + deltaY,\n });\n }\n };\n\n firstUpdated(changedProperties: Map<PropertyKey, unknown>) {\n super.firstUpdated(changedProperties);\n // Initialize context with current transform\n this.panZoomTransform = { x: this.x, y: this.y, scale: this.scale };\n\n // Auto-fit content if enabled (use RAF to ensure content is rendered)\n if (this.autoFit) {\n requestAnimationFrame(() => {\n this.fitToContent();\n });\n }\n }\n\n /**\n * Convert screen coordinates (e.g., mouse event clientX/clientY) to canvas coordinates.\n * This handles all pan/zoom transformations automatically.\n *\n * @param screenX - X coordinate in screen space (e.g., event.clientX)\n * @param screenY - Y coordinate in screen space (e.g., event.clientY)\n * @returns Object with x, y in canvas coordinate space\n *\n * @example\n * handleClick(e: MouseEvent) {\n * const canvasPos = panZoom.screenToCanvas(e.clientX, e.clientY);\n * console.log(`Clicked at canvas position: ${canvasPos.x}, ${canvasPos.y}`);\n * }\n */\n screenToCanvas(screenX: number, screenY: number): { x: number; y: number } {\n const rect = this.getBoundingClientRect();\n return {\n x: (screenX - rect.left - this.x) / this.scale,\n y: (screenY - rect.top - this.y) / this.scale,\n };\n }\n\n /**\n * Convert canvas coordinates to screen coordinates.\n * Useful for positioning overlays or tooltips relative to canvas elements.\n *\n * @param canvasX - X coordinate in canvas space\n * @param canvasY - Y coordinate in canvas space\n * @returns Object with x, y in screen coordinate space\n *\n * @example\n * const screenPos = panZoom.canvasToScreen(element.x, element.y);\n * tooltip.style.left = `${screenPos.x}px`;\n * tooltip.style.top = `${screenPos.y}px`;\n */\n canvasToScreen(canvasX: number, canvasY: number): { x: number; y: number } {\n const rect = this.getBoundingClientRect();\n return {\n x: rect.left + canvasX * this.scale + this.x,\n y: rect.top + canvasY * this.scale + this.y,\n };\n }\n\n /**\n * Reset the pan-zoom transform to its default values (x: 0, y: 0, scale: 1).\n * This method can be called programmatically to reset the view.\n *\n * @example\n * const panZoomRef = useRef(null);\n * <button onClick={() => panZoomRef.current.reset()}>Reset View</button>\n */\n reset(): void {\n this._updateTransform({ x: 0, y: 0, scale: 1 });\n }\n\n /**\n * Fit content to the container, centering it and scaling to fit.\n * Uses a padding factor to leave some margin around the content.\n *\n * @param padding - Padding factor (0-1), e.g., 0.1 = 10% padding on each side. Default: 0.05\n */\n fitToContent(padding = 0.05): void {\n const containerRect = this.getBoundingClientRect();\n if (containerRect.width === 0 || containerRect.height === 0) return;\n\n // Find the first child element to measure\n const contentWrapper = this.shadowRoot?.querySelector(\".content-wrapper\");\n const slottedContent = contentWrapper\n ?.querySelector(\"slot\")\n ?.assignedElements()[0] as HTMLElement | undefined;\n\n if (!slottedContent) return;\n\n // Get content dimensions\n const contentRect = slottedContent.getBoundingClientRect();\n const contentWidth = contentRect.width / this.scale;\n const contentHeight = contentRect.height / this.scale;\n\n if (contentWidth === 0 || contentHeight === 0) return;\n\n // Calculate available space with padding\n const availableWidth = containerRect.width * (1 - 2 * padding);\n const availableHeight = containerRect.height * (1 - 2 * padding);\n\n // Calculate scale to fit\n const scaleX = availableWidth / contentWidth;\n const scaleY = availableHeight / contentHeight;\n const newScale = Math.min(scaleX, scaleY);\n\n // Calculate position to center\n const scaledWidth = contentWidth * newScale;\n const scaledHeight = contentHeight * newScale;\n const newX = (containerRect.width - scaledWidth) / 2;\n const newY = (containerRect.height - scaledHeight) / 2;\n\n this._updateTransform({\n x: newX,\n y: newY,\n scale: newScale,\n });\n }\n\n render() {\n return html`\n <div\n class=\"content-wrapper\"\n style=\"transform: translate(${this.x}px, ${this.y}px) scale(${this.scale});\"\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -9,13 +9,10 @@ function EFSourceMixin(superClass, options) {
|
|
|
9
9
|
this.src = "";
|
|
10
10
|
this.md5SumLoader = new Proxy({
|
|
11
11
|
run: () => this.loadMd5Sum(),
|
|
12
|
-
|
|
12
|
+
host: this
|
|
13
13
|
}, { get(target, prop) {
|
|
14
|
-
if (prop === "value") return target.
|
|
15
|
-
if (prop === "taskComplete")
|
|
16
|
-
const host = target._host;
|
|
17
|
-
return host.#md5Promise || Promise.resolve(host.#md5Value);
|
|
18
|
-
}
|
|
14
|
+
if (prop === "value") return target.host._getMd5Value();
|
|
15
|
+
if (prop === "taskComplete") return target.host._getMd5Promise() || Promise.resolve(target.host._getMd5Value());
|
|
19
16
|
return target[prop];
|
|
20
17
|
} });
|
|
21
18
|
}
|
|
@@ -52,13 +49,24 @@ function EFSourceMixin(superClass, options) {
|
|
|
52
49
|
async #doLoadMd5(src, signal) {
|
|
53
50
|
let normalizedSrc = src.startsWith("/") ? src.slice(1) : src;
|
|
54
51
|
normalizedSrc = normalizedSrc.replace(/^\/+/, "");
|
|
55
|
-
const md5Path = `/api/v1/
|
|
52
|
+
const md5Path = `/api/v1/files/local/md5?src=${encodeURIComponent(normalizedSrc)}`;
|
|
56
53
|
const response = await fetch(md5Path, { signal });
|
|
57
54
|
if (!response.ok) return;
|
|
58
55
|
return (await response.json()).md5 ?? void 0;
|
|
59
56
|
}
|
|
57
|
+
/** @internal Exposes md5 state for md5SumLoader proxy without private field access in handler */
|
|
58
|
+
_getMd5Value() {
|
|
59
|
+
return this.#md5Value;
|
|
60
|
+
}
|
|
61
|
+
/** @internal Exposes md5 promise state for md5SumLoader proxy */
|
|
62
|
+
_getMd5Promise() {
|
|
63
|
+
return this.#md5Promise;
|
|
64
|
+
}
|
|
60
65
|
}
|
|
61
|
-
__decorate([property({
|
|
66
|
+
__decorate([property({
|
|
67
|
+
type: String,
|
|
68
|
+
reflect: true
|
|
69
|
+
})], EFSourceElement.prototype, "src", void 0);
|
|
62
70
|
return EFSourceElement;
|
|
63
71
|
}
|
|
64
72
|
|