@editframe/elements 0.37.3-beta → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +7 -10
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +8 -4
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +4 -4
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +3 -1
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.js +267 -145
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
- package/dist/preview/renderTimegroupToVideo.js +85 -105
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.js +1 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +21 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -40,7 +40,7 @@ interface MediaEngine {
|
|
|
40
40
|
id?: RenditionId;
|
|
41
41
|
trackId: number | undefined;
|
|
42
42
|
src: string;
|
|
43
|
-
}, signal
|
|
43
|
+
}, signal: AbortSignal) => Promise<ArrayBuffer>;
|
|
44
44
|
computeSegmentId: (desiredSeekTimeMs: number, rendition: MediaRendition) => number | undefined;
|
|
45
45
|
/**
|
|
46
46
|
* Get the video rendition if available, otherwise return undefined.
|
|
@@ -15,10 +15,17 @@ var UrlGenerator = class {
|
|
|
15
15
|
generateSegmentUrl(segmentId, renditionId, metadata) {
|
|
16
16
|
const audioRendition = metadata.audioRendition;
|
|
17
17
|
const videoRendition = metadata.videoRendition;
|
|
18
|
-
|
|
18
|
+
let rendition;
|
|
19
|
+
if (renditionId === "audio") rendition = audioRendition;
|
|
20
|
+
else rendition = videoRendition;
|
|
19
21
|
if (!rendition) {
|
|
20
|
-
console.error("Rendition not found",
|
|
21
|
-
|
|
22
|
+
console.error("Rendition not found", {
|
|
23
|
+
renditionId,
|
|
24
|
+
hasAudio: !!audioRendition,
|
|
25
|
+
hasVideo: !!videoRendition,
|
|
26
|
+
metadata
|
|
27
|
+
});
|
|
28
|
+
throw new Error(`Rendition ${renditionId} not found (hasAudio=${!!audioRendition}, hasVideo=${!!videoRendition})`);
|
|
22
29
|
}
|
|
23
30
|
return (segmentId === "init" ? metadata.templates.initSegment : metadata.templates.mediaSegment).replace("{rendition}", renditionId).replace("{segmentId}", segmentId.toString()).replace("{src}", metadata.src).replace("{trackId}", rendition.trackId?.toString() ?? "");
|
|
24
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UrlGenerator.js","names":["baseUrl: () => string"],"sources":["../../../src/transcoding/utils/UrlGenerator.ts"],"sourcesContent":["/**\n * URL generation utilities for transcoding endpoints\n */\n\nimport type { RenditionId } from \"../types/index.js\";\nimport type { MediaEngine } from \"../types/index.ts\";\n\nexport class UrlGenerator {\n constructor(private baseUrl: () => string) {}\n\n /**\n * Get the base URL for constructing absolute URLs\n */\n getBaseUrl(): string {\n return this.baseUrl();\n }\n\n /**\n * Generate video segment URL\n */\n generateSegmentUrl(\n segmentId: \"init\" | number,\n renditionId: RenditionId,\n metadata: MediaEngine,\n ): string {\n const audioRendition = metadata.audioRendition;\n const videoRendition = metadata.videoRendition;\n
|
|
1
|
+
{"version":3,"file":"UrlGenerator.js","names":["baseUrl: () => string"],"sources":["../../../src/transcoding/utils/UrlGenerator.ts"],"sourcesContent":["/**\n * URL generation utilities for transcoding endpoints\n */\n\nimport type { RenditionId } from \"../types/index.js\";\nimport type { MediaEngine } from \"../types/index.ts\";\n\nexport class UrlGenerator {\n constructor(private baseUrl: () => string) {}\n\n /**\n * Get the base URL for constructing absolute URLs\n */\n getBaseUrl(): string {\n return this.baseUrl();\n }\n\n /**\n * Generate video segment URL\n */\n generateSegmentUrl(\n segmentId: \"init\" | number,\n renditionId: RenditionId,\n metadata: MediaEngine,\n ): string {\n // Determine which rendition to use based on renditionId\n // Audio renditions: \"audio\"\n // Video renditions: \"high\", \"scrub\", \"low\", \"medium\", etc.\n const audioRendition = metadata.audioRendition;\n const videoRendition = metadata.videoRendition;\n\n let rendition;\n if (renditionId === \"audio\") {\n rendition = audioRendition;\n } else {\n // For all other rendition IDs (high, scrub, low, medium), use video rendition\n rendition = videoRendition;\n }\n\n if (!rendition) {\n console.error(\"Rendition not found\", {\n renditionId,\n hasAudio: !!audioRendition,\n hasVideo: !!videoRendition,\n metadata,\n });\n throw new Error(\n `Rendition ${renditionId} not found (hasAudio=${!!audioRendition}, hasVideo=${!!videoRendition})`,\n );\n }\n\n const template =\n segmentId === \"init\"\n ? metadata.templates.initSegment\n : metadata.templates.mediaSegment;\n return template\n .replace(\"{rendition}\", renditionId)\n .replace(\"{segmentId}\", segmentId.toString())\n .replace(\"{src}\", metadata.src)\n .replace(\"{trackId}\", rendition.trackId?.toString() ?? \"\");\n }\n\n /**\n * Generate init segment URL\n */\n generateInitSegmentUrl(mediaUrl: string, rendition: string): string {\n return `${this.baseUrl()}/api/v1/transcode/${rendition}/init.mp4?url=${encodeURIComponent(mediaUrl)}`;\n }\n\n /**\n * Generate manifest URL\n */\n generateManifestUrl(mediaUrl: string): string {\n return `${this.baseUrl()}/api/v1/transcode/manifest.json?url=${encodeURIComponent(mediaUrl)}`;\n }\n\n /**\n * Generate track fragment index URL using production API format\n * @deprecated Use MD5-based URL generation in AssetMediaEngine.fetch() instead\n */\n generateTrackFragmentIndexUrl(mediaUrl: string): string {\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = mediaUrl.startsWith(\"/\") ? mediaUrl.slice(1) : mediaUrl;\n // Remove any remaining leading slashes (handles cases like \"//assets/video.mp4\")\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Legacy format - kept for backward compatibility but should not be used\n return `@ef-track-fragment-index/${normalizedSrc}`;\n }\n\n /**\n * Generate quality presets URL\n */\n generatePresetsUrl(): string {\n return `${this.baseUrl()}/api/v1/transcode/presets`;\n }\n}\n"],"mappings":";AAOA,IAAa,eAAb,MAA0B;CACxB,YAAY,AAAQA,SAAuB;EAAvB;;;;;CAKpB,aAAqB;AACnB,SAAO,KAAK,SAAS;;;;;CAMvB,mBACE,WACA,aACA,UACQ;EAIR,MAAM,iBAAiB,SAAS;EAChC,MAAM,iBAAiB,SAAS;EAEhC,IAAI;AACJ,MAAI,gBAAgB,QAClB,aAAY;MAGZ,aAAY;AAGd,MAAI,CAAC,WAAW;AACd,WAAQ,MAAM,uBAAuB;IACnC;IACA,UAAU,CAAC,CAAC;IACZ,UAAU,CAAC,CAAC;IACZ;IACD,CAAC;AACF,SAAM,IAAI,MACR,aAAa,YAAY,uBAAuB,CAAC,CAAC,eAAe,aAAa,CAAC,CAAC,eAAe,GAChG;;AAOH,UAHE,cAAc,SACV,SAAS,UAAU,cACnB,SAAS,UAAU,cAEtB,QAAQ,eAAe,YAAY,CACnC,QAAQ,eAAe,UAAU,UAAU,CAAC,CAC5C,QAAQ,SAAS,SAAS,IAAI,CAC9B,QAAQ,aAAa,UAAU,SAAS,UAAU,IAAI,GAAG;;;;;CAM9D,uBAAuB,UAAkB,WAA2B;AAClE,SAAO,GAAG,KAAK,SAAS,CAAC,oBAAoB,UAAU,gBAAgB,mBAAmB,SAAS;;;;;CAMrG,oBAAoB,UAA0B;AAC5C,SAAO,GAAG,KAAK,SAAS,CAAC,sCAAsC,mBAAmB,SAAS;;;;;;CAO7F,8BAA8B,UAA0B;EAEtD,IAAI,gBAAgB,SAAS,WAAW,IAAI,GAAG,SAAS,MAAM,EAAE,GAAG;AAEnE,kBAAgB,cAAc,QAAQ,QAAQ,GAAG;AAEjD,SAAO,4BAA4B;;;;;CAMrC,qBAA6B;AAC3B,SAAO,GAAG,KAAK,SAAS,CAAC"}
|
package/dist/utils/LRUCache.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LRUCache.js","names":[],"sources":["../../src/utils/LRUCache.ts"],"sourcesContent":["/**\n * A simple LRU (Least Recently Used) cache implementation\n */\nexport class LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxSize) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n }\n }\n this.cache.set(key, value);\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n return this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n/**\n * Size-aware LRU cache that tracks memory usage in bytes\n * Evicts entries when total size exceeds the maximum\n */\nexport class SizeAwareLRUCache<K> {\n private cache = new Map<K, Promise<ArrayBuffer>>();\n private sizes = new Map<K, number>();\n private currentSize = 0;\n private readonly maxSizeBytes: number;\n\n constructor(maxSizeBytes: number) {\n this.maxSizeBytes = maxSizeBytes;\n }\n\n get(key: K): Promise<ArrayBuffer> | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n const size = this.sizes.get(key) || 0;\n this.cache.delete(key);\n this.cache.set(key, value);\n this.sizes.delete(key);\n this.sizes.set(key, size);\n }\n return value;\n }\n\n set(key: K, value: Promise<ArrayBuffer>): void {\n // If key already exists, remove it first\n if (this.cache.has(key)) {\n const oldSize = this.sizes.get(key) || 0;\n this.currentSize -= oldSize;\n this.cache.delete(key);\n this.sizes.delete(key);\n }\n\n // Track the size when the promise resolves\n const sizeTrackingPromise = value\n .then((buffer) => {\n const bufferSize = buffer.byteLength;\n this.sizes.set(key, bufferSize);\n this.currentSize += bufferSize;\n\n // Evict oldest entries if we exceed the size limit\n this.evictIfNecessary();\n\n return buffer;\n })\n .catch((error) => {\n // If the promise fails, clean up the entry\n this.cache.delete(key);\n this.sizes.delete(key);\n throw error;\n });\n\n this.cache.set(key, sizeTrackingPromise);\n }\n\n private evictIfNecessary(): void {\n while (this.currentSize > this.maxSizeBytes && this.cache.size > 0) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n const size = this.sizes.get(firstKey) || 0;\n this.currentSize -= size;\n this.cache.delete(firstKey);\n this.sizes.delete(firstKey);\n } else {\n break;\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n const size = this.sizes.get(key) || 0;\n this.currentSize -= size;\n this.sizes.delete(key);\n return this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n this.sizes.clear();\n this.currentSize = 0;\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n get currentSizeBytes(): number {\n return this.currentSize;\n }\n\n get maxSize(): number {\n return this.maxSizeBytes;\n }\n}\n\n/**\n * Red-Black Tree node colors\n */\nenum Color {\n RED = \"RED\",\n BLACK = \"BLACK\",\n}\n\n/**\n * Red-Black Tree node for ordered key storage\n */\nclass RBTreeNode<K> {\n constructor(\n public key: K,\n public color: Color = Color.RED,\n public left: RBTreeNode<K> | null = null,\n public right: RBTreeNode<K> | null = null,\n public parent: RBTreeNode<K> | null = null,\n ) {}\n}\n\n/**\n * Red-Black Tree implementation for O(log n) operations\n * Supports insert, delete, search, range queries, and nearest neighbor\n */\nclass RedBlackTree<K> {\n private root: RBTreeNode<K> | null = null;\n private readonly compareFn: (a: K, b: K) => number;\n\n constructor(compareFn: (a: K, b: K) => number) {\n this.compareFn = compareFn;\n }\n\n insert(key: K): void {\n const node = new RBTreeNode(key);\n\n if (!this.root) {\n this.root = node;\n node.color = Color.BLACK;\n return;\n }\n\n this.insertNode(node);\n this.fixInsert(node);\n }\n\n delete(key: K): boolean {\n const node = this.findNode(key);\n if (!node) return false;\n\n this.deleteNode(node);\n return true;\n }\n\n find(key: K): K | null {\n const node = this.findNode(key);\n return node ? node.key : null;\n }\n\n findNearestInRange(center: K, distance: K): K[] {\n // Calculate the range bounds\n const start = this.subtractDistance(center, distance);\n const end = this.addDistance(center, distance);\n\n // Use existing range search (O(log n + k))\n return this.findRange(start, end);\n }\n\n private subtractDistance(center: K, distance: K): K {\n if (typeof center === \"number\" && typeof distance === \"number\") {\n return (center - distance) as K;\n }\n\n // For strings, we can't easily subtract distance, so just return center\n // This means string searches will be exact matches only\n return center;\n }\n\n private addDistance(center: K, distance: K): K {\n if (typeof center === \"number\" && typeof distance === \"number\") {\n return (center + distance) as K;\n }\n\n // For strings, we can't easily add distance, so just return center\n // This means string searches will be exact matches only\n return center;\n }\n\n findRange(start: K, end: K): K[] {\n const result: K[] = [];\n this.inorderRange(this.root, start, end, result);\n return result;\n }\n\n getAllSorted(): K[] {\n const result: K[] = [];\n this.inorder(this.root, result);\n return result;\n }\n\n private findNode(key: K): RBTreeNode<K> | null {\n let current = this.root;\n\n while (current) {\n const cmp = this.compareFn(key, current.key);\n if (cmp === 0) return current;\n current = cmp < 0 ? current.left : current.right;\n }\n\n return null;\n }\n\n private insertNode(node: RBTreeNode<K>): void {\n let parent = null;\n let current = this.root;\n\n while (current) {\n parent = current;\n const cmp = this.compareFn(node.key, current.key);\n current = cmp < 0 ? current.left : current.right;\n }\n\n node.parent = parent;\n if (!parent) {\n this.root = node;\n } else {\n const cmp = this.compareFn(node.key, parent.key);\n if (cmp < 0) {\n parent.left = node;\n } else {\n parent.right = node;\n }\n }\n }\n\n private fixInsert(node: RBTreeNode<K>): void {\n while (node.parent && node.parent.color === Color.RED) {\n if (node.parent === node.parent.parent?.left) {\n const uncle = node.parent.parent.right;\n\n if (uncle?.color === Color.RED) {\n node.parent.color = Color.BLACK;\n uncle.color = Color.BLACK;\n node.parent.parent.color = Color.RED;\n node = node.parent.parent;\n } else {\n if (node === node.parent.right) {\n node = node.parent;\n this.rotateLeft(node);\n }\n\n if (node.parent) {\n node.parent.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n this.rotateRight(node.parent.parent);\n }\n }\n }\n } else {\n const uncle = node.parent.parent?.left;\n\n if (uncle?.color === Color.RED) {\n node.parent.color = Color.BLACK;\n uncle.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n node = node.parent.parent;\n }\n } else {\n if (node === node.parent.left) {\n node = node.parent;\n this.rotateRight(node);\n }\n\n if (node.parent) {\n node.parent.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n this.rotateLeft(node.parent.parent);\n }\n }\n }\n }\n }\n\n if (this.root) {\n this.root.color = Color.BLACK;\n }\n }\n\n private deleteNode(node: RBTreeNode<K>): void {\n let y = node;\n let yOriginalColor = y.color;\n let x: RBTreeNode<K> | null;\n\n if (!node.left) {\n x = node.right;\n this.transplant(node, node.right);\n } else if (!node.right) {\n x = node.left;\n this.transplant(node, node.left);\n } else {\n y = this.minimum(node.right);\n yOriginalColor = y.color;\n x = y.right;\n\n if (y.parent === node) {\n if (x) x.parent = y;\n } else {\n this.transplant(y, y.right);\n y.right = node.right;\n if (y.right) y.right.parent = y;\n }\n\n this.transplant(node, y);\n y.left = node.left;\n if (y.left) y.left.parent = y;\n y.color = node.color;\n }\n\n if (yOriginalColor === Color.BLACK && x) {\n this.fixDelete(x);\n }\n }\n\n private fixDelete(node: RBTreeNode<K>): void {\n while (node !== this.root && node.color === Color.BLACK) {\n if (node === node.parent?.left) {\n let sibling = node.parent.right;\n\n if (sibling?.color === Color.RED) {\n sibling.color = Color.BLACK;\n node.parent.color = Color.RED;\n this.rotateLeft(node.parent);\n sibling = node.parent.right;\n }\n\n if (\n sibling?.left?.color !== Color.RED &&\n sibling?.right?.color !== Color.RED\n ) {\n if (sibling) {\n sibling.color = Color.RED;\n }\n node = node.parent;\n } else {\n if (sibling?.right?.color !== Color.RED) {\n if (sibling.left) sibling.left.color = Color.BLACK;\n sibling.color = Color.RED;\n this.rotateRight(sibling);\n sibling = node.parent.right;\n }\n\n if (sibling) {\n sibling.color = node.parent.color;\n node.parent.color = Color.BLACK;\n if (sibling.right) sibling.right.color = Color.BLACK;\n this.rotateLeft(node.parent);\n }\n if (!this.root) {\n throw new Error(\"Root is null\");\n }\n node = this.root;\n }\n } else {\n let sibling = node.parent?.left;\n\n if (sibling?.color === Color.RED) {\n sibling.color = Color.BLACK;\n if (node.parent) node.parent.color = Color.RED;\n if (node.parent) this.rotateRight(node.parent);\n sibling = node.parent?.left;\n }\n\n if (\n sibling?.right?.color !== Color.RED &&\n sibling?.left?.color !== Color.RED\n ) {\n if (sibling) {\n sibling.color = Color.RED;\n }\n if (node.parent === null) {\n throw new Error(\"Node parent is null\");\n }\n node = node.parent;\n } else {\n if (sibling?.left?.color !== Color.RED) {\n if (sibling.right) sibling.right.color = Color.BLACK;\n sibling.color = Color.RED;\n this.rotateLeft(sibling);\n sibling = node.parent?.left;\n }\n\n if (sibling) {\n sibling.color = node.parent?.color || Color.BLACK;\n if (node.parent) node.parent.color = Color.BLACK;\n if (sibling.left) sibling.left.color = Color.BLACK;\n if (node.parent) this.rotateRight(node.parent);\n }\n if (!this.root) {\n throw new Error(\"Root is null\");\n }\n node = this.root;\n }\n }\n }\n\n node.color = Color.BLACK;\n }\n\n private rotateLeft(node: RBTreeNode<K>): void {\n const rightChild = node.right;\n if (!rightChild) {\n throw new Error(\"Right child is null\");\n }\n node.right = rightChild.left;\n\n if (rightChild.left) {\n rightChild.left.parent = node;\n }\n\n rightChild.parent = node.parent;\n\n if (!node.parent) {\n this.root = rightChild;\n } else if (node === node.parent.left) {\n node.parent.left = rightChild;\n } else {\n node.parent.right = rightChild;\n }\n\n rightChild.left = node;\n node.parent = rightChild;\n }\n\n private rotateRight(node: RBTreeNode<K>): void {\n const leftChild = node.left;\n if (!leftChild) {\n throw new Error(\"Left child is null\");\n }\n node.left = leftChild.right;\n\n if (leftChild.right) {\n leftChild.right.parent = node;\n }\n\n leftChild.parent = node.parent;\n\n if (!node.parent) {\n this.root = leftChild;\n } else if (node === node.parent.right) {\n node.parent.right = leftChild;\n } else {\n node.parent.left = leftChild;\n }\n\n leftChild.right = node;\n node.parent = leftChild;\n }\n\n private transplant(u: RBTreeNode<K>, v: RBTreeNode<K> | null): void {\n if (!u.parent) {\n this.root = v;\n } else if (u === u.parent.left) {\n u.parent.left = v;\n } else {\n u.parent.right = v;\n }\n\n if (v) {\n v.parent = u.parent;\n }\n }\n\n private minimum(node: RBTreeNode<K>): RBTreeNode<K> {\n while (node.left) {\n node = node.left;\n }\n return node;\n }\n\n private inorder(node: RBTreeNode<K> | null, result: K[]): void {\n if (node) {\n this.inorder(node.left, result);\n result.push(node.key);\n this.inorder(node.right, result);\n }\n }\n\n private inorderRange(\n node: RBTreeNode<K> | null,\n start: K,\n end: K,\n result: K[],\n ): void {\n if (!node) return;\n\n const startCmp = this.compareFn(node.key, start);\n const endCmp = this.compareFn(node.key, end);\n\n if (startCmp > 0) {\n this.inorderRange(node.left, start, end, result);\n }\n\n if (startCmp >= 0 && endCmp <= 0) {\n result.push(node.key);\n }\n\n if (endCmp < 0) {\n this.inorderRange(node.right, start, end, result);\n }\n }\n}\n\n/**\n * LRU cache with binary search capabilities using Red-Black tree\n * All operations are O(log n) for ordered queries and O(1) for LRU operations\n */\nexport class OrderedLRUCache<K extends number | string, V> {\n private cache = new Map<K, V>();\n private tree: RedBlackTree<K>;\n private readonly maxSize: number;\n private readonly compareFn: (a: K, b: K) => number;\n\n constructor(maxSize: number, compareFn?: (a: K, b: K) => number) {\n this.maxSize = maxSize;\n this.compareFn = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n this.tree = new RedBlackTree(this.compareFn);\n }\n\n /**\n * Get value by exact key (O(1))\n */\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n /**\n * Set key-value pair (O(log n) for tree operations, O(1) for cache)\n */\n set(key: K, value: V): void {\n const isUpdate = this.cache.has(key);\n\n if (isUpdate) {\n this.cache.delete(key);\n } else {\n if (this.cache.size >= this.maxSize) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n this.tree.delete(firstKey);\n }\n }\n // Add to tree index for new keys\n this.tree.insert(key);\n }\n\n this.cache.set(key, value);\n }\n\n /**\n * Find exact key using tree search (O(log n))\n */\n findExact(key: K): V | undefined {\n const foundKey = this.tree.find(key);\n if (foundKey !== null) {\n return this.get(key);\n }\n return undefined;\n }\n\n /**\n * Find keys within distance of center point (O(log n + k) where k is result count)\n * Returns empty array if no keys found in range\n */\n findNearestInRange(center: K, distance: K): Array<{ key: K; value: V }> {\n const nearestKeys = this.tree.findNearestInRange(center, distance);\n const result: Array<{ key: K; value: V }> = [];\n\n for (const key of nearestKeys) {\n const value = this.get(key);\n if (value !== undefined) {\n result.push({ key, value });\n }\n }\n\n return result;\n }\n\n /**\n * Find all key-value pairs in range [start, end] (O(log n + k) where k is result count)\n */\n findRange(start: K, end: K): Array<{ key: K; value: V }> {\n const keys = this.tree.findRange(start, end);\n const result: Array<{ key: K; value: V }> = [];\n\n for (const key of keys) {\n const value = this.get(key);\n if (value !== undefined) {\n result.push({ key, value });\n }\n }\n\n return result;\n }\n\n /**\n * Get all keys in sorted order (O(n))\n */\n getSortedKeys(): ReadonlyArray<K> {\n return this.tree.getAllSorted();\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n const deleted = this.cache.delete(key);\n if (deleted) {\n this.tree.delete(key);\n }\n return deleted;\n }\n\n clear(): void {\n this.cache.clear();\n this.tree = new RedBlackTree(this.compareFn);\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n"],"mappings":";;;;AAGA,IAAa,WAAb,MAA4B;CAI1B,YAAY,SAAiB;+BAHb,IAAI,KAAW;AAI7B,OAAK,UAAU;;CAGjB,IAAI,KAAuB;EACzB,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAI;AACjC,MAAI,OAAO;AAET,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,MAAM;;AAE5B,SAAO;;CAGT,IAAI,KAAQ,OAAgB;AAC1B,MAAI,KAAK,MAAM,IAAI,IAAI,CACrB,MAAK,MAAM,OAAO,IAAI;WACb,KAAK,MAAM,QAAQ,KAAK,SAAS;GAE1C,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,SACF,MAAK,MAAM,OAAO,SAAS;;AAG/B,OAAK,MAAM,IAAI,KAAK,MAAM;;CAG5B,IAAI,KAAiB;AACnB,SAAO,KAAK,MAAM,IAAI,IAAI;;CAG5B,OAAO,KAAiB;AACtB,SAAO,KAAK,MAAM,OAAO,IAAI;;CAG/B,QAAc;AACZ,OAAK,MAAM,OAAO;;CAGpB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;;;;;;AAQtB,IAAa,oBAAb,MAAkC;CAMhC,YAAY,cAAsB;+BALlB,IAAI,KAA8B;+BAClC,IAAI,KAAgB;qBACd;AAIpB,OAAK,eAAe;;CAGtB,IAAI,KAA0C;EAC5C,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAI;AACjC,MAAI,OAAO;GAET,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AACpC,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,MAAM;AAC1B,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,KAAK;;AAE3B,SAAO;;CAGT,IAAI,KAAQ,OAAmC;AAE7C,MAAI,KAAK,MAAM,IAAI,IAAI,EAAE;GACvB,MAAM,UAAU,KAAK,MAAM,IAAI,IAAI,IAAI;AACvC,QAAK,eAAe;AACpB,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,OAAO,IAAI;;EAIxB,MAAM,sBAAsB,MACzB,MAAM,WAAW;GAChB,MAAM,aAAa,OAAO;AAC1B,QAAK,MAAM,IAAI,KAAK,WAAW;AAC/B,QAAK,eAAe;AAGpB,QAAK,kBAAkB;AAEvB,UAAO;IACP,CACD,OAAO,UAAU;AAEhB,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,OAAO,IAAI;AACtB,SAAM;IACN;AAEJ,OAAK,MAAM,IAAI,KAAK,oBAAoB;;CAG1C,AAAQ,mBAAyB;AAC/B,SAAO,KAAK,cAAc,KAAK,gBAAgB,KAAK,MAAM,OAAO,GAAG;GAElE,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,UAAU;IACZ,MAAM,OAAO,KAAK,MAAM,IAAI,SAAS,IAAI;AACzC,SAAK,eAAe;AACpB,SAAK,MAAM,OAAO,SAAS;AAC3B,SAAK,MAAM,OAAO,SAAS;SAE3B;;;CAKN,IAAI,KAAiB;AACnB,SAAO,KAAK,MAAM,IAAI,IAAI;;CAG5B,OAAO,KAAiB;EACtB,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AACpC,OAAK,eAAe;AACpB,OAAK,MAAM,OAAO,IAAI;AACtB,SAAO,KAAK,MAAM,OAAO,IAAI;;CAG/B,QAAc;AACZ,OAAK,MAAM,OAAO;AAClB,OAAK,MAAM,OAAO;AAClB,OAAK,cAAc;;CAGrB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;CAGpB,IAAI,mBAA2B;AAC7B,SAAO,KAAK;;CAGd,IAAI,UAAkB;AACpB,SAAO,KAAK"}
|
|
1
|
+
{"version":3,"file":"LRUCache.js","names":[],"sources":["../../src/utils/LRUCache.ts"],"sourcesContent":["/**\n * A simple LRU (Least Recently Used) cache implementation\n */\nexport class LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxSize) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n }\n }\n this.cache.set(key, value);\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n return this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n/**\n * Size-aware LRU cache that tracks memory usage in bytes\n * Evicts entries when total size exceeds the maximum\n */\nexport class SizeAwareLRUCache<K> {\n private cache = new Map<K, Promise<ArrayBuffer>>();\n private sizes = new Map<K, number>();\n private currentSize = 0;\n private readonly maxSizeBytes: number;\n\n constructor(maxSizeBytes: number) {\n this.maxSizeBytes = maxSizeBytes;\n }\n\n get(key: K): Promise<ArrayBuffer> | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n const size = this.sizes.get(key) || 0;\n this.cache.delete(key);\n this.cache.set(key, value);\n this.sizes.delete(key);\n this.sizes.set(key, size);\n }\n return value;\n }\n\n set(key: K, value: Promise<ArrayBuffer>): void {\n // If key already exists, remove it first\n if (this.cache.has(key)) {\n const oldSize = this.sizes.get(key) || 0;\n this.currentSize -= oldSize;\n this.cache.delete(key);\n this.sizes.delete(key);\n }\n\n // Track the size when the promise resolves\n const sizeTrackingPromise = value\n .then((buffer) => {\n const bufferSize = buffer.byteLength;\n this.sizes.set(key, bufferSize);\n this.currentSize += bufferSize;\n\n // Evict oldest entries if we exceed the size limit\n this.evictIfNecessary();\n\n return buffer;\n })\n .catch((error) => {\n // If the promise fails, clean up the entry\n this.cache.delete(key);\n this.sizes.delete(key);\n throw error;\n });\n\n // Suppress unhandled rejection on the derived promise. This promise sits in\n // the cache and may reject before any caller retrieves and awaits it.\n // Zone.js checks for handlers synchronously at rejection time — without this,\n // the re-thrown error triggers an unhandledrejection event. Callers who later\n // await the cached promise still see the rejection (this just adds a no-op branch).\n sizeTrackingPromise.catch(() => {});\n\n this.cache.set(key, sizeTrackingPromise);\n }\n\n private evictIfNecessary(): void {\n while (this.currentSize > this.maxSizeBytes && this.cache.size > 0) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n const size = this.sizes.get(firstKey) || 0;\n this.currentSize -= size;\n this.cache.delete(firstKey);\n this.sizes.delete(firstKey);\n } else {\n break;\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n const size = this.sizes.get(key) || 0;\n this.currentSize -= size;\n this.sizes.delete(key);\n return this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n this.sizes.clear();\n this.currentSize = 0;\n }\n\n get size(): number {\n return this.cache.size;\n }\n\n get currentSizeBytes(): number {\n return this.currentSize;\n }\n\n get maxSize(): number {\n return this.maxSizeBytes;\n }\n}\n\n/**\n * Red-Black Tree node colors\n */\nenum Color {\n RED = \"RED\",\n BLACK = \"BLACK\",\n}\n\n/**\n * Red-Black Tree node for ordered key storage\n */\nclass RBTreeNode<K> {\n constructor(\n public key: K,\n public color: Color = Color.RED,\n public left: RBTreeNode<K> | null = null,\n public right: RBTreeNode<K> | null = null,\n public parent: RBTreeNode<K> | null = null,\n ) {}\n}\n\n/**\n * Red-Black Tree implementation for O(log n) operations\n * Supports insert, delete, search, range queries, and nearest neighbor\n */\nclass RedBlackTree<K> {\n private root: RBTreeNode<K> | null = null;\n private readonly compareFn: (a: K, b: K) => number;\n\n constructor(compareFn: (a: K, b: K) => number) {\n this.compareFn = compareFn;\n }\n\n insert(key: K): void {\n const node = new RBTreeNode(key);\n\n if (!this.root) {\n this.root = node;\n node.color = Color.BLACK;\n return;\n }\n\n this.insertNode(node);\n this.fixInsert(node);\n }\n\n delete(key: K): boolean {\n const node = this.findNode(key);\n if (!node) return false;\n\n this.deleteNode(node);\n return true;\n }\n\n find(key: K): K | null {\n const node = this.findNode(key);\n return node ? node.key : null;\n }\n\n findNearestInRange(center: K, distance: K): K[] {\n // Calculate the range bounds\n const start = this.subtractDistance(center, distance);\n const end = this.addDistance(center, distance);\n\n // Use existing range search (O(log n + k))\n return this.findRange(start, end);\n }\n\n private subtractDistance(center: K, distance: K): K {\n if (typeof center === \"number\" && typeof distance === \"number\") {\n return (center - distance) as K;\n }\n\n // For strings, we can't easily subtract distance, so just return center\n // This means string searches will be exact matches only\n return center;\n }\n\n private addDistance(center: K, distance: K): K {\n if (typeof center === \"number\" && typeof distance === \"number\") {\n return (center + distance) as K;\n }\n\n // For strings, we can't easily add distance, so just return center\n // This means string searches will be exact matches only\n return center;\n }\n\n findRange(start: K, end: K): K[] {\n const result: K[] = [];\n this.inorderRange(this.root, start, end, result);\n return result;\n }\n\n getAllSorted(): K[] {\n const result: K[] = [];\n this.inorder(this.root, result);\n return result;\n }\n\n private findNode(key: K): RBTreeNode<K> | null {\n let current = this.root;\n\n while (current) {\n const cmp = this.compareFn(key, current.key);\n if (cmp === 0) return current;\n current = cmp < 0 ? current.left : current.right;\n }\n\n return null;\n }\n\n private insertNode(node: RBTreeNode<K>): void {\n let parent = null;\n let current = this.root;\n\n while (current) {\n parent = current;\n const cmp = this.compareFn(node.key, current.key);\n current = cmp < 0 ? current.left : current.right;\n }\n\n node.parent = parent;\n if (!parent) {\n this.root = node;\n } else {\n const cmp = this.compareFn(node.key, parent.key);\n if (cmp < 0) {\n parent.left = node;\n } else {\n parent.right = node;\n }\n }\n }\n\n private fixInsert(node: RBTreeNode<K>): void {\n while (node.parent && node.parent.color === Color.RED) {\n if (node.parent === node.parent.parent?.left) {\n const uncle = node.parent.parent.right;\n\n if (uncle?.color === Color.RED) {\n node.parent.color = Color.BLACK;\n uncle.color = Color.BLACK;\n node.parent.parent.color = Color.RED;\n node = node.parent.parent;\n } else {\n if (node === node.parent.right) {\n node = node.parent;\n this.rotateLeft(node);\n }\n\n if (node.parent) {\n node.parent.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n this.rotateRight(node.parent.parent);\n }\n }\n }\n } else {\n const uncle = node.parent.parent?.left;\n\n if (uncle?.color === Color.RED) {\n node.parent.color = Color.BLACK;\n uncle.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n node = node.parent.parent;\n }\n } else {\n if (node === node.parent.left) {\n node = node.parent;\n this.rotateRight(node);\n }\n\n if (node.parent) {\n node.parent.color = Color.BLACK;\n if (node.parent.parent) {\n node.parent.parent.color = Color.RED;\n this.rotateLeft(node.parent.parent);\n }\n }\n }\n }\n }\n\n if (this.root) {\n this.root.color = Color.BLACK;\n }\n }\n\n private deleteNode(node: RBTreeNode<K>): void {\n let y = node;\n let yOriginalColor = y.color;\n let x: RBTreeNode<K> | null;\n\n if (!node.left) {\n x = node.right;\n this.transplant(node, node.right);\n } else if (!node.right) {\n x = node.left;\n this.transplant(node, node.left);\n } else {\n y = this.minimum(node.right);\n yOriginalColor = y.color;\n x = y.right;\n\n if (y.parent === node) {\n if (x) x.parent = y;\n } else {\n this.transplant(y, y.right);\n y.right = node.right;\n if (y.right) y.right.parent = y;\n }\n\n this.transplant(node, y);\n y.left = node.left;\n if (y.left) y.left.parent = y;\n y.color = node.color;\n }\n\n if (yOriginalColor === Color.BLACK && x) {\n this.fixDelete(x);\n }\n }\n\n private fixDelete(node: RBTreeNode<K>): void {\n while (node !== this.root && node.color === Color.BLACK) {\n if (node === node.parent?.left) {\n let sibling = node.parent.right;\n\n if (sibling?.color === Color.RED) {\n sibling.color = Color.BLACK;\n node.parent.color = Color.RED;\n this.rotateLeft(node.parent);\n sibling = node.parent.right;\n }\n\n if (\n sibling?.left?.color !== Color.RED &&\n sibling?.right?.color !== Color.RED\n ) {\n if (sibling) {\n sibling.color = Color.RED;\n }\n node = node.parent;\n } else {\n if (sibling?.right?.color !== Color.RED) {\n if (sibling.left) sibling.left.color = Color.BLACK;\n sibling.color = Color.RED;\n this.rotateRight(sibling);\n sibling = node.parent.right;\n }\n\n if (sibling) {\n sibling.color = node.parent.color;\n node.parent.color = Color.BLACK;\n if (sibling.right) sibling.right.color = Color.BLACK;\n this.rotateLeft(node.parent);\n }\n if (!this.root) {\n throw new Error(\"Root is null\");\n }\n node = this.root;\n }\n } else {\n let sibling = node.parent?.left;\n\n if (sibling?.color === Color.RED) {\n sibling.color = Color.BLACK;\n if (node.parent) node.parent.color = Color.RED;\n if (node.parent) this.rotateRight(node.parent);\n sibling = node.parent?.left;\n }\n\n if (\n sibling?.right?.color !== Color.RED &&\n sibling?.left?.color !== Color.RED\n ) {\n if (sibling) {\n sibling.color = Color.RED;\n }\n if (node.parent === null) {\n throw new Error(\"Node parent is null\");\n }\n node = node.parent;\n } else {\n if (sibling?.left?.color !== Color.RED) {\n if (sibling.right) sibling.right.color = Color.BLACK;\n sibling.color = Color.RED;\n this.rotateLeft(sibling);\n sibling = node.parent?.left;\n }\n\n if (sibling) {\n sibling.color = node.parent?.color || Color.BLACK;\n if (node.parent) node.parent.color = Color.BLACK;\n if (sibling.left) sibling.left.color = Color.BLACK;\n if (node.parent) this.rotateRight(node.parent);\n }\n if (!this.root) {\n throw new Error(\"Root is null\");\n }\n node = this.root;\n }\n }\n }\n\n node.color = Color.BLACK;\n }\n\n private rotateLeft(node: RBTreeNode<K>): void {\n const rightChild = node.right;\n if (!rightChild) {\n throw new Error(\"Right child is null\");\n }\n node.right = rightChild.left;\n\n if (rightChild.left) {\n rightChild.left.parent = node;\n }\n\n rightChild.parent = node.parent;\n\n if (!node.parent) {\n this.root = rightChild;\n } else if (node === node.parent.left) {\n node.parent.left = rightChild;\n } else {\n node.parent.right = rightChild;\n }\n\n rightChild.left = node;\n node.parent = rightChild;\n }\n\n private rotateRight(node: RBTreeNode<K>): void {\n const leftChild = node.left;\n if (!leftChild) {\n throw new Error(\"Left child is null\");\n }\n node.left = leftChild.right;\n\n if (leftChild.right) {\n leftChild.right.parent = node;\n }\n\n leftChild.parent = node.parent;\n\n if (!node.parent) {\n this.root = leftChild;\n } else if (node === node.parent.right) {\n node.parent.right = leftChild;\n } else {\n node.parent.left = leftChild;\n }\n\n leftChild.right = node;\n node.parent = leftChild;\n }\n\n private transplant(u: RBTreeNode<K>, v: RBTreeNode<K> | null): void {\n if (!u.parent) {\n this.root = v;\n } else if (u === u.parent.left) {\n u.parent.left = v;\n } else {\n u.parent.right = v;\n }\n\n if (v) {\n v.parent = u.parent;\n }\n }\n\n private minimum(node: RBTreeNode<K>): RBTreeNode<K> {\n while (node.left) {\n node = node.left;\n }\n return node;\n }\n\n private inorder(node: RBTreeNode<K> | null, result: K[]): void {\n if (node) {\n this.inorder(node.left, result);\n result.push(node.key);\n this.inorder(node.right, result);\n }\n }\n\n private inorderRange(\n node: RBTreeNode<K> | null,\n start: K,\n end: K,\n result: K[],\n ): void {\n if (!node) return;\n\n const startCmp = this.compareFn(node.key, start);\n const endCmp = this.compareFn(node.key, end);\n\n if (startCmp > 0) {\n this.inorderRange(node.left, start, end, result);\n }\n\n if (startCmp >= 0 && endCmp <= 0) {\n result.push(node.key);\n }\n\n if (endCmp < 0) {\n this.inorderRange(node.right, start, end, result);\n }\n }\n}\n\n/**\n * LRU cache with binary search capabilities using Red-Black tree\n * All operations are O(log n) for ordered queries and O(1) for LRU operations\n */\nexport class OrderedLRUCache<K extends number | string, V> {\n private cache = new Map<K, V>();\n private tree: RedBlackTree<K>;\n private readonly maxSize: number;\n private readonly compareFn: (a: K, b: K) => number;\n\n constructor(maxSize: number, compareFn?: (a: K, b: K) => number) {\n this.maxSize = maxSize;\n this.compareFn = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n this.tree = new RedBlackTree(this.compareFn);\n }\n\n /**\n * Get value by exact key (O(1))\n */\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value) {\n // Refresh position by removing and re-adding\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n /**\n * Set key-value pair (O(log n) for tree operations, O(1) for cache)\n */\n set(key: K, value: V): void {\n const isUpdate = this.cache.has(key);\n\n if (isUpdate) {\n this.cache.delete(key);\n } else {\n if (this.cache.size >= this.maxSize) {\n // Remove oldest entry (first item in map)\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n this.tree.delete(firstKey);\n }\n }\n // Add to tree index for new keys\n this.tree.insert(key);\n }\n\n this.cache.set(key, value);\n }\n\n /**\n * Find exact key using tree search (O(log n))\n */\n findExact(key: K): V | undefined {\n const foundKey = this.tree.find(key);\n if (foundKey !== null) {\n return this.get(key);\n }\n return undefined;\n }\n\n /**\n * Find keys within distance of center point (O(log n + k) where k is result count)\n * Returns empty array if no keys found in range\n */\n findNearestInRange(center: K, distance: K): Array<{ key: K; value: V }> {\n const nearestKeys = this.tree.findNearestInRange(center, distance);\n const result: Array<{ key: K; value: V }> = [];\n\n for (const key of nearestKeys) {\n const value = this.get(key);\n if (value !== undefined) {\n result.push({ key, value });\n }\n }\n\n return result;\n }\n\n /**\n * Find all key-value pairs in range [start, end] (O(log n + k) where k is result count)\n */\n findRange(start: K, end: K): Array<{ key: K; value: V }> {\n const keys = this.tree.findRange(start, end);\n const result: Array<{ key: K; value: V }> = [];\n\n for (const key of keys) {\n const value = this.get(key);\n if (value !== undefined) {\n result.push({ key, value });\n }\n }\n\n return result;\n }\n\n /**\n * Get all keys in sorted order (O(n))\n */\n getSortedKeys(): ReadonlyArray<K> {\n return this.tree.getAllSorted();\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n delete(key: K): boolean {\n const deleted = this.cache.delete(key);\n if (deleted) {\n this.tree.delete(key);\n }\n return deleted;\n }\n\n clear(): void {\n this.cache.clear();\n this.tree = new RedBlackTree(this.compareFn);\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n"],"mappings":";;;;AAGA,IAAa,WAAb,MAA4B;CAI1B,YAAY,SAAiB;+BAHb,IAAI,KAAW;AAI7B,OAAK,UAAU;;CAGjB,IAAI,KAAuB;EACzB,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAI;AACjC,MAAI,OAAO;AAET,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,MAAM;;AAE5B,SAAO;;CAGT,IAAI,KAAQ,OAAgB;AAC1B,MAAI,KAAK,MAAM,IAAI,IAAI,CACrB,MAAK,MAAM,OAAO,IAAI;WACb,KAAK,MAAM,QAAQ,KAAK,SAAS;GAE1C,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,SACF,MAAK,MAAM,OAAO,SAAS;;AAG/B,OAAK,MAAM,IAAI,KAAK,MAAM;;CAG5B,IAAI,KAAiB;AACnB,SAAO,KAAK,MAAM,IAAI,IAAI;;CAG5B,OAAO,KAAiB;AACtB,SAAO,KAAK,MAAM,OAAO,IAAI;;CAG/B,QAAc;AACZ,OAAK,MAAM,OAAO;;CAGpB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;;;;;;AAQtB,IAAa,oBAAb,MAAkC;CAMhC,YAAY,cAAsB;+BALlB,IAAI,KAA8B;+BAClC,IAAI,KAAgB;qBACd;AAIpB,OAAK,eAAe;;CAGtB,IAAI,KAA0C;EAC5C,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAI;AACjC,MAAI,OAAO;GAET,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AACpC,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,MAAM;AAC1B,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,IAAI,KAAK,KAAK;;AAE3B,SAAO;;CAGT,IAAI,KAAQ,OAAmC;AAE7C,MAAI,KAAK,MAAM,IAAI,IAAI,EAAE;GACvB,MAAM,UAAU,KAAK,MAAM,IAAI,IAAI,IAAI;AACvC,QAAK,eAAe;AACpB,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,OAAO,IAAI;;EAIxB,MAAM,sBAAsB,MACzB,MAAM,WAAW;GAChB,MAAM,aAAa,OAAO;AAC1B,QAAK,MAAM,IAAI,KAAK,WAAW;AAC/B,QAAK,eAAe;AAGpB,QAAK,kBAAkB;AAEvB,UAAO;IACP,CACD,OAAO,UAAU;AAEhB,QAAK,MAAM,OAAO,IAAI;AACtB,QAAK,MAAM,OAAO,IAAI;AACtB,SAAM;IACN;AAOJ,sBAAoB,YAAY,GAAG;AAEnC,OAAK,MAAM,IAAI,KAAK,oBAAoB;;CAG1C,AAAQ,mBAAyB;AAC/B,SAAO,KAAK,cAAc,KAAK,gBAAgB,KAAK,MAAM,OAAO,GAAG;GAElE,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,UAAU;IACZ,MAAM,OAAO,KAAK,MAAM,IAAI,SAAS,IAAI;AACzC,SAAK,eAAe;AACpB,SAAK,MAAM,OAAO,SAAS;AAC3B,SAAK,MAAM,OAAO,SAAS;SAE3B;;;CAKN,IAAI,KAAiB;AACnB,SAAO,KAAK,MAAM,IAAI,IAAI;;CAG5B,OAAO,KAAiB;EACtB,MAAM,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AACpC,OAAK,eAAe;AACpB,OAAK,MAAM,OAAO,IAAI;AACtB,SAAO,KAAK,MAAM,OAAO,IAAI;;CAG/B,QAAc;AACZ,OAAK,MAAM,OAAO;AAClB,OAAK,MAAM,OAAO;AAClB,OAAK,cAAc;;CAGrB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;CAGpB,IAAI,mBAA2B;AAC7B,SAAO,KAAK;;CAGd,IAAI,UAAkB;AACpB,SAAO,KAAK"}
|
package/dist/utils/frameTime.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
//#region src/utils/frameTime.ts
|
|
2
2
|
/**
|
|
3
|
+
* Frame timing utilities for quantizing time values to frame boundaries.
|
|
4
|
+
* These utilities ensure consistent frame-aligned timing across the codebase.
|
|
5
|
+
*/
|
|
6
|
+
/** Default FPS when none is specified */
|
|
7
|
+
const DEFAULT_FPS = 30;
|
|
8
|
+
/**
|
|
9
|
+
* Calculate the duration of a single frame in milliseconds.
|
|
10
|
+
*/
|
|
11
|
+
function calculateFrameIntervalMs(fps) {
|
|
12
|
+
if (fps <= 0) return 1e3 / DEFAULT_FPS;
|
|
13
|
+
return 1e3 / fps;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Quantize a time value (in milliseconds) to the nearest frame boundary.
|
|
17
|
+
* This ensures frame markers align perfectly with playhead position.
|
|
18
|
+
*/
|
|
19
|
+
function quantizeToFrameTimeMs(timeMs, fps) {
|
|
20
|
+
if (!fps || fps <= 0) return timeMs;
|
|
21
|
+
const frameDurationMs = calculateFrameIntervalMs(fps);
|
|
22
|
+
return Math.round(timeMs / frameDurationMs) * frameDurationMs;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
3
25
|
* Quantize a time value (in seconds) to the nearest frame boundary.
|
|
4
26
|
* This ensures time values align with frame boundaries for consistent rendering.
|
|
5
27
|
*/
|
|
@@ -10,5 +32,5 @@ function quantizeToFrameTimeS(timeSeconds, fps) {
|
|
|
10
32
|
}
|
|
11
33
|
|
|
12
34
|
//#endregion
|
|
13
|
-
export { quantizeToFrameTimeS };
|
|
35
|
+
export { quantizeToFrameTimeMs, quantizeToFrameTimeS };
|
|
14
36
|
//# sourceMappingURL=frameTime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frameTime.js","names":[],"sources":["../../src/utils/frameTime.ts"],"sourcesContent":["/**\n * Frame timing utilities for quantizing time values to frame boundaries.\n * These utilities ensure consistent frame-aligned timing across the codebase.\n */\n\n/** Default FPS when none is specified */\nexport const DEFAULT_FPS = 30;\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / DEFAULT_FPS;\n return 1000 / fps;\n}\n\n/**\n * Quantize a time value (in milliseconds) to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationMs = calculateFrameIntervalMs(fps);\n return Math.round(timeMs / frameDurationMs) * frameDurationMs;\n}\n\n/**\n * Quantize a time value (in seconds) to the nearest frame boundary.\n * This ensures time values align with frame boundaries for consistent rendering.\n */\nexport function quantizeToFrameTimeS(timeSeconds: number, fps: number): number {\n if (!fps || fps <= 0) return timeSeconds;\n const frameDurationS = 1 / fps;\n return Math.round(timeSeconds / frameDurationS) * frameDurationS;\n}\n\n/**\n * Clamp and quantize a seek time to valid range.\n * Prevents \"Sample not found\" errors at video boundaries.\n
|
|
1
|
+
{"version":3,"file":"frameTime.js","names":[],"sources":["../../src/utils/frameTime.ts"],"sourcesContent":["/**\n * Frame timing utilities for quantizing time values to frame boundaries.\n * These utilities ensure consistent frame-aligned timing across the codebase.\n */\n\n/** Default FPS when none is specified */\nexport const DEFAULT_FPS = 30;\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / DEFAULT_FPS;\n return 1000 / fps;\n}\n\n/**\n * Quantize a time value (in milliseconds) to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationMs = calculateFrameIntervalMs(fps);\n return Math.round(timeMs / frameDurationMs) * frameDurationMs;\n}\n\n/**\n * Quantize a time value (in seconds) to the nearest frame boundary.\n * This ensures time values align with frame boundaries for consistent rendering.\n */\nexport function quantizeToFrameTimeS(timeSeconds: number, fps: number): number {\n if (!fps || fps <= 0) return timeSeconds;\n const frameDurationS = 1 / fps;\n return Math.round(timeSeconds / frameDurationS) * frameDurationS;\n}\n\n/**\n * Clamp and quantize a seek time to valid range.\n * Prevents \"Sample not found\" errors at video boundaries.\n *\n * @param desiredSeekTimeMs - The desired seek time in milliseconds\n * @param durationMs - The total duration in milliseconds\n * @param fps - Frames per second (defaults to 30)\n * @returns Clamped and quantized time in milliseconds\n */\nexport function clampAndQuantizeSeekTimeMs(\n desiredSeekTimeMs: number,\n durationMs: number,\n fps: number = DEFAULT_FPS,\n): number {\n if (durationMs <= 0) return 0;\n\n // Quantize to frame boundaries\n const quantizedMs = quantizeToFrameTimeMs(desiredSeekTimeMs, fps);\n\n // Clamp to valid range [0, lastFrameTime]\n // The last valid frame is at durationMs - frameDurationMs to ensure we don't\n // seek past the last decodable frame\n const frameDurationMs = calculateFrameIntervalMs(fps);\n const maxValidTime = Math.max(0, durationMs - frameDurationMs);\n return Math.max(0, Math.min(quantizedMs, maxValidTime));\n}\n"],"mappings":";;;;;;AAMA,MAAa,cAAc;;;;AAK3B,SAAgB,yBAAyB,KAAqB;AAC5D,KAAI,OAAO,EAAG,QAAO,MAAO;AAC5B,QAAO,MAAO;;;;;;AAOhB,SAAgB,sBAAsB,QAAgB,KAAqB;AACzE,KAAI,CAAC,OAAO,OAAO,EAAG,QAAO;CAC7B,MAAM,kBAAkB,yBAAyB,IAAI;AACrD,QAAO,KAAK,MAAM,SAAS,gBAAgB,GAAG;;;;;;AAOhD,SAAgB,qBAAqB,aAAqB,KAAqB;AAC7E,KAAI,CAAC,OAAO,OAAO,EAAG,QAAO;CAC7B,MAAM,iBAAiB,IAAI;AAC3B,QAAO,KAAK,MAAM,cAAc,eAAe,GAAG"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/editframe/elements.git",
|
|
8
|
+
"directory": "packages/elements"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"scripts": {
|
|
7
|
-
"typecheck": "tsc --noEmit
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
8
13
|
"build": "tsdown && node scripts/build-css.js",
|
|
9
14
|
"build:watch": "tsdown --watch",
|
|
10
15
|
"typedoc": "(typedoc --json ./types.json --plugin typedoc-plugin-zod --excludeExternals ./src || true) && ([ -f ./types.json ] && jq -c . ./types.json > ./types.tmp.json && mv ./types.tmp.json ./types.json || true)"
|
|
@@ -13,7 +18,7 @@
|
|
|
13
18
|
"license": "UNLICENSED",
|
|
14
19
|
"dependencies": {
|
|
15
20
|
"@bramus/style-observer": "^1.3.0",
|
|
16
|
-
"@editframe/assets": "0.
|
|
21
|
+
"@editframe/assets": "0.38.0",
|
|
17
22
|
"@lit/context": "^1.1.6",
|
|
18
23
|
"@opentelemetry/api": "^1.9.0",
|
|
19
24
|
"@opentelemetry/context-zone": "^1.26.0",
|
|
@@ -28,15 +33,14 @@
|
|
|
28
33
|
"zod": "^3.24.1"
|
|
29
34
|
},
|
|
30
35
|
"devDependencies": {
|
|
36
|
+
"@jridgewell/trace-mapping": "^0.3.28",
|
|
31
37
|
"@types/dom-webcodecs": "^0.1.11",
|
|
32
|
-
"@types/node": "^
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
33
39
|
"autoprefixer": "^10.4.19",
|
|
34
40
|
"postcss": "^8.4.38",
|
|
35
41
|
"tailwindcss": "^3.4.3",
|
|
36
|
-
"typescript": "^5.
|
|
42
|
+
"typescript": "^5.9.3"
|
|
37
43
|
},
|
|
38
|
-
"main": "./dist/index.js",
|
|
39
|
-
"module": "./dist/index.js",
|
|
40
44
|
"types": "./dist/index.d.ts",
|
|
41
45
|
"exports": {
|
|
42
46
|
".": {
|
|
@@ -63,6 +67,12 @@
|
|
|
63
67
|
"default": "./dist/index.js"
|
|
64
68
|
}
|
|
65
69
|
},
|
|
70
|
+
"./server": {
|
|
71
|
+
"import": {
|
|
72
|
+
"types": "./dist/server.d.ts",
|
|
73
|
+
"default": "./dist/server.js"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
66
76
|
"./node": {
|
|
67
77
|
"import": {
|
|
68
78
|
"types": "./dist/node.d.ts",
|
|
@@ -71,7 +81,10 @@
|
|
|
71
81
|
},
|
|
72
82
|
"./package.json": "./package.json",
|
|
73
83
|
"./styles.css": "./dist/style.css",
|
|
84
|
+
"./theme.css": "./dist/gui/ef-theme.css",
|
|
74
85
|
"./types.json": "./types.json"
|
|
75
86
|
}
|
|
76
|
-
}
|
|
87
|
+
},
|
|
88
|
+
"main": "./dist/index.js",
|
|
89
|
+
"module": "./dist/index.js"
|
|
77
90
|
}
|
package/scripts/build-css.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync, cpSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import autoprefixer from "autoprefixer";
|
|
@@ -14,6 +14,7 @@ const configPath = join(__dirname, "..", "tailwind.config.ts");
|
|
|
14
14
|
|
|
15
15
|
// Ensure dist directory exists
|
|
16
16
|
mkdirSync(distDir, { recursive: true });
|
|
17
|
+
mkdirSync(join(distDir, "gui"), { recursive: true });
|
|
17
18
|
|
|
18
19
|
// Read source CSS
|
|
19
20
|
const cssPath = join(srcDir, "elements.css");
|
|
@@ -30,6 +31,12 @@ postcss([tailwindcss({ config: configPath }), autoprefixer()])
|
|
|
30
31
|
writeFileSync(join(distDir, "style.css.map"), result.map.toString());
|
|
31
32
|
}
|
|
32
33
|
console.log("✅ CSS processed and written to dist/style.css");
|
|
34
|
+
|
|
35
|
+
// Copy theme CSS file
|
|
36
|
+
const themeCssPath = join(srcDir, "gui", "ef-theme.css");
|
|
37
|
+
const themeDistPath = join(distDir, "gui", "ef-theme.css");
|
|
38
|
+
cpSync(themeCssPath, themeDistPath);
|
|
39
|
+
console.log("✅ Theme CSS copied to dist/gui/ef-theme.css");
|
|
33
40
|
})
|
|
34
41
|
.catch((error) => {
|
|
35
42
|
console.error("❌ CSS processing failed:", error);
|
package/test/setup.ts
CHANGED
|
@@ -62,7 +62,6 @@ afterAll(() => {
|
|
|
62
62
|
if (typeof window !== "undefined") {
|
|
63
63
|
// Always set the flag, creating it if it doesn't exist
|
|
64
64
|
// This handles both profiled and non-profiled test runs
|
|
65
|
-
console.log("[Profiler] Signaling stop...");
|
|
66
65
|
(window as any).__PROFILER_STOP_REQUESTED__ = true;
|
|
67
66
|
|
|
68
67
|
// Give profiler time to detect the signal and retrieve profile data
|
package/test/useAssetMSW.ts
CHANGED
|
@@ -100,4 +100,54 @@ export const assetMSWHandlers = [
|
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
}),
|
|
103
|
+
|
|
104
|
+
http.get("/api/v1/files/:id/index", async () => {
|
|
105
|
+
const mockIndex = {
|
|
106
|
+
0: {
|
|
107
|
+
duration: 10000,
|
|
108
|
+
timescale: 1000,
|
|
109
|
+
fragments: [
|
|
110
|
+
{
|
|
111
|
+
offset: 0,
|
|
112
|
+
size: 1024,
|
|
113
|
+
timestamp: 0,
|
|
114
|
+
duration: 10000,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return HttpResponse.json(mockIndex, {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}),
|
|
126
|
+
|
|
127
|
+
http.get("/api/v1/files/:id/tracks/:trackId", async ({ request }) => {
|
|
128
|
+
const rangeHeader = request.headers.get("range");
|
|
129
|
+
|
|
130
|
+
if (rangeHeader) {
|
|
131
|
+
const mockData = new ArrayBuffer(1024);
|
|
132
|
+
return new HttpResponse(mockData, {
|
|
133
|
+
status: 206,
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "video/mp4",
|
|
136
|
+
"Accept-Ranges": "bytes",
|
|
137
|
+
"Content-Range": rangeHeader,
|
|
138
|
+
"Content-Length": "1024",
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const mockData = new ArrayBuffer(1024);
|
|
144
|
+
return new HttpResponse(mockData, {
|
|
145
|
+
status: 200,
|
|
146
|
+
headers: {
|
|
147
|
+
"Content-Type": "video/mp4",
|
|
148
|
+
"Accept-Ranges": "bytes",
|
|
149
|
+
"Content-Length": "1024",
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}),
|
|
103
153
|
];
|
|
@@ -15,14 +15,28 @@ export interface SnapshotComparisonResult {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Capture a canvas
|
|
18
|
+
* Capture a canvas or image as a data URL.
|
|
19
19
|
* Uses JPEG format with configurable quality for smaller file sizes.
|
|
20
20
|
*/
|
|
21
21
|
export function captureCanvasAsDataUrl(
|
|
22
|
-
|
|
22
|
+
source: CanvasImageSource | HTMLCanvasElement,
|
|
23
23
|
format: "image/png" | "image/jpeg" = "image/jpeg",
|
|
24
24
|
quality: number = 0.85,
|
|
25
25
|
): string {
|
|
26
|
+
// If it's already a canvas, use toDataURL directly
|
|
27
|
+
if (source instanceof HTMLCanvasElement) {
|
|
28
|
+
return source.toDataURL(format, quality);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise, draw to temp canvas first
|
|
32
|
+
const canvas = document.createElement("canvas");
|
|
33
|
+
canvas.width = (source as any).width as number;
|
|
34
|
+
canvas.height = (source as any).height as number;
|
|
35
|
+
const ctx = canvas.getContext("2d");
|
|
36
|
+
if (!ctx) {
|
|
37
|
+
throw new Error("Failed to get canvas 2d context");
|
|
38
|
+
}
|
|
39
|
+
ctx.drawImage(source, 0, 0);
|
|
26
40
|
return canvas.toDataURL(format, quality);
|
|
27
41
|
}
|
|
28
42
|
|
|
@@ -178,7 +192,7 @@ export async function assertCanvasSnapshot(
|
|
|
178
192
|
* Throws an assertion error if the diff exceeds the acceptable threshold.
|
|
179
193
|
*/
|
|
180
194
|
export async function expectCanvasToMatchSnapshot(
|
|
181
|
-
|
|
195
|
+
source: CanvasImageSource | HTMLCanvasElement,
|
|
182
196
|
testName: string,
|
|
183
197
|
snapshotName: string,
|
|
184
198
|
options: {
|
|
@@ -187,7 +201,7 @@ export async function expectCanvasToMatchSnapshot(
|
|
|
187
201
|
} = {},
|
|
188
202
|
): Promise<void> {
|
|
189
203
|
const result = await assertCanvasSnapshot(
|
|
190
|
-
|
|
204
|
+
source as unknown as HTMLCanvasElement,
|
|
191
205
|
testName,
|
|
192
206
|
snapshotName,
|
|
193
207
|
options,
|
|
@@ -210,12 +224,12 @@ export async function expectCanvasToMatchSnapshot(
|
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
/**
|
|
213
|
-
* Compare two canvases directly against each other.
|
|
227
|
+
* Compare two canvases or images directly against each other.
|
|
214
228
|
* Returns comparison results including diff percentage.
|
|
215
229
|
*/
|
|
216
230
|
export async function compareTwoCanvases(
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
source1: CanvasImageSource | HTMLCanvasElement,
|
|
232
|
+
source2: CanvasImageSource | HTMLCanvasElement,
|
|
219
233
|
testName: string,
|
|
220
234
|
comparisonName: string,
|
|
221
235
|
options: {
|
|
@@ -226,8 +240,8 @@ export async function compareTwoCanvases(
|
|
|
226
240
|
const { threshold = 0.1, acceptableDiffPercentage = 1.0 } = options;
|
|
227
241
|
|
|
228
242
|
// Use PNG format for consistent comparison (odiff works best with PNG)
|
|
229
|
-
const dataUrl1 = captureCanvasAsDataUrl(
|
|
230
|
-
const dataUrl2 = captureCanvasAsDataUrl(
|
|
243
|
+
const dataUrl1 = captureCanvasAsDataUrl(source1, "image/png");
|
|
244
|
+
const dataUrl2 = captureCanvasAsDataUrl(source2, "image/png");
|
|
231
245
|
|
|
232
246
|
const response = await fetch("/@ef-compare-two-images", {
|
|
233
247
|
method: "POST",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
13
|
-
key = keys[i];
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
15
|
-
get: ((k) => from[k]).bind(null, key),
|
|
16
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
-
value: mod,
|
|
23
|
-
enumerable: true
|
|
24
|
-
}) : target, mod));
|
|
25
|
-
|
|
26
|
-
//#endregion
|
|
27
|
-
export { __commonJS, __toESM };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AssetIdMediaEngine.js","names":["data: Record<number, TrackFragmentIndex>","assetId: string","apiHost: string","paths: InitSegmentPaths"],"sources":["../../../src/elements/EFMedia/AssetIdMediaEngine.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type {\n InitSegmentPaths,\n MediaEngine,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport type { UrlGenerator } from \"../../transcoding/utils/UrlGenerator\";\nimport type { EFMedia } from \"../EFMedia\";\nimport { AssetMediaEngine } from \"./AssetMediaEngine\";\n\nexport class AssetIdMediaEngine\n extends AssetMediaEngine\n implements MediaEngine\n{\n static async fetchByAssetId(\n host: EFMedia,\n _urlGenerator: UrlGenerator,\n assetId: string,\n apiHost: string,\n requiredTracks: \"audio\" | \"video\" | \"both\" = \"both\",\n signal?: AbortSignal,\n ) {\n const url = `${apiHost}/api/v1/isobmff_files/${assetId}/index`;\n const response = await host.fetch(url, { signal });\n \n // Check for abort after potentially slow network operation\n signal?.throwIfAborted();\n \n // Check if response is ok before parsing JSON\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Failed to fetch asset index: ${response.status} ${text}`);\n }\n \n // Check content type to avoid parsing non-JSON responses\n const contentType = response.headers.get(\"content-type\");\n if (contentType && !contentType.includes(\"application/json\")) {\n const text = await response.text();\n throw new Error(`Expected JSON but got ${contentType}: ${text.substring(0, 100)}`);\n }\n \n let data: Record<number, TrackFragmentIndex>;\n try {\n data = (await response.json()) as Record<number, TrackFragmentIndex>;\n \n // Check for abort after potentially slow JSON parsing\n signal?.throwIfAborted();\n } catch (error) {\n // If aborted during JSON parsing, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // If JSON parse fails, the response might be \"File not found\" or similar text\n const text = await response.text();\n throw new Error(`Failed to parse JSON response: ${text.substring(0, 100)}`);\n }\n \n const engine = new AssetIdMediaEngine(host, assetId, data, apiHost, _urlGenerator);\n \n // Check for abort after engine construction\n signal?.throwIfAborted();\n \n // Validate that segments are accessible by trying to fetch the first init segment\n // This prevents creating a media engine that will fail on all subsequent segment fetches\n // If segments require authentication that's not available, fail early\n // Only validate tracks that are actually required by the consumer (e.g., EFAudio only needs audio)\n // Skip validation if no signal provided (backwards compatibility) - validation is optional\n if (signal) {\n const videoTrack = engine.getVideoTrackIndex();\n const audioTrack = engine.getAudioTrackIndex();\n const needsVideo = requiredTracks === \"video\" || requiredTracks === \"both\";\n const needsAudio = requiredTracks === \"audio\" || requiredTracks === \"both\";\n \n // Validate video track if required and available\n if (needsVideo && videoTrack && videoTrack.track !== undefined) {\n try {\n await engine.fetchInitSegment(\n { trackId: videoTrack.track, src: engine.src },\n signal,\n );\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // If fetch fails with 401, segments require authentication that's not available\n // Fail media engine creation early to avoid all subsequent fetch calls\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n (error.message.includes(\"Failed to fetch\") && error.message.includes(\"401\")))\n ) {\n throw new Error(`Video segments require authentication: ${error.message}`);\n }\n // For other errors (404, network errors, etc.), allow media engine creation\n // These might be transient or expected in some test scenarios\n }\n }\n \n // Check for abort between validations\n signal?.throwIfAborted();\n \n // Validate audio track if required and available\n if (needsAudio && audioTrack && audioTrack.track !== undefined) {\n try {\n await engine.fetchInitSegment(\n { trackId: audioTrack.track, src: engine.src },\n signal,\n );\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // If fetch fails with 401, segments require authentication that's not available\n // Fail media engine creation early to avoid all subsequent fetch calls\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n (error.message.includes(\"Failed to fetch\") && error.message.includes(\"401\")))\n ) {\n throw new Error(`Audio segments require authentication: ${error.message}`);\n }\n // For other errors (404, network errors, etc.), allow media engine creation\n // These might be transient or expected in some test scenarios\n }\n }\n }\n \n return engine;\n }\n\n constructor(\n host: EFMedia,\n public assetId: string,\n data: Record<number, TrackFragmentIndex>,\n private apiHost: string,\n urlGenerator: UrlGenerator,\n ) {\n // Pass assetId as src to parent constructor for compatibility\n super(host, assetId, urlGenerator);\n // Initialize data after parent constructor\n this.data = data;\n\n // Calculate duration from the data\n const longestFragment = Object.values(this.data).reduce(\n (max, fragment) => Math.max(max, fragment.duration / fragment.timescale),\n 0,\n );\n this.durationMs = longestFragment * 1000;\n \n // Initialize MediaEngine interface properties\n this.templates = {\n initSegment: `${apiHost}/api/v1/isobmff_tracks/${assetId}/{trackId}`,\n mediaSegment: `${apiHost}/api/v1/isobmff_tracks/${assetId}/{trackId}`,\n };\n }\n\n // Override URL-building methods to use API endpoints instead of file paths\n getInitSegmentPaths(): InitSegmentPaths {\n const paths: InitSegmentPaths = {};\n const audioTrack = this.getAudioTrackIndex();\n const videoTrack = this.getVideoTrackIndex();\n\n if (audioTrack !== undefined) {\n paths.audio = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${audioTrack.track}`,\n pos: audioTrack.initSegment.offset,\n size: audioTrack.initSegment.size,\n };\n }\n\n if (videoTrack !== undefined) {\n paths.video = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${videoTrack.track}`,\n pos: videoTrack.initSegment.offset,\n size: videoTrack.initSegment.size,\n };\n }\n\n return paths;\n }\n\n // MediaEngine interface property - initialized in constructor/static fetch\n templates!: { initSegment: string; mediaSegment: string };\n\n buildInitSegmentUrl(trackId: number) {\n return `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${trackId}`;\n }\n\n buildMediaSegmentUrl(trackId: number, _segmentId: number) {\n return `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${trackId}`;\n }\n\n convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n segmentId: number,\n rendition: VideoRendition,\n ): number[] {\n if (!rendition.trackId) {\n throw new Error(\n \"[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata\",\n );\n }\n // For AssetMediaEngine, we need to calculate the actual segment start time\n // using the precise segment boundaries from the track fragment index\n const trackData = this.data[rendition.trackId];\n if (!trackData) {\n throw new Error(\"Track not found\");\n }\n const segment = trackData.segments?.[segmentId];\n if (!segment) {\n throw new Error(\"Segment not found\");\n }\n const segmentStartMs = (segment.cts / trackData.timescale) * 1000;\n\n return globalTimestamps.map(\n (globalMs) => (globalMs - segmentStartMs) / 1000,\n );\n }\n}\n"],"mappings":";;;AAUA,IAAa,qBAAb,MAAa,2BACH,iBAEV;CACE,aAAa,eACX,MACA,eACA,SACA,SACA,iBAA6C,QAC7C,QACA;EACA,MAAM,MAAM,GAAG,QAAQ,wBAAwB,QAAQ;EACvD,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK,EAAE,QAAQ,CAAC;AAGlD,UAAQ,gBAAgB;AAGxB,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAM,IAAI,MAAM,gCAAgC,SAAS,OAAO,GAAG,OAAO;;EAI5E,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe;AACxD,MAAI,eAAe,CAAC,YAAY,SAAS,mBAAmB,EAAE;GAC5D,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAM,IAAI,MAAM,yBAAyB,YAAY,IAAI,KAAK,UAAU,GAAG,IAAI,GAAG;;EAGpF,IAAIA;AACJ,MAAI;AACF,UAAQ,MAAM,SAAS,MAAM;AAG7B,WAAQ,gBAAgB;WACjB,OAAO;AAEd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;GAGR,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAM,IAAI,MAAM,kCAAkC,KAAK,UAAU,GAAG,IAAI,GAAG;;EAG7E,MAAM,SAAS,IAAI,mBAAmB,MAAM,SAAS,MAAM,SAAS,cAAc;AAGlF,UAAQ,gBAAgB;AAOxB,MAAI,QAAQ;GACV,MAAM,aAAa,OAAO,oBAAoB;GAC9C,MAAM,aAAa,OAAO,oBAAoB;GAC9C,MAAM,aAAa,mBAAmB,WAAW,mBAAmB;GACpE,MAAM,aAAa,mBAAmB,WAAW,mBAAmB;AAGpE,OAAI,cAAc,cAAc,WAAW,UAAU,OACnD,KAAI;AACF,UAAM,OAAO,iBACX;KAAE,SAAS,WAAW;KAAO,KAAK,OAAO;KAAK,EAC9C,OACD;YACM,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAIR,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,MAAM,IAC5B,MAAM,QAAQ,SAAS,eAAe,IACrC,MAAM,QAAQ,SAAS,kBAAkB,IAAI,MAAM,QAAQ,SAAS,MAAM,EAE7E,OAAM,IAAI,MAAM,0CAA0C,MAAM,UAAU;;AAQhF,WAAQ,gBAAgB;AAGxB,OAAI,cAAc,cAAc,WAAW,UAAU,OACnD,KAAI;AACF,UAAM,OAAO,iBACX;KAAE,SAAS,WAAW;KAAO,KAAK,OAAO;KAAK,EAC9C,OACD;YACM,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAIR,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,MAAM,IAC5B,MAAM,QAAQ,SAAS,eAAe,IACrC,MAAM,QAAQ,SAAS,kBAAkB,IAAI,MAAM,QAAQ,SAAS,MAAM,EAE7E,OAAM,IAAI,MAAM,0CAA0C,MAAM,UAAU;;;AAQlF,SAAO;;CAGT,YACE,MACA,AAAOC,SACP,MACA,AAAQC,SACR,cACA;AAEA,QAAM,MAAM,SAAS,aAAa;EAN3B;EAEC;AAMR,OAAK,OAAO;AAOZ,OAAK,aAJmB,OAAO,OAAO,KAAK,KAAK,CAAC,QAC9C,KAAK,aAAa,KAAK,IAAI,KAAK,SAAS,WAAW,SAAS,UAAU,EACxE,EACD,GACmC;AAGpC,OAAK,YAAY;GACf,aAAa,GAAG,QAAQ,yBAAyB,QAAQ;GACzD,cAAc,GAAG,QAAQ,yBAAyB,QAAQ;GAC3D;;CAIH,sBAAwC;EACtC,MAAMC,QAA0B,EAAE;EAClC,MAAM,aAAa,KAAK,oBAAoB;EAC5C,MAAM,aAAa,KAAK,oBAAoB;AAE5C,MAAI,eAAe,OACjB,OAAM,QAAQ;GACZ,MAAM,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG,WAAW;GAC1E,KAAK,WAAW,YAAY;GAC5B,MAAM,WAAW,YAAY;GAC9B;AAGH,MAAI,eAAe,OACjB,OAAM,QAAQ;GACZ,MAAM,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG,WAAW;GAC1E,KAAK,WAAW,YAAY;GAC5B,MAAM,WAAW,YAAY;GAC9B;AAGH,SAAO;;CAMT,oBAAoB,SAAiB;AACnC,SAAO,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG;;CAGlE,qBAAqB,SAAiB,YAAoB;AACxD,SAAO,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG;;CAGlE,mCACE,kBACA,WACA,WACU;AACV,MAAI,CAAC,UAAU,QACb,OAAM,IAAI,MACR,+EACD;EAIH,MAAM,YAAY,KAAK,KAAK,UAAU;AACtC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,kBAAkB;EAEpC,MAAM,UAAU,UAAU,WAAW;AACrC,MAAI,CAAC,QACH,OAAM,IAAI,MAAM,oBAAoB;EAEtC,MAAM,iBAAkB,QAAQ,MAAM,UAAU,YAAa;AAE7D,SAAO,iBAAiB,KACrB,cAAc,WAAW,kBAAkB,IAC7C"}
|