@editframe/elements 0.30.2-beta.0 → 0.31.0-beta.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.d.ts +5 -0
- package/dist/EF_FRAMEGEN.js +20 -4
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_INTERACTIVE.js.map +1 -1
- package/dist/_virtual/rolldown_runtime.js +27 -0
- package/dist/canvas/EFCanvas.d.ts +311 -0
- package/dist/canvas/EFCanvas.js +1089 -0
- package/dist/canvas/EFCanvas.js.map +1 -0
- package/dist/canvas/EFCanvasItem.d.ts +55 -0
- package/dist/canvas/EFCanvasItem.js +72 -0
- package/dist/canvas/EFCanvasItem.js.map +1 -0
- package/dist/canvas/api/CanvasAPI.d.ts +115 -0
- package/dist/canvas/api/CanvasAPI.js +182 -0
- package/dist/canvas/api/CanvasAPI.js.map +1 -0
- package/dist/canvas/api/types.d.ts +42 -0
- package/dist/canvas/coordinateTransform.js +90 -0
- package/dist/canvas/coordinateTransform.js.map +1 -0
- package/dist/canvas/getElementBounds.js +40 -0
- package/dist/canvas/getElementBounds.js.map +1 -0
- package/dist/canvas/overlays/SelectionOverlay.js +265 -0
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
- package/dist/canvas/overlays/overlayState.js +153 -0
- package/dist/canvas/overlays/overlayState.js.map +1 -0
- package/dist/canvas/selection/SelectionController.js +105 -0
- package/dist/canvas/selection/SelectionController.js.map +1 -0
- package/dist/canvas/selection/SelectionModel.d.ts +98 -0
- package/dist/canvas/selection/SelectionModel.js +229 -0
- package/dist/canvas/selection/SelectionModel.js.map +1 -0
- package/dist/canvas/selection/selectionContext.d.ts +31 -0
- package/dist/canvas/selection/selectionContext.js +12 -0
- package/dist/canvas/selection/selectionContext.js.map +1 -0
- package/dist/elements/ContainerInfo.d.ts +29 -0
- package/dist/elements/ContainerInfo.js +30 -0
- package/dist/elements/ContainerInfo.js.map +1 -0
- package/dist/elements/EFAudio.d.ts +13 -3
- package/dist/elements/EFAudio.js +64 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +18 -16
- package/dist/elements/EFCaptions.js +110 -19
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +16 -6
- package/dist/elements/EFImage.js +79 -9
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +18 -7
- package/dist/elements/EFMedia.js +23 -3
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +96 -0
- package/dist/elements/EFPanZoom.js +290 -0
- package/dist/elements/EFPanZoom.js.map +1 -0
- package/dist/elements/EFSourceMixin.js +7 -6
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +6 -6
- package/dist/elements/EFSurface.js +7 -2
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +192 -71
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +102 -13
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +32 -6
- package/dist/elements/EFTextSegment.js +53 -15
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +129 -56
- package/dist/elements/EFThumbnailStrip.js +605 -359
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +233 -25
- package/dist/elements/EFTimegroup.js +865 -144
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +42 -5
- package/dist/elements/EFVideo.js +165 -11
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +6 -6
- package/dist/elements/EFWaveform.js +2 -1
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.d.ts +35 -0
- package/dist/elements/ElementPositionInfo.js +49 -0
- package/dist/elements/ElementPositionInfo.js.map +1 -0
- package/dist/elements/FetchMixin.js +16 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SessionThumbnailCache.js +154 -0
- package/dist/elements/SessionThumbnailCache.js.map +1 -0
- package/dist/elements/TargetController.js +3 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js +9 -3
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/findRootTemporal.js +30 -0
- package/dist/elements/findRootTemporal.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +18 -5
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/updateAnimations.js +171 -28
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +4 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +74 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
- package/dist/gui/EFActiveRootTemporal.js +94 -0
- package/dist/gui/EFActiveRootTemporal.js.map +1 -0
- package/dist/gui/EFConfiguration.d.ts +7 -1
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +109 -13
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +11 -214
- package/dist/gui/EFFilmstrip.js +53 -1152
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +3 -3
- package/dist/gui/EFFitScale.js +39 -12
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +48 -0
- package/dist/gui/EFOverlayItem.js +97 -0
- package/dist/gui/EFOverlayItem.js.map +1 -0
- package/dist/gui/EFOverlayLayer.d.ts +70 -0
- package/dist/gui/EFOverlayLayer.js +104 -0
- package/dist/gui/EFOverlayLayer.js.map +1 -0
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +12 -16
- package/dist/gui/EFResizableBox.js +109 -451
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +30 -5
- package/dist/gui/EFScrubber.js +224 -31
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +4 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +71 -0
- package/dist/gui/EFTimelineRuler.js +320 -0
- package/dist/gui/EFTimelineRuler.js.map +1 -0
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +91 -0
- package/dist/gui/EFTransformHandles.js +393 -0
- package/dist/gui/EFTransformHandles.js.map +1 -0
- package/dist/gui/EFWorkbench.d.ts +178 -0
- package/dist/gui/EFWorkbench.js +2067 -22
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.d.ts +31 -0
- package/dist/gui/FitScaleHelpers.js +41 -0
- package/dist/gui/FitScaleHelpers.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +2 -1
- package/dist/gui/PlaybackController.js +46 -15
- 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/hierarchy/EFHierarchy.d.ts +65 -0
- package/dist/gui/hierarchy/EFHierarchy.js +338 -0
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
- package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
- package/dist/gui/hierarchy/hierarchyContext.js +8 -0
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
- package/dist/gui/icons.js +34 -0
- package/dist/gui/icons.js.map +1 -0
- package/dist/gui/panZoomTransformContext.js +12 -0
- package/dist/gui/panZoomTransformContext.js.map +1 -0
- package/dist/gui/previewSettingsContext.js +12 -0
- package/dist/gui/previewSettingsContext.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +270 -0
- package/dist/gui/timeline/EFTimeline.js +1369 -0
- package/dist/gui/timeline/EFTimeline.js.map +1 -0
- package/dist/gui/timeline/EFTimelineRow.js +374 -0
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
- package/dist/gui/timeline/TrimHandles.d.ts +36 -0
- package/dist/gui/timeline/TrimHandles.js +204 -0
- package/dist/gui/timeline/TrimHandles.js.map +1 -0
- package/dist/gui/timeline/flattenHierarchy.js +31 -0
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
- package/dist/gui/timeline/timelineStateContext.js +42 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TextTrack.js +250 -0
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TrackItem.js +269 -0
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
- package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
- package/dist/gui/transformCalculations.js +217 -0
- package/dist/gui/transformCalculations.js.map +1 -0
- package/dist/gui/transformUtils.d.ts +37 -0
- package/dist/gui/transformUtils.js +77 -0
- package/dist/gui/transformUtils.js.map +1 -0
- package/dist/gui/tree/EFTree.d.ts +59 -0
- package/dist/gui/tree/EFTree.js +174 -0
- package/dist/gui/tree/EFTree.js.map +1 -0
- package/dist/gui/tree/EFTreeItem.d.ts +38 -0
- package/dist/gui/tree/EFTreeItem.js +146 -0
- package/dist/gui/tree/EFTreeItem.js.map +1 -0
- package/dist/gui/tree/treeContext.d.ts +60 -0
- package/dist/gui/tree/treeContext.js +23 -0
- package/dist/gui/tree/treeContext.js.map +1 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +30 -6
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react.development.js +1521 -0
- package/dist/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/react/index.js +13 -0
- package/dist/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +13 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/preview/AdaptiveResolutionTracker.js +228 -0
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
- package/dist/preview/RenderProfiler.js +135 -0
- package/dist/preview/RenderProfiler.js.map +1 -0
- package/dist/preview/previewSettings.js +131 -0
- package/dist/preview/previewSettings.js.map +1 -0
- package/dist/preview/previewTypes.js +64 -0
- package/dist/preview/previewTypes.js.map +1 -0
- package/dist/preview/renderTimegroupPreview.js +656 -0
- package/dist/preview/renderTimegroupPreview.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
- package/dist/preview/renderTimegroupToCanvas.js +833 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
- package/dist/preview/renderTimegroupToVideo.js +274 -0
- package/dist/preview/renderTimegroupToVideo.js.map +1 -0
- package/dist/preview/renderers.js +16 -0
- package/dist/preview/renderers.js.map +1 -0
- package/dist/preview/statsTrackingStrategy.js +201 -0
- package/dist/preview/statsTrackingStrategy.js.map +1 -0
- package/dist/preview/thumbnailCacheSettings.js +52 -0
- package/dist/preview/thumbnailCacheSettings.js.map +1 -0
- package/dist/preview/workers/WorkerPool.js +178 -0
- package/dist/preview/workers/WorkerPool.js.map +1 -0
- package/dist/preview/workers/encoderWorkerInline.js +103 -0
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -0
- package/dist/sandbox/PlaybackControls.js +10 -0
- package/dist/sandbox/PlaybackControls.js.map +1 -0
- package/dist/sandbox/ScenarioRunner.js +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/style.css +71 -67
- package/dist/transcoding/types/index.d.ts +2 -1
- package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
- package/dist/transcoding/utils/UrlGenerator.js +12 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -375
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +14 -0
- package/dist/utils/frameTime.js.map +1 -0
- package/package.json +3 -3
- package/test/profilingPlugin.ts +223 -0
- package/test/recordReplayProxyPlugin.js +22 -27
- package/test/thumbnail-performance-test.html +116 -0
- package/test/visualRegressionUtils.ts +286 -0
- package/types.json +1 -1
- package/dist/elements/TimegroupController.d.ts +0 -18
- package/dist/msToTimeCode.js +0 -17
- package/dist/msToTimeCode.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFCaptions.js","names":["EFCaptionsActiveWord","EFCaptionsSegment","EFCaptionsBeforeActiveWord","EFCaptionsAfterActiveWord","EFCaptions","captionsData: Caption | null"],"sources":["../../src/elements/EFCaptions.ts"],"sourcesContent":["import { Task, TaskStatus } from \"@lit/task\";\nimport { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport type { GetISOBMFFFileTranscriptionResult } from \"../../../api/src/index.js\";\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { CrossUpdateController } from \"./CrossUpdateController.js\";\nimport { EFAudio } from \"./EFAudio.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal, flushStartTimeMsCache } from \"./EFTemporal.js\";\nimport { flushSequenceDurationCache } from \"./EFTimegroup.js\";\nimport { EFVideo } from \"./EFVideo.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\nexport interface WordSegment {\n text: string;\n start: number;\n end: number;\n}\n\nexport interface Segment {\n start: number;\n end: number;\n text: string;\n}\n\nexport interface Caption {\n segments: Segment[];\n word_segments: WordSegment[];\n}\n\nconst stopWords = new Set([\"\", \".\", \"!\", \"?\", \",\"]);\n\n@customElement(\"ef-captions-active-word\")\nexport class EFCaptionsActiveWord extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: normal;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.wordText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Set deterministic --ef-word-seed value based on word index\n const seed = (this.wordIndex * 9007) % 233; // Prime numbers for better distribution\n const seedValue = seed / 233; // Normalize to 0-1 range\n this.style.setProperty(\"--ef-word-seed\", seedValue.toString());\n\n return html`${this.wordText}`;\n }\n\n @property({ type: Number, attribute: false })\n wordStartMs = 0;\n\n @property({ type: Number, attribute: false })\n wordEndMs = 0;\n\n @property({ type: String, attribute: false })\n wordText = \"\";\n\n @property({ type: Number, attribute: false })\n wordIndex = 0;\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.wordStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.wordEndMs || 0);\n }\n\n get durationMs(): number {\n return this.wordEndMs - this.wordStartMs;\n }\n}\n\n@customElement(\"ef-captions-segment\")\nexport class EFCaptionsSegment extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: normal;\n line-height: 1;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n return html`${this.segmentText}`;\n }\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions-before-active-word\")\nexport class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Check if there's an active word by looking for sibling active word element\n const activeWord = this.closest(\"ef-captions\")?.querySelector(\n \"ef-captions-active-word\",\n );\n const hasActiveWord = activeWord?.wordText && !activeWord.hidden;\n\n return html`${this.segmentText}${hasActiveWord ? \" \" : \"\"}`;\n }\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions-after-active-word\")\nexport class EFCaptionsAfterActiveWord extends EFCaptionsSegment {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Check if there's an active word by looking for sibling active word element\n const activeWord = this.closest(\"ef-captions\")?.querySelector(\n \"ef-captions-active-word\",\n );\n const hasActiveWord = activeWord?.wordText && !activeWord.hidden;\n\n return html`${hasActiveWord ? \" \" : \"\"}${this.segmentText}`;\n }\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions\")\nexport class EFCaptions extends EFSourceMixin(\n EFTemporal(FetchMixin(LitElement)),\n { assetType: \"caption_files\" },\n) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n\n @property({ type: String, attribute: \"target\", reflect: true })\n targetSelector = \"\";\n\n set target(value: string) {\n this.targetSelector = value;\n }\n\n @property({ attribute: \"word-style\" })\n wordStyle = \"\";\n\n /**\n * URL or path to a JSON file containing custom captions data.\n * The JSON should conform to the Caption interface with 'segments' and 'word_segments' arrays.\n */\n @property({ type: String, attribute: \"captions-src\", reflect: true })\n captionsSrc = \"\";\n\n /**\n * Direct captions data object. Takes priority over captions-src and captions-script.\n * Should conform to the Caption interface with 'segments' and 'word_segments' arrays.\n */\n @property({ type: Object, attribute: false })\n captionsData: Caption | null = null;\n\n /**\n * ID of a <script> element containing JSON captions data.\n * The script's textContent should be valid JSON conforming to the Caption interface.\n */\n @property({ type: String, attribute: \"captions-script\", reflect: true })\n captionsScript = \"\";\n\n activeWordContainers = this.getElementsByTagName(\"ef-captions-active-word\");\n segmentContainers = this.getElementsByTagName(\"ef-captions-segment\");\n beforeActiveWordContainers = this.getElementsByTagName(\n \"ef-captions-before-active-word\",\n );\n afterActiveWordContainers = this.getElementsByTagName(\n \"ef-captions-after-active-word\",\n );\n\n render() {\n return html`<slot></slot>`;\n }\n\n transcriptionsPath() {\n if (!this.targetElement) {\n return null;\n }\n if (this.targetElement.assetId) {\n return `${this.apiHost}/api/v1/isobmff_files/${this.targetElement.assetId}/transcription`;\n }\n return null;\n }\n\n captionsPath() {\n if (!this.targetElement) {\n return null;\n }\n if (this.targetElement.assetId) {\n return `${this.apiHost}/api/v1/caption_files/${this.targetElement.assetId}`;\n }\n const targetSrc = this.targetElement.src;\n return `/@ef-captions/${targetSrc}`;\n }\n\n protected md5SumLoader = new Task(this, {\n autoRun: false,\n args: () => [this.target, this.fetch] as const,\n task: async ([_target, fetch], { signal }) => {\n if (!this.targetElement) {\n return null;\n }\n const md5Path = `/@ef-asset/${this.targetElement.src ?? \"\"}`;\n const response = await fetch(md5Path, { method: \"HEAD\", signal });\n return response.headers.get(\"etag\") ?? undefined;\n },\n });\n\n private transcriptionDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.transcriptionsPath(),\n this.fetch,\n this.hasCustomCaptionsData,\n ] as const,\n task: async ([transcriptionsPath, fetch, hasCustomData], { signal }) => {\n // Skip transcription if we have custom captions data\n if (hasCustomData || !transcriptionsPath) {\n return null;\n }\n const response = await fetch(transcriptionsPath, { signal });\n return response.json() as any as GetISOBMFFFileTranscriptionResult;\n },\n });\n\n private transcriptionFragmentPath(\n transcriptionId: string,\n fragmentIndex: number,\n ) {\n return `${this.apiHost}/api/v1/transcriptions/${transcriptionId}/fragments/${fragmentIndex}`;\n }\n\n private fragmentIndexTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [this.transcriptionDataTask.value, this.ownCurrentTimeMs] as const,\n task: async ([transcription, ownCurrentTimeMs]) => {\n if (!transcription) {\n return null;\n }\n const fragmentIndex = Math.floor(\n ownCurrentTimeMs / transcription.work_slice_ms,\n );\n return fragmentIndex;\n },\n });\n\n private customCaptionsDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.captionsSrc,\n this.captionsData,\n this.captionsScript,\n this.fetch,\n ] as const,\n task: async (\n [captionsSrc, captionsData, captionsScript, fetch],\n { signal },\n ) => {\n // Priority: direct data > script reference > URL source\n if (captionsData) {\n return captionsData;\n }\n\n if (captionsScript) {\n const scriptElement = document.getElementById(captionsScript);\n if (scriptElement?.textContent) {\n try {\n return JSON.parse(scriptElement.textContent) as Caption;\n } catch (error) {\n console.error(\n `Failed to parse captions from script #${captionsScript}:`,\n error,\n );\n return null;\n }\n }\n }\n\n if (captionsSrc) {\n try {\n const response = await fetch(captionsSrc, { signal });\n return (await response.json()) as Caption;\n } catch (error) {\n console.error(`Failed to load captions from ${captionsSrc}:`, error);\n return null;\n }\n }\n\n return null;\n },\n });\n\n private transcriptionFragmentDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.transcriptionDataTask.value,\n this.fragmentIndexTask.value,\n this.fetch,\n ] as const,\n task: async ([transcription, fragmentIndex, fetch], { signal }) => {\n if (\n transcription === null ||\n transcription === undefined ||\n fragmentIndex === null ||\n fragmentIndex === undefined\n ) {\n return null;\n }\n const fragmentPath = this.transcriptionFragmentPath(\n transcription.id,\n fragmentIndex,\n );\n const response = await fetch(fragmentPath, { signal });\n return response.json() as any as Caption;\n },\n });\n\n unifiedCaptionsDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.customCaptionsDataTask.value,\n this.transcriptionFragmentDataTask.value,\n ] as const,\n task: async ([_customData, _transcriptionData]) => {\n if (this.customCaptionsDataTask.status === TaskStatus.PENDING) {\n await this.customCaptionsDataTask.taskComplete;\n }\n if (this.transcriptionFragmentDataTask.status === TaskStatus.PENDING) {\n await this.transcriptionFragmentDataTask.taskComplete;\n }\n return (\n this.customCaptionsDataTask.value ||\n this.transcriptionFragmentDataTask.value\n );\n },\n });\n\n frameTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.unifiedCaptionsDataTask.status, this.ownCurrentTimeMs],\n task: async () => {\n await this.unifiedCaptionsDataTask.taskComplete;\n // Trigger updateTextContainers when data is ready or time changes\n this.updateTextContainers();\n },\n });\n\n connectedCallback() {\n super.connectedCallback();\n\n // Try to get target element safely\n const target = this.targetSelector\n ? document.getElementById(this.targetSelector)\n : null;\n if (target && (target instanceof EFAudio || target instanceof EFVideo)) {\n new CrossUpdateController(target, this);\n }\n // For standalone captions with custom data, ensure proper timeline sync\n else if (this.hasCustomCaptionsData && this.rootTimegroup) {\n new CrossUpdateController(this.rootTimegroup, this);\n }\n\n // Prevent display:none from being set on caption elements\n // This maintains constant width in the parent flex container\n const observer = new MutationObserver(() => {\n if (this.style.display === \"none\") {\n // Remove the display:none and use opacity instead\n this.style.removeProperty(\"display\");\n this.style.opacity = \"0\";\n this.style.pointerEvents = \"none\";\n }\n });\n observer.observe(this, { attributes: true, attributeFilter: [\"style\"] });\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n this.updateTextContainers();\n\n // Force duration recalculation when custom captions data changes\n if (\n changedProperties.has(\"captionsData\") ||\n changedProperties.has(\"captionsSrc\") ||\n changedProperties.has(\"captionsScript\")\n ) {\n this.requestUpdate(\"intrinsicDurationMs\");\n\n // Flush sequence duration cache and notify parent timegroups that child duration has changed\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Notify parent timegroup to recalculate its duration\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n }\n }\n\n // Update captions when timeline position changes\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.updateTextContainers();\n }\n }\n\n updateTextContainers() {\n const captionsData = this.unifiedCaptionsDataTask.value as Caption;\n if (!captionsData) {\n return;\n }\n\n // Use ownCurrentTimeMs which is synchronized with the timegroup\n const currentTimeMs = this.ownCurrentTimeMs;\n const currentTimeSec = currentTimeMs / 1000;\n\n // Find the current word from word_segments\n // Use exclusive end boundary to prevent overlap at exact boundaries\n const currentWord = captionsData.word_segments.find(\n (word) => currentTimeSec >= word.start && currentTimeSec < word.end,\n );\n\n // Find the current segment\n // Use exclusive end boundary to prevent overlap at exact boundaries\n const currentSegment = captionsData.segments.find(\n (segment) =>\n currentTimeSec >= segment.start && currentTimeSec < segment.end,\n );\n\n for (const wordContainer of this.activeWordContainers) {\n if (currentWord) {\n wordContainer.wordText = currentWord.text;\n wordContainer.wordStartMs = currentWord.start * 1000;\n wordContainer.wordEndMs = currentWord.end * 1000;\n // Set word index for deterministic animation variation\n const wordIndex = captionsData.word_segments.findIndex(\n (w) =>\n w.start === currentWord.start &&\n w.end === currentWord.end &&\n w.text === currentWord.text,\n );\n wordContainer.wordIndex = wordIndex >= 0 ? wordIndex : 0;\n // Force re-render to update hidden property\n wordContainer.requestUpdate();\n } else {\n // No active word - maintain layout with invisible placeholder\n wordContainer.wordText = \"\"; // Empty when no active word\n wordContainer.wordStartMs = 0;\n wordContainer.wordEndMs = 0;\n wordContainer.requestUpdate();\n }\n }\n\n for (const segmentContainer of this.segmentContainers) {\n if (currentSegment) {\n segmentContainer.segmentText = currentSegment.text;\n segmentContainer.segmentStartMs = currentSegment.start * 1000;\n segmentContainer.segmentEndMs = currentSegment.end * 1000;\n } else {\n // No active segment - clear the container\n segmentContainer.segmentText = \"\";\n segmentContainer.segmentStartMs = 0;\n segmentContainer.segmentEndMs = 0;\n }\n segmentContainer.requestUpdate();\n }\n\n // Process context for both word and segment cases\n if (currentWord && currentSegment) {\n // Find all word segments that fall within the current segment's time range\n const segmentWords = captionsData.word_segments.filter(\n (word) =>\n word.start >= currentSegment.start && word.end <= currentSegment.end,\n );\n\n // Find the index of the current word within the segment's word segments\n const currentWordIndex = segmentWords.findIndex(\n (word) =>\n word.start === currentWord.start && word.end === currentWord.end,\n );\n\n if (currentWordIndex !== -1) {\n // Get words before the current word\n const beforeWords = segmentWords\n .slice(0, currentWordIndex)\n .map((w) => w.text.trim())\n .join(\" \");\n\n // Get words after the current word\n const afterWords = segmentWords\n .slice(currentWordIndex + 1)\n .map((w) => w.text.trim())\n .join(\" \");\n\n // Update before containers - should be visible at the same time as active word\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = beforeWords;\n container.segmentStartMs = currentWord.start * 1000;\n container.segmentEndMs = currentWord.end * 1000;\n container.requestUpdate();\n }\n\n // Update after containers - should be visible at the same time as active word\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = afterWords;\n container.segmentStartMs = currentWord.start * 1000;\n container.segmentEndMs = currentWord.end * 1000;\n container.requestUpdate();\n }\n }\n } else if (currentSegment) {\n // No active word but we have an active segment\n const segmentWords = captionsData.word_segments.filter(\n (word) =>\n word.start >= currentSegment.start && word.end <= currentSegment.end,\n );\n\n // Check if we're before the first word or after the last word\n const firstWord = segmentWords[0];\n const isBeforeFirstWord = firstWord && currentTimeSec < firstWord.start;\n\n if (isBeforeFirstWord) {\n // Before first word starts - show all words in \"after\" container (they're all upcoming)\n const allWords = segmentWords.map((w) => w.text.trim()).join(\" \");\n\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = \"\"; // Nothing before yet\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = allWords; // All words are upcoming\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n } else {\n // After last word ends - show all completed words in \"before\" container\n const allCompletedWords = segmentWords\n .map((w) => w.text.trim())\n .join(\" \");\n\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = allCompletedWords;\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n }\n } else {\n // No active segment or word - clear all context containers\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = 0;\n container.segmentEndMs = 0;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = 0;\n container.segmentEndMs = 0;\n container.requestUpdate();\n }\n }\n }\n\n get targetElement() {\n const target = document.getElementById(this.targetSelector ?? \"\");\n if (target instanceof EFAudio || target instanceof EFVideo) {\n return target;\n }\n // When using custom captions data, a target is not required\n if (this.hasCustomCaptionsData) {\n return null;\n }\n return null;\n }\n\n get hasCustomCaptionsData(): boolean {\n return !!(this.captionsData || this.captionsSrc || this.captionsScript);\n }\n\n // Follow the exact EFMedia pattern for safe duration integration\n get intrinsicDurationMs(): number | undefined {\n // Direct access to custom captions data - avoiding complex task dependencies\n // Priority: direct data > script reference > external file\n let captionsData: Caption | null = null;\n\n if (this.captionsData) {\n captionsData = this.captionsData;\n } else if (this.captionsScript) {\n const scriptElement = document.getElementById(this.captionsScript);\n if (scriptElement?.textContent) {\n try {\n captionsData = JSON.parse(scriptElement.textContent) as Caption;\n } catch {\n // Invalid JSON, fall through to return undefined\n }\n }\n } else if (this.customCaptionsDataTask.value) {\n captionsData = this.customCaptionsDataTask.value as Caption;\n }\n\n if (!captionsData) {\n return undefined;\n }\n\n if (\n captionsData.segments.length === 0 &&\n captionsData.word_segments.length === 0\n ) {\n return 0;\n }\n\n // Find the maximum end time from both segments and word_segments\n const maxSegmentEnd =\n captionsData.segments.length > 0\n ? Math.max(...captionsData.segments.map((s) => s.end))\n : 0;\n const maxWordEnd =\n captionsData.word_segments.length > 0\n ? Math.max(...captionsData.word_segments.map((w) => w.end))\n : 0;\n\n return Math.max(maxSegmentEnd, maxWordEnd) * 1000; // Convert to milliseconds\n }\n\n // Follow the exact EFMedia pattern for safe duration integration\n get hasOwnDuration(): boolean {\n // Simple check - if we have any form of custom captions data, we have our own duration\n return !!(\n this.captionsData ||\n this.captionsScript ||\n this.customCaptionsDataTask.value\n );\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions\": EFCaptions;\n \"ef-captions-active-word\": EFCaptionsActiveWord;\n \"ef-captions-segment\": EFCaptionsSegment;\n \"ef-captions-before-active-word\": EFCaptionsBeforeActiveWord;\n \"ef-captions-after-active-word\": EFCaptionsAfterActiveWord;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFCaptions.js","names":["EFCaptionsActiveWord","EFCaptionsSegment","EFCaptionsBeforeActiveWord","EFCaptionsAfterActiveWord","EFCaptions","#rootTimegroupUpdateController","#cachedIntrinsicDurationMs","captionsData: Caption | null","result: number"],"sources":["../../src/elements/EFCaptions.ts"],"sourcesContent":["import { Task, TaskStatus } from \"@lit/task\";\nimport { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport type { ReactiveController } from \"lit\";\nimport type { GetISOBMFFFileTranscriptionResult } from \"../../../api/src/index.js\";\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { CrossUpdateController } from \"./CrossUpdateController.js\";\nimport { EFAudio } from \"./EFAudio.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal, flushStartTimeMsCache } from \"./EFTemporal.js\";\nimport {\n flushSequenceDurationCache,\n EFTimegroup,\n} from \"./EFTimegroup.js\";\nimport { EFVideo } from \"./EFVideo.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\nexport interface WordSegment {\n text: string;\n start: number;\n end: number;\n}\n\nexport interface Segment {\n start: number;\n end: number;\n text: string;\n}\n\nexport interface Caption {\n segments: Segment[];\n word_segments: WordSegment[];\n}\n\nconst stopWords = new Set([\"\", \".\", \"!\", \"?\", \",\"]);\n\n@customElement(\"ef-captions-active-word\")\nexport class EFCaptionsActiveWord extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: normal;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.wordText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Set deterministic --ef-word-seed value based on word index\n const seed = (this.wordIndex * 9007) % 233; // Prime numbers for better distribution\n const seedValue = seed / 233; // Normalize to 0-1 range\n this.style.setProperty(\"--ef-word-seed\", seedValue.toString());\n\n return html`${this.wordText}`;\n }\n\n @property({ type: Number, attribute: false })\n wordStartMs = 0;\n\n @property({ type: Number, attribute: false })\n wordEndMs = 0;\n\n @property({ type: String, attribute: false })\n wordText = \"\";\n\n @property({ type: Number, attribute: false })\n wordIndex = 0;\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.wordStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.wordEndMs || 0);\n }\n\n get durationMs(): number {\n return this.wordEndMs - this.wordStartMs;\n }\n}\n\n@customElement(\"ef-captions-segment\")\nexport class EFCaptionsSegment extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: normal;\n line-height: 1;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n return html`${this.segmentText}`;\n }\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions-before-active-word\")\nexport class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Check if there's an active word by looking for sibling active word element\n const activeWord = this.closest(\"ef-captions\")?.querySelector(\n \"ef-captions-active-word\",\n );\n const hasActiveWord = activeWord?.wordText && !activeWord.hidden;\n\n return html`${this.segmentText}${hasActiveWord ? \" \" : \"\"}`;\n }\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions-after-active-word\")\nexport class EFCaptionsAfterActiveWord extends EFCaptionsSegment {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n if (stopWords.has(this.segmentText)) {\n this.hidden = true;\n return undefined;\n }\n this.hidden = false;\n\n // Check if there's an active word by looking for sibling active word element\n const activeWord = this.closest(\"ef-captions\")?.querySelector(\n \"ef-captions-active-word\",\n );\n const hasActiveWord = activeWord?.wordText && !activeWord.hidden;\n\n return html`${hasActiveWord ? \" \" : \"\"}${this.segmentText}`;\n }\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n get startTimeMs() {\n // Get parent captions element's absolute start time, then add our local offset\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentCaptions = this.closest(\"ef-captions\") as EFCaptions;\n const parentStartTime = parentCaptions?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\n@customElement(\"ef-captions\")\nexport class EFCaptions extends EFSourceMixin(\n EFTemporal(FetchMixin(LitElement)),\n { assetType: \"caption_files\" },\n) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n\n @property({ type: String, attribute: \"target\", reflect: true })\n targetSelector = \"\";\n\n set target(value: string) {\n this.targetSelector = value;\n }\n\n @property({ attribute: \"word-style\" })\n wordStyle = \"\";\n\n /**\n * URL or path to a JSON file containing custom captions data.\n * The JSON should conform to the Caption interface with 'segments' and 'word_segments' arrays.\n */\n @property({ type: String, attribute: \"captions-src\", reflect: true })\n captionsSrc = \"\";\n\n /**\n * Direct captions data object. Takes priority over captions-src and captions-script.\n * Should conform to the Caption interface with 'segments' and 'word_segments' arrays.\n */\n @property({ type: Object, attribute: false })\n captionsData: Caption | null = null;\n\n /**\n * ID of a <script> element containing JSON captions data.\n * The script's textContent should be valid JSON conforming to the Caption interface.\n */\n @property({ type: String, attribute: \"captions-script\", reflect: true })\n captionsScript = \"\";\n\n activeWordContainers = this.getElementsByTagName(\"ef-captions-active-word\");\n segmentContainers = this.getElementsByTagName(\"ef-captions-segment\");\n beforeActiveWordContainers = this.getElementsByTagName(\n \"ef-captions-before-active-word\",\n );\n afterActiveWordContainers = this.getElementsByTagName(\n \"ef-captions-after-active-word\",\n );\n\n // Cache for intrinsicDurationMs to avoid expensive O(n) recalculation every frame\n #cachedIntrinsicDurationMs: number | undefined | null = null; // null = not computed, undefined = no duration\n\n render() {\n return html`<slot></slot>`;\n }\n\n transcriptionsPath() {\n if (!this.targetElement) {\n return null;\n }\n if (this.targetElement.assetId) {\n return `${this.apiHost}/api/v1/isobmff_files/${this.targetElement.assetId}/transcription`;\n }\n return null;\n }\n\n captionsPath() {\n if (!this.targetElement) {\n return null;\n }\n if (this.targetElement.assetId) {\n return `${this.apiHost}/api/v1/caption_files/${this.targetElement.assetId}`;\n }\n const targetSrc = this.targetElement.src;\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = targetSrc.startsWith(\"/\")\n ? targetSrc.slice(1)\n : targetSrc;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n return `/api/v1/assets/local/captions?src=${encodeURIComponent(normalizedSrc)}`;\n }\n\n protected md5SumLoader = new Task(this, {\n autoRun: false,\n args: () => [this.target, this.fetch] as const,\n task: async ([_target, fetch], { signal }) => {\n if (!this.targetElement) {\n return null;\n }\n // Normalize the path: remove leading slash and any double slashes\n const src = this.targetElement.src ?? \"\";\n let normalizedSrc = src.startsWith(\"/\")\n ? src.slice(1)\n : src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n const md5Path = `/api/v1/isobmff_files/local/md5?src=${encodeURIComponent(normalizedSrc)}`;\n const response = await fetch(md5Path, { signal });\n if (!response.ok) {\n return undefined;\n }\n const data = await response.json();\n return data.md5 ?? undefined;\n },\n });\n\n private transcriptionDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.transcriptionsPath(),\n this.fetch,\n this.hasCustomCaptionsData,\n ] as const,\n task: async ([transcriptionsPath, fetch, hasCustomData], { signal }) => {\n // Skip transcription if we have custom captions data\n if (hasCustomData || !transcriptionsPath) {\n return null;\n }\n const response = await fetch(transcriptionsPath, { signal });\n return response.json() as any as GetISOBMFFFileTranscriptionResult;\n },\n });\n\n private transcriptionFragmentPath(\n transcriptionId: string,\n fragmentIndex: number,\n ) {\n return `${this.apiHost}/api/v1/transcriptions/${transcriptionId}/fragments/${fragmentIndex}`;\n }\n\n private fragmentIndexTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [this.transcriptionDataTask.value, this.ownCurrentTimeMs] as const,\n task: async ([transcription, ownCurrentTimeMs], { signal }) => {\n signal?.throwIfAborted();\n if (!transcription) {\n return null;\n }\n const fragmentIndex = Math.floor(\n ownCurrentTimeMs / transcription.work_slice_ms,\n );\n return fragmentIndex;\n },\n });\n\n private customCaptionsDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.captionsSrc,\n this.captionsData,\n this.captionsScript,\n this.fetch,\n ] as const,\n task: async (\n [captionsSrc, captionsData, captionsScript, fetch],\n { signal },\n ) => {\n // Priority: direct data > script reference > URL source\n if (captionsData) {\n return captionsData;\n }\n\n if (captionsScript) {\n const scriptElement = document.getElementById(captionsScript);\n if (scriptElement?.textContent) {\n try {\n return JSON.parse(scriptElement.textContent) as Caption;\n } catch (error) {\n console.error(\n `Failed to parse captions from script #${captionsScript}:`,\n error,\n );\n return null;\n }\n }\n }\n\n if (captionsSrc) {\n try {\n const response = await fetch(captionsSrc, { signal });\n return (await response.json()) as Caption;\n } catch (error) {\n console.error(`Failed to load captions from ${captionsSrc}:`, error);\n return null;\n }\n }\n\n return null;\n },\n });\n\n private transcriptionFragmentDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.transcriptionDataTask.value,\n this.fragmentIndexTask.value,\n this.fetch,\n ] as const,\n task: async ([transcription, fragmentIndex, fetch], { signal }) => {\n if (\n transcription === null ||\n transcription === undefined ||\n fragmentIndex === null ||\n fragmentIndex === undefined\n ) {\n return null;\n }\n const fragmentPath = this.transcriptionFragmentPath(\n transcription.id,\n fragmentIndex,\n );\n const response = await fetch(fragmentPath, { signal });\n return response.json() as any as Caption;\n },\n });\n\n unifiedCaptionsDataTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () =>\n [\n this.customCaptionsDataTask.value,\n this.transcriptionFragmentDataTask.value,\n ] as const,\n task: async ([_customData, _transcriptionData], { signal }) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n if (this.customCaptionsDataTask.status === TaskStatus.PENDING) {\n await this.customCaptionsDataTask.taskComplete;\n // Check abort after async operation\n signal?.throwIfAborted();\n }\n if (this.transcriptionFragmentDataTask.status === TaskStatus.PENDING) {\n await this.transcriptionFragmentDataTask.taskComplete;\n // Check abort after async operation\n signal?.throwIfAborted();\n }\n return (\n this.customCaptionsDataTask.value ||\n this.transcriptionFragmentDataTask.value\n );\n },\n });\n\n frameTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.unifiedCaptionsDataTask.status, this.ownCurrentTimeMs],\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to prevent unhandled rejection\n this.frameTask.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected 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 (isAbortError) {\n return;\n }\n console.error(\"EFCaptions frameTask error\", error);\n },\n task: async ([_status, _ownCurrentTimeMs], { signal }) => {\n try {\n await this.unifiedCaptionsDataTask.taskComplete;\n } catch (error) {\n // Handle AbortError from dependent task gracefully\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n // Trigger updateTextContainers when data is ready or time changes\n this.updateTextContainers();\n },\n });\n\n #rootTimegroupUpdateController?: ReactiveController;\n\n connectedCallback() {\n super.connectedCallback();\n\n // Try to get target element safely\n const target = this.targetSelector\n ? document.getElementById(this.targetSelector)\n : null;\n if (target && (target instanceof EFAudio || target instanceof EFVideo)) {\n new CrossUpdateController(target, this);\n }\n // For standalone captions with custom data, ensure proper timeline sync\n else if (this.hasCustomCaptionsData && this.rootTimegroup) {\n new CrossUpdateController(this.rootTimegroup, this);\n }\n\n // Ensure captions update when root timegroup's currentTimeMs changes\n // This is critical for sequence mode where timegroups become inactive\n // and then active again when scrubbing\n if (this.rootTimegroup) {\n this.#rootTimegroupUpdateController = {\n hostUpdated: () => {\n // Always update captions when root timegroup updates\n // ownCurrentTimeMs is a getter that reads rootTimegroup.currentTimeMs,\n // so it will always read the latest value when updateTextContainers() is called\n // This ensures captions update even when ownCurrentTimeMs appears\n // unchanged due to clamping in inactive timegroups\n // Use microtask to ensure root timegroup's update completes first\n Promise.resolve().then(() => {\n this.updateTextContainers();\n });\n },\n hostDisconnected: () => {\n this.#rootTimegroupUpdateController = undefined;\n },\n };\n this.rootTimegroup.addController(this.#rootTimegroupUpdateController);\n }\n\n // Prevent display:none from being set on caption elements\n // This maintains constant width in the parent flex container\n const observer = new MutationObserver(() => {\n if (this.style.display === \"none\") {\n // Remove the display:none and use opacity instead\n this.style.removeProperty(\"display\");\n this.style.opacity = \"0\";\n this.style.pointerEvents = \"none\";\n } else if (!this.style.display || this.style.display === \"\") {\n // When display is removed (element becomes visible), reset opacity\n this.style.removeProperty(\"opacity\");\n this.style.removeProperty(\"pointer-events\");\n }\n });\n observer.observe(this, { attributes: true, attributeFilter: [\"style\"] });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.#rootTimegroupUpdateController && this.rootTimegroup) {\n this.rootTimegroup.removeController(this.#rootTimegroupUpdateController);\n this.#rootTimegroupUpdateController = undefined;\n }\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n // Set up root timegroup controller if rootTimegroup is now available\n // (it might not have been available in connectedCallback)\n if (this.rootTimegroup && !this.#rootTimegroupUpdateController) {\n this.#rootTimegroupUpdateController = {\n hostUpdated: () => {\n // Always update captions when root timegroup updates\n // ownCurrentTimeMs is a getter that reads rootTimegroup.currentTimeMs,\n // so it will always read the latest value when updateTextContainers() is called\n // This ensures captions update even when ownCurrentTimeMs appears\n // unchanged due to clamping in inactive timegroups\n // Use microtask to ensure root timegroup's update completes first\n Promise.resolve().then(() => {\n this.updateTextContainers();\n });\n },\n hostDisconnected: () => {\n this.#rootTimegroupUpdateController = undefined;\n },\n };\n this.rootTimegroup.addController(this.#rootTimegroupUpdateController);\n }\n\n // Clean up controller if rootTimegroup changed\n if (\n changedProperties.has(\"rootTimegroup\") &&\n this.#rootTimegroupUpdateController\n ) {\n const oldRootTimegroup = changedProperties.get(\"rootTimegroup\") as\n | EFTimegroup\n | undefined;\n if (oldRootTimegroup && oldRootTimegroup !== this.rootTimegroup) {\n oldRootTimegroup.removeController(this.#rootTimegroupUpdateController);\n this.#rootTimegroupUpdateController = undefined;\n }\n }\n\n this.updateTextContainers();\n\n // Force duration recalculation when custom captions data changes\n if (\n changedProperties.has(\"captionsData\") ||\n changedProperties.has(\"captionsSrc\") ||\n changedProperties.has(\"captionsScript\")\n ) {\n // Invalidate the intrinsicDurationMs cache\n this.#cachedIntrinsicDurationMs = null;\n this.requestUpdate(\"intrinsicDurationMs\");\n\n // Flush sequence duration cache and notify parent timegroups that child duration has changed\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Notify parent timegroup to recalculate its duration\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n }\n }\n\n // Update captions when timeline position changes\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.updateTextContainers();\n }\n }\n\n updateTextContainers() {\n const captionsData = this.unifiedCaptionsDataTask.value as Caption;\n if (!captionsData) {\n return;\n }\n\n // For captions with custom data, try to use the video's source time\n // This ensures correct timing when clips don't start at the beginning of the source video\n let currentTimeMs = this.ownCurrentTimeMs;\n if (this.hasCustomCaptionsData && this.parentTimegroup) {\n // Find video element in the same parent timegroup\n const videoElement = Array.from(this.parentTimegroup.children).find(\n (child): child is EFVideo => child instanceof EFVideo,\n );\n if (videoElement) {\n const sourceInMs = videoElement.sourceInMs ?? 0;\n // Use video's source time minus sourceIn to get time relative to clip start\n // This matches the adjusted captions data timestamps (which are relative to clip.start)\n currentTimeMs = videoElement.currentSourceTimeMs - sourceInMs;\n // Clamp to valid range\n currentTimeMs = Math.max(0, Math.min(currentTimeMs, this.durationMs));\n }\n }\n\n const currentTimeSec = currentTimeMs / 1000;\n\n // Find the current word from word_segments\n // Use exclusive end boundary to prevent overlap at exact boundaries\n const currentWord = captionsData.word_segments.find(\n (word) => currentTimeSec >= word.start && currentTimeSec < word.end,\n );\n\n // Find the current segment\n // Use exclusive end boundary to prevent overlap at exact boundaries\n const currentSegment = captionsData.segments.find(\n (segment) =>\n currentTimeSec >= segment.start && currentTimeSec < segment.end,\n );\n\n for (const wordContainer of this.activeWordContainers) {\n if (currentWord) {\n wordContainer.wordText = currentWord.text;\n wordContainer.wordStartMs = currentWord.start * 1000;\n wordContainer.wordEndMs = currentWord.end * 1000;\n // Set word index for deterministic animation variation\n const wordIndex = captionsData.word_segments.findIndex(\n (w) =>\n w.start === currentWord.start &&\n w.end === currentWord.end &&\n w.text === currentWord.text,\n );\n wordContainer.wordIndex = wordIndex >= 0 ? wordIndex : 0;\n // Force re-render to update hidden property\n wordContainer.requestUpdate();\n } else {\n // No active word - maintain layout with invisible placeholder\n wordContainer.wordText = \"\"; // Empty when no active word\n wordContainer.wordStartMs = 0;\n wordContainer.wordEndMs = 0;\n wordContainer.requestUpdate();\n }\n }\n\n for (const segmentContainer of this.segmentContainers) {\n if (currentSegment) {\n segmentContainer.segmentText = currentSegment.text;\n segmentContainer.segmentStartMs = currentSegment.start * 1000;\n segmentContainer.segmentEndMs = currentSegment.end * 1000;\n } else {\n // No active segment - clear the container\n segmentContainer.segmentText = \"\";\n segmentContainer.segmentStartMs = 0;\n segmentContainer.segmentEndMs = 0;\n }\n segmentContainer.requestUpdate();\n }\n\n // Process context for both word and segment cases\n if (currentWord && currentSegment) {\n // Find all word segments that fall within the current segment's time range\n const segmentWords = captionsData.word_segments.filter(\n (word) =>\n word.start >= currentSegment.start && word.end <= currentSegment.end,\n );\n\n // Find the index of the current word within the segment's word segments\n const currentWordIndex = segmentWords.findIndex(\n (word) =>\n word.start === currentWord.start && word.end === currentWord.end,\n );\n\n if (currentWordIndex !== -1) {\n // Get words before the current word\n const beforeWords = segmentWords\n .slice(0, currentWordIndex)\n .map((w) => w.text.trim())\n .join(\" \");\n\n // Get words after the current word\n const afterWords = segmentWords\n .slice(currentWordIndex + 1)\n .map((w) => w.text.trim())\n .join(\" \");\n\n // Update before containers - should be visible at the same time as active word\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = beforeWords;\n container.segmentStartMs = currentWord.start * 1000;\n container.segmentEndMs = currentWord.end * 1000;\n container.requestUpdate();\n }\n\n // Update after containers - should be visible at the same time as active word\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = afterWords;\n container.segmentStartMs = currentWord.start * 1000;\n container.segmentEndMs = currentWord.end * 1000;\n container.requestUpdate();\n }\n }\n } else if (currentSegment) {\n // No active word but we have an active segment\n const segmentWords = captionsData.word_segments.filter(\n (word) =>\n word.start >= currentSegment.start && word.end <= currentSegment.end,\n );\n\n // Check if we're before the first word or after the last word\n const firstWord = segmentWords[0];\n const isBeforeFirstWord = firstWord && currentTimeSec < firstWord.start;\n\n if (isBeforeFirstWord) {\n // Before first word starts - show all words in \"after\" container (they're all upcoming)\n const allWords = segmentWords.map((w) => w.text.trim()).join(\" \");\n\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = \"\"; // Nothing before yet\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = allWords; // All words are upcoming\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n } else {\n // After last word ends - show all completed words in \"before\" container\n const allCompletedWords = segmentWords\n .map((w) => w.text.trim())\n .join(\" \");\n\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = allCompletedWords;\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = currentSegment.start * 1000;\n container.segmentEndMs = currentSegment.end * 1000;\n container.requestUpdate();\n }\n }\n } else {\n // No active segment or word - clear all context containers\n for (const container of this.beforeActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = 0;\n container.segmentEndMs = 0;\n container.requestUpdate();\n }\n\n for (const container of this.afterActiveWordContainers) {\n container.segmentText = \"\";\n container.segmentStartMs = 0;\n container.segmentEndMs = 0;\n container.requestUpdate();\n }\n }\n }\n\n get targetElement() {\n const target = document.getElementById(this.targetSelector ?? \"\");\n if (target instanceof EFAudio || target instanceof EFVideo) {\n return target;\n }\n // When using custom captions data, a target is not required\n if (this.hasCustomCaptionsData) {\n return null;\n }\n return null;\n }\n\n get hasCustomCaptionsData(): boolean {\n return !!(this.captionsData || this.captionsSrc || this.captionsScript);\n }\n\n // Follow the exact EFMedia pattern for safe duration integration\n get intrinsicDurationMs(): number | undefined {\n // Return cached value if available (null means not computed yet)\n if (this.#cachedIntrinsicDurationMs !== null) {\n return this.#cachedIntrinsicDurationMs;\n }\n\n // Direct access to custom captions data - avoiding complex task dependencies\n // Priority: direct data > script reference > external file\n let captionsData: Caption | null = null;\n\n if (this.captionsData) {\n captionsData = this.captionsData;\n } else if (this.captionsScript) {\n const scriptElement = document.getElementById(this.captionsScript);\n if (scriptElement?.textContent) {\n try {\n captionsData = JSON.parse(scriptElement.textContent) as Caption;\n } catch {\n // Invalid JSON, fall through to return undefined\n }\n }\n } else if (this.customCaptionsDataTask.value) {\n captionsData = this.customCaptionsDataTask.value as Caption;\n }\n\n if (!captionsData) {\n // Don't cache undefined when data hasn't loaded yet - it may load later\n // Only cache once we have confirmed no data source\n if (!this.captionsData && !this.captionsScript && !this.captionsSrc) {\n this.#cachedIntrinsicDurationMs = undefined;\n }\n return undefined;\n }\n\n let result: number;\n if (\n captionsData.segments.length === 0 &&\n captionsData.word_segments.length === 0\n ) {\n result = 0;\n } else {\n // Find the maximum end time from both segments and word_segments\n // Use reduce instead of Math.max(...array) to avoid creating intermediate arrays\n const maxSegmentEnd =\n captionsData.segments.length > 0\n ? captionsData.segments.reduce(\n (max, s) => (s.end > max ? s.end : max),\n 0,\n )\n : 0;\n const maxWordEnd =\n captionsData.word_segments.length > 0\n ? captionsData.word_segments.reduce(\n (max, w) => (w.end > max ? w.end : max),\n 0,\n )\n : 0;\n\n result = Math.max(maxSegmentEnd, maxWordEnd) * 1000; // Convert to milliseconds\n }\n\n // Cache the computed result\n this.#cachedIntrinsicDurationMs = result;\n return result;\n }\n\n // Follow the exact EFMedia pattern for safe duration integration\n get hasOwnDuration(): boolean {\n // Simple check - if we have any form of custom captions data, we have our own duration\n return !!(\n this.captionsData ||\n this.captionsScript ||\n this.customCaptionsDataTask.value\n );\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions\": EFCaptions;\n \"ef-captions-active-word\": EFCaptionsActiveWord;\n \"ef-captions-segment\": EFCaptionsSegment;\n \"ef-captions-before-active-word\": EFCaptionsBeforeActiveWord;\n \"ef-captions-after-active-word\": EFCaptionsAfterActiveWord;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
2
1
|
import { EFSourceMixinInterface } from "./EFSourceMixin.js";
|
|
2
|
+
import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
3
3
|
import { FetchMixinInterface } from "./FetchMixin.js";
|
|
4
4
|
import * as _lit_task0 from "@lit/task";
|
|
5
5
|
import { Task } from "@lit/task";
|
|
6
|
-
import * as
|
|
6
|
+
import * as lit1 from "lit";
|
|
7
7
|
import { LitElement } from "lit";
|
|
8
|
-
import * as
|
|
8
|
+
import * as lit_html1 from "lit-html";
|
|
9
9
|
import * as lit_html_directives_ref_js0 from "lit-html/directives/ref.js";
|
|
10
10
|
|
|
11
11
|
//#region src/elements/EFImage.d.ts
|
|
12
12
|
declare const EFImage_base: (new (...args: any[]) => TemporalMixinInterface) & (new (...args: any[]) => EFSourceMixinInterface) & (new (...args: any[]) => FetchMixinInterface) & typeof LitElement;
|
|
13
13
|
declare class EFImage extends EFImage_base {
|
|
14
14
|
#private;
|
|
15
|
-
static styles:
|
|
15
|
+
static styles: lit1.CSSResult[];
|
|
16
16
|
imageRef: lit_html_directives_ref_js0.Ref<HTMLImageElement>;
|
|
17
17
|
canvasRef: lit_html_directives_ref_js0.Ref<HTMLCanvasElement>;
|
|
18
18
|
set assetId(value: string | null);
|
|
19
19
|
get assetId(): string | null;
|
|
20
|
-
render():
|
|
20
|
+
render(): lit_html1.TemplateResult<1>;
|
|
21
21
|
private isDirectUrl;
|
|
22
22
|
assetPath(): string;
|
|
23
23
|
get hasOwnDuration(): boolean;
|
|
24
|
-
fetchImage: Task<readonly [string, typeof fetch], void>;
|
|
24
|
+
fetchImage: Task<readonly [string, typeof fetch, string, string | null], void>;
|
|
25
25
|
frameTask: Task<readonly [_lit_task0.TaskStatus], void>;
|
|
26
|
+
/**
|
|
27
|
+
* Get the natural dimensions of the image.
|
|
28
|
+
* Returns null if the image hasn't loaded yet.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
getNaturalDimensions(): {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
} | null;
|
|
26
36
|
}
|
|
27
37
|
declare global {
|
|
28
38
|
interface HTMLElementTagNameMap {
|
package/dist/elements/EFImage.js
CHANGED
|
@@ -16,29 +16,78 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
16
16
|
this.canvasRef = createRef();
|
|
17
17
|
this.fetchImage = new Task(this, {
|
|
18
18
|
autoRun: EF_INTERACTIVE,
|
|
19
|
-
args: () => [
|
|
20
|
-
|
|
19
|
+
args: () => [
|
|
20
|
+
this.assetPath(),
|
|
21
|
+
this.fetch,
|
|
22
|
+
this.src,
|
|
23
|
+
this.assetId
|
|
24
|
+
],
|
|
25
|
+
onError: (error) => {
|
|
26
|
+
this.fetchImage.taskComplete.catch(() => {});
|
|
27
|
+
const isAbortError = error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"));
|
|
28
|
+
const isCanvasNotReady = error instanceof Error && error.message === "Canvas not ready";
|
|
29
|
+
if (isAbortError || isCanvasNotReady) return;
|
|
30
|
+
console.error("EFImage fetchImage error", error);
|
|
31
|
+
},
|
|
32
|
+
task: async ([assetPath, fetch, src, assetId], { signal }) => {
|
|
33
|
+
if (!src && !assetId) return;
|
|
21
34
|
if (this.isDirectUrl(assetPath)) return;
|
|
22
35
|
const response = await fetch(assetPath, { signal });
|
|
36
|
+
signal?.throwIfAborted();
|
|
23
37
|
const image = new Image();
|
|
24
|
-
|
|
38
|
+
const blob = await response.blob();
|
|
39
|
+
signal?.throwIfAborted();
|
|
40
|
+
image.src = URL.createObjectURL(blob);
|
|
25
41
|
await new Promise((resolve, reject) => {
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
if (signal?.aborted) {
|
|
43
|
+
URL.revokeObjectURL(image.src);
|
|
44
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const abortHandler = () => {
|
|
48
|
+
URL.revokeObjectURL(image.src);
|
|
49
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
50
|
+
};
|
|
51
|
+
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
52
|
+
image.onload = () => {
|
|
53
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
54
|
+
resolve();
|
|
55
|
+
};
|
|
56
|
+
image.onerror = (error) => {
|
|
57
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
58
|
+
URL.revokeObjectURL(image.src);
|
|
59
|
+
reject(error);
|
|
60
|
+
};
|
|
28
61
|
});
|
|
62
|
+
signal?.throwIfAborted();
|
|
29
63
|
if (!this.canvasRef.value) throw new Error("Canvas not ready");
|
|
30
64
|
const ctx = this.canvasRef.value.getContext("2d");
|
|
31
65
|
if (!ctx) throw new Error("Canvas 2d context not ready");
|
|
32
66
|
this.canvasRef.value.width = image.width;
|
|
33
67
|
this.canvasRef.value.height = image.height;
|
|
34
68
|
ctx.drawImage(image, 0, 0);
|
|
69
|
+
URL.revokeObjectURL(image.src);
|
|
35
70
|
}
|
|
36
71
|
});
|
|
37
72
|
this.frameTask = new Task(this, {
|
|
38
73
|
autoRun: EF_INTERACTIVE,
|
|
39
74
|
args: () => [this.fetchImage.status],
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
onError: (error) => {
|
|
76
|
+
this.frameTask.taskComplete.catch(() => {});
|
|
77
|
+
if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
|
|
78
|
+
console.error("EFImage frameTask error", error);
|
|
79
|
+
},
|
|
80
|
+
task: async ([_status], { signal }) => {
|
|
81
|
+
try {
|
|
82
|
+
await this.fetchImage.taskComplete;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
85
|
+
signal?.throwIfAborted();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
signal?.throwIfAborted();
|
|
42
91
|
}
|
|
43
92
|
});
|
|
44
93
|
}
|
|
@@ -70,16 +119,37 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
70
119
|
return this.isDirectUrl(assetPath) ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
71
120
|
}
|
|
72
121
|
isDirectUrl(src) {
|
|
73
|
-
return src.startsWith("http://") || src.startsWith("https://");
|
|
122
|
+
return src.startsWith("http://") || src.startsWith("https://") || src.startsWith("data:");
|
|
74
123
|
}
|
|
75
124
|
assetPath() {
|
|
76
125
|
if (this.assetId) return `${this.apiHost}/api/v1/image_files/${this.assetId}`;
|
|
77
126
|
if (this.isDirectUrl(this.src)) return this.src;
|
|
78
|
-
|
|
127
|
+
let normalizedSrc = this.src.startsWith("/") ? this.src.slice(1) : this.src;
|
|
128
|
+
normalizedSrc = normalizedSrc.replace(/^\/+/, "");
|
|
129
|
+
return `/api/v1/assets/local/image?src=${encodeURIComponent(normalizedSrc)}`;
|
|
79
130
|
}
|
|
80
131
|
get hasOwnDuration() {
|
|
81
132
|
return this.hasExplicitDuration;
|
|
82
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Get the natural dimensions of the image.
|
|
136
|
+
* Returns null if the image hasn't loaded yet.
|
|
137
|
+
*
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
getNaturalDimensions() {
|
|
141
|
+
const img = this.imageRef.value;
|
|
142
|
+
if (img && img.naturalWidth > 0 && img.naturalHeight > 0) return {
|
|
143
|
+
width: img.naturalWidth,
|
|
144
|
+
height: img.naturalHeight
|
|
145
|
+
};
|
|
146
|
+
const canvas = this.canvasRef.value;
|
|
147
|
+
if (canvas && canvas.width > 0 && canvas.height > 0) return {
|
|
148
|
+
width: canvas.width,
|
|
149
|
+
height: canvas.height
|
|
150
|
+
};
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
83
153
|
};
|
|
84
154
|
__decorate([property({
|
|
85
155
|
type: String,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFImage.js","names":["EFImage","#assetId"],"sources":["../../src/elements/EFImage.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\n@customElement(\"ef-image\")\nexport class EFImage extends EFTemporal(\n EFSourceMixin(FetchMixin(LitElement), {\n assetType: \"image_files\",\n }),\n) {\n static styles = [\n css`\n :host {\n display: block;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n canvas, img {\n position: static;\n all: initial;\n width: 100%;\n height: 100%;\n }\n `,\n ];\n\n imageRef = createRef<HTMLImageElement>();\n canvasRef = createRef<HTMLCanvasElement>();\n\n #assetId: string | null = null;\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n set assetId(value: string | null) {\n this.#assetId = value;\n }\n\n get assetId() {\n return this.#assetId ?? this.getAttribute(\"asset-id\");\n }\n\n render() {\n const assetPath = this.assetPath();\n const isDirectUrl = this.isDirectUrl(assetPath);\n return isDirectUrl\n ? html`<img ${ref(this.imageRef)} src=${assetPath} />`\n : html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n private isDirectUrl(src: string): boolean {\n return src.startsWith(\"http://\") || src.startsWith(\"https://\");\n }\n\n assetPath() {\n if (this.assetId) {\n return `${this.apiHost}/api/v1/image_files/${this.assetId}`;\n }\n if (this.isDirectUrl(this.src)) {\n return this.src;\n }\n return `/@ef-image/${this.src}`;\n }\n\n get hasOwnDuration() {\n return this.hasExplicitDuration;\n }\n\n fetchImage = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.assetPath(), this.fetch] as const,\n task: async ([assetPath, fetch], { signal }) => {\n // For direct URLs, skip task - src is set directly in render\n if (this.isDirectUrl(assetPath)) {\n return;\n }\n\n // For asset-id and local files, use canvas as before\n const response = await fetch(assetPath, { signal });\n const image = new Image();\n image.src = URL.createObjectURL(await response.blob());\n\n await new Promise((resolve, reject) => {\n image.onload = resolve;\n image.onerror = reject;\n });\n\n if (!this.canvasRef.value) throw new Error(\"Canvas not ready\");\n const ctx = this.canvasRef.value.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.canvasRef.value.width = image.width;\n this.canvasRef.value.height = image.height;\n ctx.drawImage(image, 0, 0);\n },\n });\n\n frameTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.fetchImage.status] as const,\n task: async () => {\n await this.fetchImage.taskComplete;\n },\n });\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image\": EFImage;\n }\n}\n"],"mappings":";;;;;;;;;;;AAUO,oBAAMA,kBAAgB,WAC3B,cAAc,WAAW,WAAW,EAAE,EACpC,WAAW,eACZ,CAAC,CACH,CAAC;;;kBAkBW,WAA6B;mBAC5B,WAA8B;oBAsC7B,IAAI,KAAK,MAAM;GAC1B,SAAS;GACT,YAAY,CAAC,KAAK,WAAW,EAAE,KAAK,MAAM;GAC1C,MAAM,OAAO,CAAC,WAAW,QAAQ,EAAE,aAAa;AAE9C,QAAI,KAAK,YAAY,UAAU,CAC7B;IAIF,MAAM,WAAW,MAAM,MAAM,WAAW,EAAE,QAAQ,CAAC;IACnD,MAAM,QAAQ,IAAI,OAAO;AACzB,UAAM,MAAM,IAAI,gBAAgB,MAAM,SAAS,MAAM,CAAC;AAEtD,UAAM,IAAI,SAAS,SAAS,WAAW;AACrC,WAAM,SAAS;AACf,WAAM,UAAU;MAChB;AAEF,QAAI,CAAC,KAAK,UAAU,MAAO,OAAM,IAAI,MAAM,mBAAmB;IAC9D,MAAM,MAAM,KAAK,UAAU,MAAM,WAAW,KAAK;AACjD,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,8BAA8B;AACxD,SAAK,UAAU,MAAM,QAAQ,MAAM;AACnC,SAAK,UAAU,MAAM,SAAS,MAAM;AACpC,QAAI,UAAU,OAAO,GAAG,EAAE;;GAE7B,CAAC;mBAEU,IAAI,KAAK,MAAM;GACzB,SAAS;GACT,YAAY,CAAC,KAAK,WAAW,OAAO;GACpC,MAAM,YAAY;AAChB,UAAM,KAAK,WAAW;;GAEzB,CAAC;;;gBA1Fc,CACd,GAAG;;;;;;;;;;;;;MAcJ;;CAKD,WAA0B;CAC1B,IACI,QAAQ,OAAsB;AAChC,QAAKC,UAAW;;CAGlB,IAAI,UAAU;AACZ,SAAO,MAAKA,WAAY,KAAK,aAAa,WAAW;;CAGvD,SAAS;EACP,MAAM,YAAY,KAAK,WAAW;AAElC,SADoB,KAAK,YAAY,UAAU,GAE3C,IAAI,QAAQ,IAAI,KAAK,SAAS,CAAC,OAAO,UAAU,OAChD,IAAI,WAAW,IAAI,KAAK,UAAU,CAAC;;CAGzC,AAAQ,YAAY,KAAsB;AACxC,SAAO,IAAI,WAAW,UAAU,IAAI,IAAI,WAAW,WAAW;;CAGhE,YAAY;AACV,MAAI,KAAK,QACP,QAAO,GAAG,KAAK,QAAQ,sBAAsB,KAAK;AAEpD,MAAI,KAAK,YAAY,KAAK,IAAI,CAC5B,QAAO,KAAK;AAEd,SAAO,cAAc,KAAK;;CAG5B,IAAI,iBAAiB;AACnB,SAAO,KAAK;;;YAhCb,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAY,SAAS;CAAM,CAAC;sBA3BlE,cAAc,WAAW"}
|
|
1
|
+
{"version":3,"file":"EFImage.js","names":["EFImage","#assetId"],"sources":["../../src/elements/EFImage.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\n@customElement(\"ef-image\")\nexport class EFImage extends EFTemporal(\n EFSourceMixin(FetchMixin(LitElement), {\n assetType: \"image_files\",\n }),\n) {\n static styles = [\n css`\n :host {\n display: block;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n canvas, img {\n position: static;\n all: initial;\n width: 100%;\n height: 100%;\n }\n `,\n ];\n\n imageRef = createRef<HTMLImageElement>();\n canvasRef = createRef<HTMLCanvasElement>();\n\n #assetId: string | null = null;\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n set assetId(value: string | null) {\n this.#assetId = value;\n }\n\n get assetId() {\n return this.#assetId ?? this.getAttribute(\"asset-id\");\n }\n\n render() {\n const assetPath = this.assetPath();\n const isDirectUrl = this.isDirectUrl(assetPath);\n return isDirectUrl\n ? html`<img ${ref(this.imageRef)} src=${assetPath} />`\n : html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n private isDirectUrl(src: string): boolean {\n return src.startsWith(\"http://\") || src.startsWith(\"https://\") || src.startsWith(\"data:\");\n }\n\n assetPath() {\n if (this.assetId) {\n return `${this.apiHost}/api/v1/image_files/${this.assetId}`;\n }\n if (this.isDirectUrl(this.src)) {\n return this.src;\n }\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = this.src.startsWith(\"/\")\n ? this.src.slice(1)\n : this.src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n return `/api/v1/assets/local/image?src=${encodeURIComponent(normalizedSrc)}`;\n }\n\n get hasOwnDuration() {\n return this.hasExplicitDuration;\n }\n\n fetchImage = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.assetPath(), this.fetch, this.src, this.assetId] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to prevent unhandled rejection\n this.fetchImage.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected 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 // Also ignore \"Canvas not ready\" errors - happens during element lifecycle\n const isCanvasNotReady = error instanceof Error && error.message === \"Canvas not ready\";\n \n if (isAbortError || isCanvasNotReady) {\n return;\n }\n console.error(\"EFImage fetchImage error\", error);\n },\n task: async ([assetPath, fetch, src, assetId], { signal }) => {\n // Skip if no source is set\n if (!src && !assetId) {\n return;\n }\n\n // For direct URLs, skip task - src is set directly in render\n if (this.isDirectUrl(assetPath)) {\n return;\n }\n\n // For asset-id and local files, use canvas as before\n const response = await fetch(assetPath, { signal });\n // Check abort after fetch\n signal?.throwIfAborted();\n \n const image = new Image();\n const blob = await response.blob();\n // Check abort after blob conversion\n signal?.throwIfAborted();\n \n image.src = URL.createObjectURL(blob);\n\n await new Promise<void>((resolve, reject) => {\n // Check abort before setting up image load handlers\n if (signal?.aborted) {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", abortHandler, { once: true });\n \n image.onload = () => {\n signal?.removeEventListener(\"abort\", abortHandler);\n resolve();\n };\n image.onerror = (error) => {\n signal?.removeEventListener(\"abort\", abortHandler);\n URL.revokeObjectURL(image.src);\n reject(error);\n };\n });\n\n // Check abort after image load\n signal?.throwIfAborted();\n\n if (!this.canvasRef.value) throw new Error(\"Canvas not ready\");\n const ctx = this.canvasRef.value.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.canvasRef.value.width = image.width;\n this.canvasRef.value.height = image.height;\n ctx.drawImage(image, 0, 0);\n \n // Clean up object URL after use\n URL.revokeObjectURL(image.src);\n },\n });\n\n frameTask = new Task(this, {\n autoRun: EF_INTERACTIVE,\n args: () => [this.fetchImage.status] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to prevent unhandled rejection\n this.frameTask.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected 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 (isAbortError) {\n return;\n }\n console.error(\"EFImage frameTask error\", error);\n },\n task: async ([_status], { signal }) => {\n try {\n await this.fetchImage.taskComplete;\n } catch (error) {\n // Handle AbortError from fetchImage gracefully\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return;\n }\n throw error;\n }\n signal?.throwIfAborted();\n },\n });\n\n /**\n * Get the natural dimensions of the image.\n * Returns null if the image hasn't loaded yet.\n *\n * @public\n */\n getNaturalDimensions(): { width: number; height: number } | null {\n // For direct URLs, check img element\n const img = this.imageRef.value;\n if (img && img.naturalWidth > 0 && img.naturalHeight > 0) {\n return {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n\n // For canvas-based images, check canvas dimensions\n const canvas = this.canvasRef.value;\n if (canvas && canvas.width > 0 && canvas.height > 0) {\n return {\n width: canvas.width,\n height: canvas.height,\n };\n }\n\n return null;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image\": EFImage;\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,12 +2,59 @@ import { AssetMediaEngine } from "./AssetMediaEngine.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/elements/EFMedia/AssetIdMediaEngine.ts
|
|
4
4
|
var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
|
|
5
|
-
static async fetchByAssetId(host, _urlGenerator, assetId, apiHost) {
|
|
5
|
+
static async fetchByAssetId(host, _urlGenerator, assetId, apiHost, requiredTracks = "both", signal) {
|
|
6
6
|
const url = `${apiHost}/api/v1/isobmff_files/${assetId}/index`;
|
|
7
|
-
|
|
7
|
+
const response = await host.fetch(url, { signal });
|
|
8
|
+
signal?.throwIfAborted();
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
const text = await response.text();
|
|
11
|
+
throw new Error(`Failed to fetch asset index: ${response.status} ${text}`);
|
|
12
|
+
}
|
|
13
|
+
const contentType = response.headers.get("content-type");
|
|
14
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
15
|
+
const text = await response.text();
|
|
16
|
+
throw new Error(`Expected JSON but got ${contentType}: ${text.substring(0, 100)}`);
|
|
17
|
+
}
|
|
18
|
+
let data;
|
|
19
|
+
try {
|
|
20
|
+
data = await response.json();
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
throw new Error(`Failed to parse JSON response: ${text.substring(0, 100)}`);
|
|
26
|
+
}
|
|
27
|
+
const engine = new AssetIdMediaEngine(host, assetId, data, apiHost, _urlGenerator);
|
|
28
|
+
signal?.throwIfAborted();
|
|
29
|
+
if (signal) {
|
|
30
|
+
const videoTrack = engine.videoTrackIndex;
|
|
31
|
+
const audioTrack = engine.audioTrackIndex;
|
|
32
|
+
const needsVideo = requiredTracks === "video" || requiredTracks === "both";
|
|
33
|
+
const needsAudio = requiredTracks === "audio" || requiredTracks === "both";
|
|
34
|
+
if (needsVideo && videoTrack && videoTrack.track !== void 0) try {
|
|
35
|
+
await engine.fetchInitSegment({
|
|
36
|
+
trackId: videoTrack.track,
|
|
37
|
+
src: engine.src
|
|
38
|
+
}, signal);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
41
|
+
if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") && error.message.includes("401"))) throw new Error(`Video segments require authentication: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
if (needsAudio && audioTrack && audioTrack.track !== void 0) try {
|
|
45
|
+
await engine.fetchInitSegment({
|
|
46
|
+
trackId: audioTrack.track,
|
|
47
|
+
src: engine.src
|
|
48
|
+
}, signal);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
51
|
+
if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") && error.message.includes("401"))) throw new Error(`Audio segments require authentication: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return engine;
|
|
8
55
|
}
|
|
9
|
-
constructor(host, assetId, data, apiHost) {
|
|
10
|
-
super(host, assetId);
|
|
56
|
+
constructor(host, assetId, data, apiHost, urlGenerator) {
|
|
57
|
+
super(host, assetId, urlGenerator);
|
|
11
58
|
this.assetId = assetId;
|
|
12
59
|
this.apiHost = apiHost;
|
|
13
60
|
this.data = data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetIdMediaEngine.js","names":["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 ) {\n const url = `${apiHost}/api/v1/isobmff_files/${assetId}/index`;\n const response = await host.fetch(url);\n const data = (await response.json()) as Record<number, TrackFragmentIndex>;\n return new AssetIdMediaEngine(host, assetId, data, apiHost);\n }\n\n constructor(\n host: EFMedia,\n public assetId: string,\n data: Record<number, TrackFragmentIndex>,\n private apiHost: string,\n ) {\n // Pass assetId as src to parent constructor for compatibility\n super(host, assetId);\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\n // Override URL-building methods to use API endpoints instead of file paths\n get initSegmentPaths() {\n const paths: InitSegmentPaths = {};\n\n if (this.audioTrackIndex !== undefined) {\n paths.audio = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${this.audioTrackIndex.track}`,\n pos: this.audioTrackIndex.initSegment.offset,\n size: this.audioTrackIndex.initSegment.size,\n };\n }\n\n if (this.videoTrackIndex !== undefined) {\n paths.video = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${this.videoTrackIndex.track}`,\n pos: this.videoTrackIndex.initSegment.offset,\n size: this.videoTrackIndex.initSegment.size,\n };\n }\n\n return paths;\n }\n\n get templates() {\n return {\n initSegment: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/{trackId}`,\n mediaSegment: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/{trackId}`,\n };\n }\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;EACA,MAAM,MAAM,GAAG,QAAQ,wBAAwB,QAAQ;AAGvD,SAAO,IAAI,mBAAmB,MAAM,SADtB,OADG,MAAM,KAAK,MAAM,IAAI,EACT,MAAM,EACgB,QAAQ;;CAG7D,YACE,MACA,AAAOA,SACP,MACA,AAAQC,SACR;AAEA,QAAM,MAAM,QAAQ;EALb;EAEC;AAKR,OAAK,OAAO;AAOZ,OAAK,aAJmB,OAAO,OAAO,KAAK,KAAK,CAAC,QAC9C,KAAK,aAAa,KAAK,IAAI,KAAK,SAAS,WAAW,SAAS,UAAU,EACxE,EACD,GACmC;;CAItC,IAAI,mBAAmB;EACrB,MAAMC,QAA0B,EAAE;AAElC,MAAI,KAAK,oBAAoB,OAC3B,OAAM,QAAQ;GACZ,MAAM,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG,KAAK,gBAAgB;GACpF,KAAK,KAAK,gBAAgB,YAAY;GACtC,MAAM,KAAK,gBAAgB,YAAY;GACxC;AAGH,MAAI,KAAK,oBAAoB,OAC3B,OAAM,QAAQ;GACZ,MAAM,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ,GAAG,KAAK,gBAAgB;GACpF,KAAK,KAAK,gBAAgB,YAAY;GACtC,MAAM,KAAK,gBAAgB,YAAY;GACxC;AAGH,SAAO;;CAGT,IAAI,YAAY;AACd,SAAO;GACL,aAAa,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ;GACnE,cAAc,GAAG,KAAK,QAAQ,yBAAyB,KAAK,QAAQ;GACrE;;CAGH,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"}
|
|
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.videoTrackIndex;\n const audioTrack = engine.audioTrackIndex;\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\n // Override URL-building methods to use API endpoints instead of file paths\n get initSegmentPaths() {\n const paths: InitSegmentPaths = {};\n\n if (this.audioTrackIndex !== undefined) {\n paths.audio = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${this.audioTrackIndex.track}`,\n pos: this.audioTrackIndex.initSegment.offset,\n size: this.audioTrackIndex.initSegment.size,\n };\n }\n\n if (this.videoTrackIndex !== undefined) {\n paths.video = {\n path: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${this.videoTrackIndex.track}`,\n pos: this.videoTrackIndex.initSegment.offset,\n size: this.videoTrackIndex.initSegment.size,\n };\n }\n\n return paths;\n }\n\n get templates() {\n return {\n initSegment: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/{trackId}`,\n mediaSegment: `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/{trackId}`,\n };\n }\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":"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"}
|