@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioTrack.js","names":["EFAudioTrack","#loadWaveformData","#lastSrc","#abortController","#scheduleRender","#renderRequested","#renderWaveform","#getTrackPositionInfo","#hostHeight","#drawWaveformRegion","#resizeObserver","#renderPlaceholder"],"sources":["../../../../src/gui/timeline/tracks/AudioTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { EFAudio } from \"../../../elements/EFAudio.js\";\nimport { TrackItem } from \"./TrackItem.js\";\nimport {\n extractWaveformData,\n type WaveformData,\n} from \"./waveformUtils.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n} from \"../timelineStateContext.js\";\n\n/** Padding in pixels to render beyond visible area (for smooth scrolling) */\nconst VIRTUAL_RENDER_PADDING_PX = 100;\n\n@customElement(\"ef-audio-track\")\nexport class EFAudioTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .waveform-host {\n position: absolute;\n left: 0;\n top: 2px;\n right: 0;\n bottom: 2px;\n overflow: hidden;\n }\n .waveform-canvas {\n display: block;\n position: absolute;\n top: 0;\n height: 100%;\n pointer-events: none;\n }\n `,\n ];\n\n canvasRef = createRef<HTMLCanvasElement>();\n\n /** Timeline state context for viewport info */\n @consume({ context: timelineStateContext, subscribe: true })\n @state()\n private _timelineState?: TimelineState;\n\n @state()\n private _waveformData: WaveformData | null = null;\n\n @state()\n private _isLoading = false;\n\n #lastSrc: string | null = null;\n #abortController: AbortController | null = null;\n #resizeObserver?: ResizeObserver;\n #renderRequested = false;\n #hostHeight = 0;\n\n /**\n * Load waveform data when the audio source changes\n */\n async #loadWaveformData(): Promise<void> {\n const audio = this.element as EFAudio;\n const src = audio?.src;\n\n // Skip if no source or same source already loaded\n if (!src || src === this.#lastSrc) {\n return;\n }\n\n this.#lastSrc = src;\n\n // Cancel any in-progress load\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n\n this._isLoading = true;\n\n try {\n const waveformData = await extractWaveformData(\n src,\n this.#abortController.signal,\n );\n\n if (waveformData) {\n this._waveformData = waveformData;\n this.#scheduleRender();\n }\n } catch (error) {\n if (!(error instanceof DOMException && error.name === \"AbortError\")) {\n console.warn(\"Failed to load waveform data:\", error);\n }\n } finally {\n this._isLoading = false;\n }\n }\n\n /**\n * Schedule a canvas render on the next animation frame\n */\n #scheduleRender(): void {\n if (this.#renderRequested) return;\n this.#renderRequested = true;\n\n requestAnimationFrame(() => {\n this.#renderRequested = false;\n this.#renderWaveform();\n });\n }\n\n /**\n * Get the track's position info relative to timeline scroll\n */\n #getTrackPositionInfo(): {\n trackStartPx: number;\n trackWidthPx: number;\n viewportScrollLeft: number;\n viewportWidth: number;\n pixelsPerMs: number;\n } | null {\n const audio = this.element as EFAudio;\n const durationMs = audio.durationMs ?? 0;\n if (durationMs === 0) return null;\n\n const pixelsPerMs = this._timelineState?.pixelsPerMs ?? this.pixelsPerMs;\n const trackWidthPx = durationMs * pixelsPerMs;\n\n // Get track's absolute position from startTimeMs\n const trackStartMs = audio.startTimeMs ?? 0;\n const trackStartPx = trackStartMs * pixelsPerMs;\n\n // Get viewport info from context\n const viewportScrollLeft = this._timelineState?.viewportScrollLeft ?? 0;\n const viewportWidth = this._timelineState?.viewportWidth ?? 800;\n\n return {\n trackStartPx,\n trackWidthPx,\n viewportScrollLeft,\n viewportWidth,\n pixelsPerMs,\n };\n }\n\n /**\n * Render the waveform to canvas with virtual rendering.\n * \n * The approach:\n * 1. Calculate the visible portion of the track (intersection of track and viewport)\n * 2. Position the canvas at that visible portion within the track\n * 3. Draw only the waveform data for that visible time range\n * 4. Update position and content as scroll/zoom changes\n */\n #renderWaveform(): void {\n const canvas = this.canvasRef.value;\n const waveformData = this._waveformData;\n\n if (!canvas || !waveformData) return;\n\n const positionInfo = this.#getTrackPositionInfo();\n if (!positionInfo) return;\n\n const { trackStartPx, trackWidthPx, viewportScrollLeft, viewportWidth, pixelsPerMs } =\n positionInfo;\n\n // Calculate visible region in absolute pixels (with padding for smooth scrolling)\n const visibleLeftPx = viewportScrollLeft - VIRTUAL_RENDER_PADDING_PX;\n const visibleRightPx = viewportScrollLeft + viewportWidth + VIRTUAL_RENDER_PADDING_PX;\n\n // Track boundaries in absolute pixels\n const trackEndPx = trackStartPx + trackWidthPx;\n\n // Check if track is visible at all\n if (trackEndPx < visibleLeftPx || trackStartPx > visibleRightPx) {\n // Track not visible, hide canvas\n canvas.style.display = \"none\";\n return;\n }\n canvas.style.display = \"block\";\n\n // Calculate the intersection: what part of the track is visible\n // All coordinates are now relative to the track's left edge (0 = track start)\n const visibleStartInTrack = Math.max(0, visibleLeftPx - trackStartPx);\n const visibleEndInTrack = Math.min(trackWidthPx, visibleRightPx - trackStartPx);\n const visibleWidthPx = visibleEndInTrack - visibleStartInTrack;\n\n if (visibleWidthPx <= 0) return;\n\n const height = this.#hostHeight || 18;\n\n // Set canvas size with DPR\n const dpr = window.devicePixelRatio || 1;\n const targetWidth = Math.ceil(visibleWidthPx * dpr);\n const targetHeight = Math.ceil(height * dpr);\n\n if (canvas.width !== targetWidth || canvas.height !== targetHeight) {\n canvas.width = targetWidth;\n canvas.height = targetHeight;\n }\n \n // Position canvas at the visible portion within the track\n canvas.style.left = `${visibleStartInTrack}px`;\n canvas.style.width = `${visibleWidthPx}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, visibleWidthPx, height);\n\n // Calculate what time range to render\n const audio = this.element as EFAudio;\n const sourceInMs = audio.sourceStartMs ?? 0;\n \n // Convert visible pixel range to time range\n const timeStartMs = sourceInMs + visibleStartInTrack / pixelsPerMs;\n const timeEndMs = sourceInMs + visibleEndInTrack / pixelsPerMs;\n\n // Draw the waveform for the visible portion\n this.#drawWaveformRegion(\n ctx,\n waveformData,\n 0, // Start drawing at x=0 of canvas (canvas is already positioned)\n visibleWidthPx,\n height,\n timeStartMs,\n timeEndMs,\n );\n }\n\n /**\n * Draw a region of the waveform to canvas\n */\n #drawWaveformRegion(\n ctx: CanvasRenderingContext2D,\n waveformData: WaveformData,\n x: number,\n width: number,\n height: number,\n startMs: number,\n endMs: number,\n ): void {\n const { peaks, samplesPerSecond } = waveformData;\n\n // Calculate sample range\n const startSample = Math.floor((startMs / 1000) * samplesPerSecond);\n const endSample = Math.ceil((endMs / 1000) * samplesPerSecond);\n const sampleCount = endSample - startSample;\n\n if (sampleCount <= 0 || width <= 0) return;\n\n const centerY = height / 2;\n const halfHeight = (height / 2) - 2; // Leave 2px padding top/bottom\n const color = this.getElementTypeColor();\n\n ctx.fillStyle = color;\n ctx.globalAlpha = 0.8;\n ctx.beginPath();\n\n // Draw top half (max values) left to right\n const pixelsPerSample = width / sampleCount;\n\n for (let i = 0; i <= sampleCount; i++) {\n const sampleIndex = startSample + i;\n const peakIndex = sampleIndex * 2;\n\n // Clamp to valid range\n if (peakIndex + 1 >= peaks.length) break;\n\n const maxValue = peaks[peakIndex + 1] ?? 0;\n const px = x + i * pixelsPerSample;\n const py = centerY - maxValue * halfHeight;\n\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n\n // Draw bottom half (min values) right to left\n for (let i = sampleCount; i >= 0; i--) {\n const sampleIndex = startSample + i;\n const peakIndex = sampleIndex * 2;\n\n // Clamp to valid range\n if (peakIndex >= peaks.length) continue;\n\n const minValue = peaks[peakIndex] ?? 0;\n const px = x + i * pixelsPerSample;\n const py = centerY - minValue * halfHeight;\n\n ctx.lineTo(px, py);\n }\n\n ctx.closePath();\n ctx.fill();\n\n // Draw center line\n ctx.globalAlpha = 0.3;\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, centerY);\n ctx.lineTo(x + width, centerY);\n ctx.stroke();\n\n ctx.globalAlpha = 1;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n\n // Start loading waveform data\n this.#loadWaveformData();\n\n // Observe size changes\n this.#resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n this.#hostHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n this.#scheduleRender();\n }\n });\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#abortController?.abort();\n this.#resizeObserver?.disconnect();\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n\n // Check if we need to reload waveform data\n const audio = this.element as EFAudio;\n if (audio?.src !== this.#lastSrc) {\n this.#loadWaveformData();\n }\n\n // Re-render when timeline state changes (scroll, zoom)\n if (changedProperties.has(\"_timelineState\")) {\n this.#scheduleRender();\n }\n\n // Attach resize observer to track container once rendered\n if (this.canvasRef.value && this.#resizeObserver) {\n const container = this.canvasRef.value.parentElement;\n if (container) {\n this.#resizeObserver.disconnect();\n this.#resizeObserver.observe(container);\n }\n }\n\n // Always schedule render after update to catch any changes\n this.#scheduleRender();\n }\n\n contents() {\n const audio = this.element as EFAudio;\n if (!(audio instanceof EFAudio)) {\n return nothing;\n }\n\n const durationMs = audio.durationMs ?? 0;\n if (durationMs === 0) {\n return nothing;\n }\n\n // Show loading placeholder if no waveform data yet\n if (!this._waveformData) {\n return this.#renderPlaceholder();\n }\n\n // The host fills the track container, canvas is positioned within it\n return html`\n <div class=\"waveform-host\">\n <canvas ${ref(this.canvasRef)} class=\"waveform-canvas\"></canvas>\n </div>\n `;\n }\n\n /**\n * Render placeholder while loading\n */\n #renderPlaceholder() {\n return html`\n <div\n style=\"\n position: absolute;\n left: 0;\n top: 2px;\n bottom: 2px;\n right: 0;\n background: linear-gradient(90deg, \n ${this.getElementTypeColor()}22 0%, \n ${this.getElementTypeColor()}44 50%,\n ${this.getElementTypeColor()}22 100%\n );\n background-size: 200% 100%;\n animation: ${this._isLoading ? \"shimmer 1.5s infinite\" : \"none\"};\n border-radius: 2px;\n \"\n ></div>\n <style>\n @keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n </style>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { currentTimeContext } from "../../currentTimeContext.js";
|
|
2
|
+
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
3
|
+
import { ICONS, phosphorIcon } from "../../icons.js";
|
|
4
|
+
import { TrackItem } from "./TrackItem.js";
|
|
5
|
+
import { consume } from "@lit/context";
|
|
6
|
+
import { css, html, nothing } from "lit";
|
|
7
|
+
import { customElement } from "lit/decorators.js";
|
|
8
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
9
|
+
|
|
10
|
+
//#region src/gui/timeline/tracks/CaptionsTrack.ts
|
|
11
|
+
let measurementCanvas = null;
|
|
12
|
+
let measurementContext = null;
|
|
13
|
+
const textMeasurementCache = /* @__PURE__ */ new Map();
|
|
14
|
+
const MAX_CACHE_SIZE = 500;
|
|
15
|
+
/**
|
|
16
|
+
* Measure text width accurately using canvas.
|
|
17
|
+
* Matches the actual font used in word elements (font-weight: 500).
|
|
18
|
+
* Results are cached to avoid repeated measurements of the same text.
|
|
19
|
+
*/
|
|
20
|
+
function measureTextWidth(text, fontSize, fontWeight = 500) {
|
|
21
|
+
const cacheKey = `${text}:${fontSize}:${fontWeight}`;
|
|
22
|
+
const cached = textMeasurementCache.get(cacheKey);
|
|
23
|
+
if (cached !== void 0) return cached;
|
|
24
|
+
if (!measurementCanvas || !measurementContext) {
|
|
25
|
+
measurementCanvas = document.createElement("canvas");
|
|
26
|
+
measurementContext = measurementCanvas.getContext("2d");
|
|
27
|
+
}
|
|
28
|
+
if (!measurementContext) return text.length * fontSize * .6;
|
|
29
|
+
measurementContext.font = `${fontWeight} ${fontSize}px ${getComputedStyle(document.body).fontFamily || "system-ui, sans-serif"}`;
|
|
30
|
+
const width = measurementContext.measureText(text).width;
|
|
31
|
+
if (textMeasurementCache.size >= MAX_CACHE_SIZE) {
|
|
32
|
+
const keysToDelete = Array.from(textMeasurementCache.keys()).slice(0, MAX_CACHE_SIZE / 2);
|
|
33
|
+
for (const key of keysToDelete) textMeasurementCache.delete(key);
|
|
34
|
+
}
|
|
35
|
+
textMeasurementCache.set(cacheKey, width);
|
|
36
|
+
return width;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if words can fit individually within a segment when positioned by time
|
|
40
|
+
*
|
|
41
|
+
* Strategy: Allow overlaps as long as all words can be rendered within the container.
|
|
42
|
+
* Only use compact mode when words are so cramped they can't be displayed at all.
|
|
43
|
+
*/
|
|
44
|
+
function canWordsFitIndividually(words, segmentStart, segmentWidthPx, pixelsPerMs) {
|
|
45
|
+
if (words.length === 0) return {
|
|
46
|
+
fits: false,
|
|
47
|
+
reason: "no words"
|
|
48
|
+
};
|
|
49
|
+
let totalTextWidth = 0;
|
|
50
|
+
const wordWidths = [];
|
|
51
|
+
for (const word of words) {
|
|
52
|
+
if (!word) continue;
|
|
53
|
+
const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;
|
|
54
|
+
const startPx = pixelsPerMs * (word.start - segmentStart) * 1e3;
|
|
55
|
+
const endPx = pixelsPerMs * (word.end - segmentStart) * 1e3;
|
|
56
|
+
const timeWidth = endPx - startPx;
|
|
57
|
+
wordWidths.push({
|
|
58
|
+
textWidth,
|
|
59
|
+
timeWidth,
|
|
60
|
+
startPx,
|
|
61
|
+
endPx
|
|
62
|
+
});
|
|
63
|
+
totalTextWidth += textWidth;
|
|
64
|
+
}
|
|
65
|
+
if (totalTextWidth <= segmentWidthPx * .9) return { fits: true };
|
|
66
|
+
for (const { textWidth, timeWidth } of wordWidths) if (timeWidth < textWidth * .3) return {
|
|
67
|
+
fits: false,
|
|
68
|
+
reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * .3).toFixed(1)}px)`
|
|
69
|
+
};
|
|
70
|
+
if (Math.max(...wordWidths.map((w) => w.endPx)) <= segmentWidthPx * 1.1) return { fits: true };
|
|
71
|
+
return {
|
|
72
|
+
fits: false,
|
|
73
|
+
reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Controller to ensure captions track updates reactively during playback.
|
|
78
|
+
*
|
|
79
|
+
* Performance optimization: Only requests updates when the visual state actually
|
|
80
|
+
* needs to change (active word/segment changed), not on every frame.
|
|
81
|
+
*/
|
|
82
|
+
var CaptionsTimeController = class CaptionsTimeController {
|
|
83
|
+
static {
|
|
84
|
+
this.MIN_TIME_CHANGE_MS = 100;
|
|
85
|
+
}
|
|
86
|
+
constructor(host) {
|
|
87
|
+
this.host = host;
|
|
88
|
+
this.lastTimeMs = -1;
|
|
89
|
+
this.lastActiveWordIndex = -1;
|
|
90
|
+
this.lastActiveSegmentIndex = -1;
|
|
91
|
+
this.host.addController(this);
|
|
92
|
+
}
|
|
93
|
+
hostConnected() {
|
|
94
|
+
this.startTimeUpdate();
|
|
95
|
+
}
|
|
96
|
+
hostDisconnected() {
|
|
97
|
+
this.stopTimeUpdate();
|
|
98
|
+
}
|
|
99
|
+
startTimeUpdate() {
|
|
100
|
+
const update = () => {
|
|
101
|
+
const captions = this.host.element;
|
|
102
|
+
const currentTimeMs = captions.rootTimegroup?.currentTimeMs || 0;
|
|
103
|
+
const captionsData = captions?.unifiedCaptionsDataTask?.value;
|
|
104
|
+
let shouldUpdate = false;
|
|
105
|
+
if (captionsData) {
|
|
106
|
+
const captionsLocalTimeSec = (currentTimeMs - captions.startTimeMs) / 1e3;
|
|
107
|
+
const activeWordIndex = captionsData.word_segments.findIndex((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
|
|
108
|
+
const activeSegmentIndex = captionsData.segments.findIndex((seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end);
|
|
109
|
+
if (activeWordIndex !== this.lastActiveWordIndex) {
|
|
110
|
+
this.lastActiveWordIndex = activeWordIndex;
|
|
111
|
+
shouldUpdate = true;
|
|
112
|
+
}
|
|
113
|
+
if (activeSegmentIndex !== this.lastActiveSegmentIndex) {
|
|
114
|
+
this.lastActiveSegmentIndex = activeSegmentIndex;
|
|
115
|
+
shouldUpdate = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (Math.abs(currentTimeMs - this.lastTimeMs) >= CaptionsTimeController.MIN_TIME_CHANGE_MS) shouldUpdate = true;
|
|
119
|
+
if (shouldUpdate) {
|
|
120
|
+
this.lastTimeMs = currentTimeMs;
|
|
121
|
+
this.host.requestUpdate();
|
|
122
|
+
}
|
|
123
|
+
this.animationFrameId = requestAnimationFrame(update);
|
|
124
|
+
};
|
|
125
|
+
update();
|
|
126
|
+
}
|
|
127
|
+
stopTimeUpdate() {
|
|
128
|
+
if (this.animationFrameId) {
|
|
129
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
130
|
+
this.animationFrameId = void 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
135
|
+
constructor(..._args) {
|
|
136
|
+
super(..._args);
|
|
137
|
+
this.contextCurrentTimeMs = 0;
|
|
138
|
+
this._timeController = new CaptionsTimeController(this);
|
|
139
|
+
this.lastPixelsPerMs = 0;
|
|
140
|
+
}
|
|
141
|
+
static {
|
|
142
|
+
this.styles = [...TrackItem.styles, css`
|
|
143
|
+
.segment-block {
|
|
144
|
+
position: absolute;
|
|
145
|
+
border-radius: 3px;
|
|
146
|
+
transition: box-shadow 0.15s ease, z-index 0.15s ease;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
overflow: visible;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.segment-block:hover {
|
|
152
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
|
153
|
+
z-index: 5;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.word-element {
|
|
157
|
+
position: absolute;
|
|
158
|
+
font-size: 9px;
|
|
159
|
+
line-height: 1.2;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
font-weight: 500;
|
|
162
|
+
top: 50%;
|
|
163
|
+
transform: translateY(-50%);
|
|
164
|
+
padding: 2px 4px;
|
|
165
|
+
border-radius: 2px;
|
|
166
|
+
transition: all 0.1s ease;
|
|
167
|
+
background: rgba(30, 41, 59, 0.9);
|
|
168
|
+
color: rgb(226, 232, 240);
|
|
169
|
+
z-index: 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.word-element.active {
|
|
173
|
+
background: rgb(74, 222, 128);
|
|
174
|
+
color: rgb(20, 30, 20);
|
|
175
|
+
font-weight: 700;
|
|
176
|
+
font-size: 10px;
|
|
177
|
+
z-index: 10;
|
|
178
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.word-element.future {
|
|
182
|
+
background: rgba(51, 65, 85, 0.8);
|
|
183
|
+
color: rgb(226, 232, 240);
|
|
184
|
+
z-index: 5;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.segment-block.active .word-element:not(.active):not(.future) {
|
|
188
|
+
color: rgb(203, 213, 225);
|
|
189
|
+
background: rgba(30, 41, 59, 0.9);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Compact text mode - when words are too small to position individually */
|
|
193
|
+
.segment-block.compact-text {
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
padding: 0 4px;
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
/* Keep position: absolute from .segment-block for correct time-based positioning */
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Allow overflow on hover for compact text */
|
|
202
|
+
.segment-block.compact-text:hover {
|
|
203
|
+
overflow: visible;
|
|
204
|
+
z-index: 100;
|
|
205
|
+
/* Expand to fit content on hover */
|
|
206
|
+
width: max-content !important;
|
|
207
|
+
min-width: max-content;
|
|
208
|
+
background: rgba(34, 60, 40, 0.95) !important;
|
|
209
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.segment-text-compact {
|
|
213
|
+
font-size: 10px;
|
|
214
|
+
line-height: 1.2;
|
|
215
|
+
white-space: nowrap;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
text-overflow: ellipsis;
|
|
218
|
+
color: rgb(226, 232, 240);
|
|
219
|
+
width: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* On hover, show full text */
|
|
223
|
+
.segment-block.compact-text:hover .segment-text-compact {
|
|
224
|
+
overflow: visible;
|
|
225
|
+
text-overflow: clip;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.segment-block.compact-text.active .segment-text-compact {
|
|
229
|
+
color: rgb(203, 213, 225);
|
|
230
|
+
font-weight: 500;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.segment-duration-indicator {
|
|
234
|
+
position: absolute;
|
|
235
|
+
bottom: 0;
|
|
236
|
+
left: 0;
|
|
237
|
+
right: 0;
|
|
238
|
+
height: 2px;
|
|
239
|
+
background: currentColor;
|
|
240
|
+
opacity: 0.2;
|
|
241
|
+
border-radius: 0 0 3px 3px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.segment-block.active .segment-duration-indicator {
|
|
245
|
+
opacity: 0.4;
|
|
246
|
+
height: 2px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.word-marker {
|
|
250
|
+
position: absolute;
|
|
251
|
+
bottom: 0;
|
|
252
|
+
width: 1px;
|
|
253
|
+
height: 30%;
|
|
254
|
+
background: rgba(255, 255, 255, 0.3);
|
|
255
|
+
pointer-events: none;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.word-marker.active {
|
|
259
|
+
background: rgba(255, 255, 255, 0.8);
|
|
260
|
+
height: 50%;
|
|
261
|
+
width: 2px;
|
|
262
|
+
}
|
|
263
|
+
`];
|
|
264
|
+
}
|
|
265
|
+
updated(changedProperties) {
|
|
266
|
+
super.updated(changedProperties);
|
|
267
|
+
if (changedProperties.has("pixelsPerMs")) {
|
|
268
|
+
const currentPixelsPerMs = this.pixelsPerMs;
|
|
269
|
+
if (currentPixelsPerMs !== this.lastPixelsPerMs) {
|
|
270
|
+
this.lastPixelsPerMs = currentPixelsPerMs;
|
|
271
|
+
this.requestUpdate();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
render() {
|
|
276
|
+
const captionsData = this.element.unifiedCaptionsDataTask.value;
|
|
277
|
+
return html`<div style=${styleMap(this.gutterStyles)}>
|
|
278
|
+
<div
|
|
279
|
+
class="relative"
|
|
280
|
+
style="background-color: var(--filmstrip-bg);"
|
|
281
|
+
?data-focused=${this.isFocused}
|
|
282
|
+
@mouseenter=${() => {
|
|
283
|
+
if (this.focusContext) this.focusContext.focusedElement = this.element;
|
|
284
|
+
}}
|
|
285
|
+
@mouseleave=${() => {
|
|
286
|
+
if (this.focusContext) this.focusContext.focusedElement = null;
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
<div
|
|
290
|
+
?data-focused=${this.isFocused}
|
|
291
|
+
class="trim-container relative mb-0 block text-nowrap border text-sm overflow-visible"
|
|
292
|
+
style=${styleMap({
|
|
293
|
+
...this.trimPortionStyles,
|
|
294
|
+
height: "var(--timeline-track-height, 22px)",
|
|
295
|
+
backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
|
|
296
|
+
borderColor: "var(--filmstrip-border)",
|
|
297
|
+
borderLeftColor: this.getElementTypeColor(),
|
|
298
|
+
borderLeftWidth: "3px",
|
|
299
|
+
minHeight: "22px"
|
|
300
|
+
})}
|
|
301
|
+
>
|
|
302
|
+
${this.renderCaptionsData(captionsData)}
|
|
303
|
+
${this.enableTrim ? html`<ef-trim-handles
|
|
304
|
+
element-id=${this.element.id || ""}
|
|
305
|
+
pixels-per-ms=${this.pixelsPerMs}
|
|
306
|
+
trim-start-ms=${this.element.trimStartMs ?? 0}
|
|
307
|
+
trim-end-ms=${this.element.trimEndMs ?? 0}
|
|
308
|
+
intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}
|
|
309
|
+
@trim-change=${this.handleTrimChange}
|
|
310
|
+
></ef-trim-handles>` : nothing}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
${this.renderChildren()}
|
|
314
|
+
</div>`;
|
|
315
|
+
}
|
|
316
|
+
renderCaptionsData(captionsData) {
|
|
317
|
+
if (!captionsData) return html``;
|
|
318
|
+
const captions = this.element;
|
|
319
|
+
const rootTimegroup = captions.rootTimegroup;
|
|
320
|
+
const captionsLocalTimeSec = ((this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0) - captions.startTimeMs) / 1e3;
|
|
321
|
+
const captionColor = "rgb(34, 197, 94)";
|
|
322
|
+
const activeWord = captionsData.word_segments.find((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
|
|
323
|
+
return html`
|
|
324
|
+
${captionsData.word_segments.map((word) => {
|
|
325
|
+
const wordStartPx = this.pixelsPerMs * word.start * 1e3;
|
|
326
|
+
const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1e3;
|
|
327
|
+
const isActive = word === activeWord;
|
|
328
|
+
if (wordWidth < 1.5) return nothing;
|
|
329
|
+
return html`<div
|
|
330
|
+
class="word-marker ${isActive ? "active" : ""}"
|
|
331
|
+
style=${styleMap({ left: `${wordStartPx}px` })}
|
|
332
|
+
></div>`;
|
|
333
|
+
})}
|
|
334
|
+
${captionsData.segments.map((segment) => {
|
|
335
|
+
const isActiveSegment = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
|
|
336
|
+
const segmentStartPx = this.pixelsPerMs * segment.start * 1e3;
|
|
337
|
+
const segmentWidth = this.pixelsPerMs * (segment.end - segment.start) * 1e3;
|
|
338
|
+
const segmentDuration = (segment.end - segment.start) * 1e3;
|
|
339
|
+
const wordsInSegment = captionsData.word_segments.filter((word) => word.start >= segment.start && word.end <= segment.end).sort((a, b) => a.start - b.start);
|
|
340
|
+
const density = Math.min(wordsInSegment.length / 10, 1);
|
|
341
|
+
const useCompactText = !canWordsFitIndividually(wordsInSegment, segment.start, segmentWidth, this.pixelsPerMs).fits;
|
|
342
|
+
let avgSpacing = 0;
|
|
343
|
+
if (!useCompactText && wordsInSegment.length > 1) {
|
|
344
|
+
let totalSpacing = 0;
|
|
345
|
+
let spacingCount = 0;
|
|
346
|
+
for (let i = 0; i < wordsInSegment.length - 1; i++) {
|
|
347
|
+
const word1 = wordsInSegment[i];
|
|
348
|
+
const word2 = wordsInSegment[i + 1];
|
|
349
|
+
if (!word1 || !word2) continue;
|
|
350
|
+
const word1EndPx = this.pixelsPerMs * (word1.end - segment.start) * 1e3;
|
|
351
|
+
const spacing = this.pixelsPerMs * (word2.start - segment.start) * 1e3 - word1EndPx;
|
|
352
|
+
if (spacing > 0) {
|
|
353
|
+
totalSpacing += spacing;
|
|
354
|
+
spacingCount++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;
|
|
358
|
+
}
|
|
359
|
+
const MIN_READABLE_FONT_SIZE = 6;
|
|
360
|
+
const baseFontSize = 9;
|
|
361
|
+
const activeFontSize = 10;
|
|
362
|
+
let scaledFontSize = baseFontSize;
|
|
363
|
+
let scaledActiveFontSize = activeFontSize;
|
|
364
|
+
if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {
|
|
365
|
+
const scaleFactor = Math.max(MIN_READABLE_FONT_SIZE / baseFontSize, avgSpacing / 8);
|
|
366
|
+
scaledFontSize = Math.max(MIN_READABLE_FONT_SIZE, baseFontSize * scaleFactor);
|
|
367
|
+
scaledActiveFontSize = Math.max(MIN_READABLE_FONT_SIZE, activeFontSize * scaleFactor);
|
|
368
|
+
}
|
|
369
|
+
const renderWords = () => {
|
|
370
|
+
if (useCompactText) return html`
|
|
371
|
+
<span class="segment-text-compact">${segment.text}</span>
|
|
372
|
+
`;
|
|
373
|
+
return wordsInSegment.map((word) => {
|
|
374
|
+
const wordOffsetFromSegmentStart = (word.start - segment.start) * 1e3;
|
|
375
|
+
const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;
|
|
376
|
+
const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1e3;
|
|
377
|
+
const isActive = word === activeWord;
|
|
378
|
+
const isFuture = activeWord && word.start > activeWord.end;
|
|
379
|
+
return html`
|
|
380
|
+
<span
|
|
381
|
+
class="word-element ${isActive ? "active" : ""} ${isFuture ? "future" : ""}"
|
|
382
|
+
style=${styleMap({
|
|
383
|
+
left: `${wordLeftPx}px`,
|
|
384
|
+
minWidth: `${Math.max(wordWidthPx, 8)}px`,
|
|
385
|
+
fontSize: isActive ? `${scaledActiveFontSize}px` : `${scaledFontSize}px`,
|
|
386
|
+
top: "50%"
|
|
387
|
+
})}
|
|
388
|
+
title="Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)"
|
|
389
|
+
>
|
|
390
|
+
${word.text.trim()}
|
|
391
|
+
</span>
|
|
392
|
+
`;
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
return html`<div
|
|
396
|
+
class="segment-block ${isActiveSegment ? "active" : ""} ${useCompactText ? "compact-text" : ""}"
|
|
397
|
+
style=${styleMap({
|
|
398
|
+
left: `${segmentStartPx}px`,
|
|
399
|
+
width: `${Math.max(segmentWidth, 4)}px`,
|
|
400
|
+
height: "100%",
|
|
401
|
+
top: "0px",
|
|
402
|
+
backgroundColor: isActiveSegment ? `rgba(34, 197, 94, ${.3 + density * .2})` : `rgba(34, 197, 94, ${.1 + density * .1})`,
|
|
403
|
+
borderColor: isActiveSegment ? captionColor : `rgba(34, 197, 94, 0.4)`,
|
|
404
|
+
minWidth: segmentWidth < 20 ? "20px" : "auto"
|
|
405
|
+
})}
|
|
406
|
+
title=${useCompactText ? `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s` : `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\nWords: ${wordsInSegment.length}`}
|
|
407
|
+
@click=${(e) => {
|
|
408
|
+
e.stopPropagation();
|
|
409
|
+
if (rootTimegroup) rootTimegroup.currentTimeMs = captions.startTimeMs + segment.start * 1e3;
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
${renderWords()}
|
|
413
|
+
${!useCompactText ? html`<div class="segment-duration-indicator"></div>` : nothing}
|
|
414
|
+
</div>`;
|
|
415
|
+
})}
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
renderChildren() {
|
|
419
|
+
return nothing;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
__decorate([consume({
|
|
423
|
+
context: currentTimeContext,
|
|
424
|
+
subscribe: true
|
|
425
|
+
})], EFCaptionsTrack.prototype, "contextCurrentTimeMs", void 0);
|
|
426
|
+
EFCaptionsTrack = __decorate([customElement("ef-captions-track")], EFCaptionsTrack);
|
|
427
|
+
let EFCaptionsActiveWordTrack = class EFCaptionsActiveWordTrack$1 extends TrackItem {
|
|
428
|
+
get captionsTrackStyles() {
|
|
429
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
430
|
+
return {
|
|
431
|
+
position: "relative",
|
|
432
|
+
left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
|
|
433
|
+
width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
render() {
|
|
437
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
438
|
+
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
439
|
+
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
440
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
441
|
+
${phosphorIcon(ICONS.microphone)} Active Word
|
|
442
|
+
</div>
|
|
443
|
+
</div>`;
|
|
444
|
+
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
445
|
+
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
446
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
447
|
+
${captionsData.word_segments.map((word) => {
|
|
448
|
+
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
449
|
+
return html`<div
|
|
450
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
451
|
+
style=${styleMap({
|
|
452
|
+
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
453
|
+
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
454
|
+
height: "100%",
|
|
455
|
+
top: "0px",
|
|
456
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
|
|
457
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
|
|
458
|
+
})}
|
|
459
|
+
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
460
|
+
>
|
|
461
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${word.text.trim()}</span>` : ""}
|
|
462
|
+
</div>`;
|
|
463
|
+
})}
|
|
464
|
+
</div>
|
|
465
|
+
</div>`;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
EFCaptionsActiveWordTrack = __decorate([customElement("ef-captions-active-word-track")], EFCaptionsActiveWordTrack);
|
|
469
|
+
let EFCaptionsSegmentTrack = class EFCaptionsSegmentTrack$1 extends TrackItem {
|
|
470
|
+
get captionsTrackStyles() {
|
|
471
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
472
|
+
return {
|
|
473
|
+
position: "relative",
|
|
474
|
+
left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
|
|
475
|
+
width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
render() {
|
|
479
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
480
|
+
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
481
|
+
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
482
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
483
|
+
${phosphorIcon(ICONS.textT)} Segment
|
|
484
|
+
</div>
|
|
485
|
+
</div>`;
|
|
486
|
+
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
487
|
+
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
488
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
489
|
+
${captionsData.segments.map((segment) => {
|
|
490
|
+
const isCurrentlyActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
|
|
491
|
+
return html`<div
|
|
492
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
493
|
+
style=${styleMap({
|
|
494
|
+
left: `${this.pixelsPerMs * segment.start * 1e3}px`,
|
|
495
|
+
width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
|
|
496
|
+
height: "100%",
|
|
497
|
+
top: "0px",
|
|
498
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
|
|
499
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
|
|
500
|
+
})}
|
|
501
|
+
title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
|
|
502
|
+
>
|
|
503
|
+
${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-segment-bg);">${segment.text}</span>` : ""}
|
|
504
|
+
</div>`;
|
|
505
|
+
})}
|
|
506
|
+
</div>
|
|
507
|
+
</div>`;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
EFCaptionsSegmentTrack = __decorate([customElement("ef-captions-segment-track")], EFCaptionsSegmentTrack);
|
|
511
|
+
let EFCaptionsBeforeWordTrack = class EFCaptionsBeforeWordTrack$1 extends TrackItem {
|
|
512
|
+
get captionsTrackStyles() {
|
|
513
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
514
|
+
return {
|
|
515
|
+
position: "relative",
|
|
516
|
+
left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
|
|
517
|
+
width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
render() {
|
|
521
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
522
|
+
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
523
|
+
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
524
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
525
|
+
${phosphorIcon(ICONS.arrowLeft)} Before
|
|
526
|
+
</div>
|
|
527
|
+
</div>`;
|
|
528
|
+
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
529
|
+
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
530
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
531
|
+
${captionsData.word_segments.map((word) => {
|
|
532
|
+
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
533
|
+
return html`<div
|
|
534
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
535
|
+
style=${styleMap({
|
|
536
|
+
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
537
|
+
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
538
|
+
height: "100%",
|
|
539
|
+
top: "0px",
|
|
540
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
|
|
541
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
|
|
542
|
+
})}
|
|
543
|
+
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
544
|
+
>
|
|
545
|
+
</div>`;
|
|
546
|
+
})}
|
|
547
|
+
</div>
|
|
548
|
+
</div>`;
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
EFCaptionsBeforeWordTrack = __decorate([customElement("ef-captions-before-word-track")], EFCaptionsBeforeWordTrack);
|
|
552
|
+
let EFCaptionsAfterWordTrack = class EFCaptionsAfterWordTrack$1 extends TrackItem {
|
|
553
|
+
get captionsTrackStyles() {
|
|
554
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
555
|
+
return {
|
|
556
|
+
position: "relative",
|
|
557
|
+
left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
|
|
558
|
+
width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
render() {
|
|
562
|
+
const parentCaptions = this.element.closest("ef-captions");
|
|
563
|
+
const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
|
|
564
|
+
if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
565
|
+
<div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
566
|
+
${phosphorIcon(ICONS.arrowRight)} After
|
|
567
|
+
</div>
|
|
568
|
+
</div>`;
|
|
569
|
+
const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
|
|
570
|
+
return html`<div style=${styleMap(this.captionsTrackStyles)}>
|
|
571
|
+
<div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
|
|
572
|
+
${captionsData.word_segments.map((word) => {
|
|
573
|
+
const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
|
|
574
|
+
return html`<div
|
|
575
|
+
class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
|
|
576
|
+
style=${styleMap({
|
|
577
|
+
left: `${this.pixelsPerMs * word.start * 1e3}px`,
|
|
578
|
+
width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
|
|
579
|
+
height: "100%",
|
|
580
|
+
top: "0px",
|
|
581
|
+
backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
|
|
582
|
+
borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
|
|
583
|
+
})}
|
|
584
|
+
title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
|
|
585
|
+
>
|
|
586
|
+
</div>`;
|
|
587
|
+
})}
|
|
588
|
+
</div>
|
|
589
|
+
</div>`;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
EFCaptionsAfterWordTrack = __decorate([customElement("ef-captions-after-word-track")], EFCaptionsAfterWordTrack);
|
|
593
|
+
|
|
594
|
+
//#endregion
|
|
595
|
+
//# sourceMappingURL=CaptionsTrack.js.map
|