@editframe/elements 0.45.1 → 0.45.3
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/DelayedLoadingState.js.map +1 -1
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.js +3 -3
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.js.map +1 -1
- package/dist/canvas/api/CanvasAPI.js.map +1 -1
- package/dist/canvas/getElementBounds.js.map +1 -1
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js +25 -23
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/canvas/selection/SelectionModel.js.map +1 -1
- package/dist/canvas/selection/selectionContext.js.map +1 -1
- package/dist/elements/ContainerInfo.js.map +1 -1
- package/dist/elements/CrossUpdateController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +2 -2
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +2 -2
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +4 -4
- package/dist/elements/EFImage.js +1 -1
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/CachedFetcher.js.map +1 -1
- package/dist/elements/EFMedia/MediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/SegmentIndex.js.map +1 -1
- package/dist/elements/EFMedia/SegmentTransport.js.map +1 -1
- package/dist/elements/EFMedia/TimingModel.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/GlobalInputCache.js.map +1 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.js +9 -8
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +4 -4
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFTimegroup.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +7 -8
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SampleBuffer.js.map +1 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.js.map +1 -1
- package/dist/elements/durationConverter.js.map +1 -1
- package/dist/elements/easingUtils.js.map +1 -1
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js.map +1 -1
- package/dist/elements/updateAnimations.js +1 -1
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFOverlayItem.js.map +1 -1
- package/dist/gui/EFOverlayLayer.js.map +1 -1
- package/dist/gui/EFPreview.js.map +1 -1
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFTogglePlay.js.map +1 -1
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin2.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/currentTimeContext.js.map +1 -1
- package/dist/gui/efContext.js.map +1 -1
- package/dist/gui/fetchContext.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -1
- package/dist/gui/panZoomTransformContext.js.map +1 -1
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js.map +1 -1
- package/dist/gui/timeline/EFTimeline.js +0 -1
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/transformCalculations.js.map +1 -1
- package/dist/gui/transformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/BridgeSpanExporter.js.map +1 -1
- package/dist/otel/setupBrowserTracing.js.map +1 -1
- package/dist/otel/tracingHelpers.js.map +1 -1
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js.map +1 -1
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.js +2 -44
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +2 -2
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderVideoToVideo.js +2 -2
- package/dist/preview/renderVideoToVideo.js.map +1 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js.map +1 -1
- package/dist/preview/rendering/loadImage.js.map +1 -1
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/utils/LRUCache.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentIndex.js","names":["tracks: TrackSet","distance: number","ranges: SegmentTimeRange[]","segments: SegmentTimeRange[]"],"sources":["../../../src/elements/EFMedia/SegmentIndex.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport {\n convertToScaledTime,\n roundToMilliseconds,\n} from \"./shared/PrecisionUtils.js\";\n\nexport type TrackRole = \"video\" | \"audio\" | \"scrub\";\n\nexport interface TrackRef {\n readonly role: TrackRole;\n readonly id: string | number;\n readonly src: string;\n readonly segmentDurationMs?: number;\n readonly segmentDurationsMs?: number[];\n readonly startTimeOffsetMs?: number;\n}\n\nexport interface TrackSet {\n video?: TrackRef;\n audio?: TrackRef;\n scrub?: TrackRef;\n}\n\nexport interface SegmentTimeRange {\n segmentId: number;\n startMs: number;\n endMs: number;\n}\n\nexport interface SegmentIndex {\n readonly durationMs: number;\n readonly tracks: TrackSet;\n segmentAt(timeMs: number, track: TrackRef): number | undefined;\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[];\n}\n\n// ---------------------------------------------------------------------------\n// FragmentIndex — backed by TrackFragmentIndex (local and file-id files)\n// ---------------------------------------------------------------------------\n\nexport function createFragmentIndex(\n data: Record<number, TrackFragmentIndex>,\n src: string,\n): SegmentIndex {\n const longestFragment = Object.values(data).reduce(\n (max, fragment) => Math.max(max, fragment.duration / fragment.timescale),\n 0,\n );\n const durationMs = longestFragment * 1000;\n\n const audioTrack = Object.values(data).find((t) => t.type === \"audio\");\n const videoTrack = Object.values(data).find(\n (t) => t.type === \"video\" && t.track !== undefined && t.track > 0,\n );\n const scrubTrack = data[-1];\n\n const tracks: TrackSet = {};\n\n if (videoTrack && videoTrack.track !== undefined) {\n tracks.video = {\n role: \"video\",\n id: videoTrack.track,\n src,\n startTimeOffsetMs: videoTrack.startTimeOffsetMs,\n };\n }\n\n if (audioTrack && audioTrack.track !== undefined) {\n tracks.audio = {\n role: \"audio\",\n id: audioTrack.track,\n src,\n };\n }\n\n if (scrubTrack && scrubTrack.track !== undefined) {\n const segmentDurationsMs =\n scrubTrack.segments.length > 0\n ? scrubTrack.segments.map(\n (s) => (s.duration / scrubTrack.timescale) * 1000,\n )\n : undefined;\n tracks.scrub = {\n role: \"scrub\",\n id: scrubTrack.track,\n src,\n segmentDurationMs: 30000,\n segmentDurationsMs,\n startTimeOffsetMs: scrubTrack.startTimeOffsetMs,\n };\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) {\n throw new Error(`Track ${trackId} not found`);\n }\n const { timescale, segments } = trackData;\n\n const startTimeOffsetMs = track.startTimeOffsetMs || 0;\n const offsetSeekTimeMs = roundToMilliseconds(timeMs + startTimeOffsetMs);\n const scaledSeekTime = convertToScaledTime(offsetSeekTimeMs, timescale);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) {\n return i;\n }\n }\n\n // Gap handling: find nearest segment\n let nearestSegmentIndex = 0;\n let nearestDistance = Number.MAX_SAFE_INTEGER;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n\n let distance: number;\n if (scaledSeekTime < segment.cts) {\n distance = segment.cts - scaledSeekTime;\n } else if (scaledSeekTime >= segmentEndTime) {\n distance = scaledSeekTime - segmentEndTime;\n } else {\n return i;\n }\n\n if (distance < nearestDistance) {\n nearestDistance = distance;\n nearestSegmentIndex = i;\n }\n }\n\n return nearestSegmentIndex;\n },\n\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) return [];\n\n const { timescale, segments } = trackData;\n const ranges: SegmentTimeRange[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentStartMs = (segment.cts / timescale) * 1000;\n const segmentEndMs =\n ((segment.cts + segment.duration) / timescale) * 1000;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n ranges.push({\n segmentId: i,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return ranges;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ManifestIndex — backed by ManifestResponse (JIT transcoding)\n// ---------------------------------------------------------------------------\n\nexport function createManifestIndex(manifest: ManifestResponse): SegmentIndex {\n const durationMs = manifest.durationMs;\n const tracks: TrackSet = {};\n\n if (manifest.videoRenditions && manifest.videoRenditions.length > 0) {\n const r = manifest.videoRenditions[0]!;\n tracks.video = {\n role: \"video\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n\n const scrubRendition = manifest.videoRenditions.find(\n (v) => v.id === \"scrub\",\n );\n if (scrubRendition) {\n tracks.scrub = {\n role: \"scrub\",\n id: scrubRendition.id,\n src: manifest.sourceUrl,\n segmentDurationMs: scrubRendition.segmentDurationMs,\n segmentDurationsMs: scrubRendition.segmentDurationsMs,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n };\n }\n }\n\n if (manifest.audioRenditions && manifest.audioRenditions.length > 0) {\n const r = manifest.audioRenditions[0]!;\n tracks.audio = {\n role: \"audio\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n }\n\n function computeSegmentIdForTrack(\n desiredSeekTimeMs: number,\n track: TrackRef,\n ): number | undefined {\n if (desiredSeekTimeMs > durationMs) {\n return undefined;\n }\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = cumulativeTime + segmentDuration;\n\n const isLastSegment = i === track.segmentDurationsMs.length - 1;\n const includesEndTime =\n isLastSegment && desiredSeekTimeMs === durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1;\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return undefined;\n }\n\n if (!track.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(\n desiredSeekTimeMs / track.segmentDurationMs,\n );\n const segmentStartMs = segmentIndex * track.segmentDurationMs;\n if (segmentStartMs >= durationMs) {\n return undefined;\n }\n return segmentIndex + 1;\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n return computeSegmentIdForTrack(timeMs, track);\n },\n\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const segments: SegmentTimeRange[] = [];\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) continue;\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = Math.min(\n cumulativeTime + segmentDuration,\n durationMs,\n );\n\n if (segmentStartMs >= durationMs) break;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId: i + 1,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return segments;\n }\n\n const segmentDurationMs = track.segmentDurationMs || 1000;\n const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);\n const endSegmentIndex = Math.floor(toMs / segmentDurationMs);\n\n for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {\n const segmentId = i + 1;\n const segmentStartMs = i * segmentDurationMs;\n const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);\n\n if (segmentStartMs >= durationMs) break;\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return segments;\n },\n };\n}\n"],"mappings":";;;AA6CA,SAAgB,oBACd,MACA,KACc;CAKd,MAAM,aAJkB,OAAO,OAAO,KAAK,CAAC,QACzC,KAAK,aAAa,KAAK,IAAI,KAAK,SAAS,WAAW,SAAS,UAAU,EACxE,EACD,GACoC;CAErC,MAAM,aAAa,OAAO,OAAO,KAAK,CAAC,MAAM,MAAM,EAAE,SAAS,QAAQ;CACtE,MAAM,aAAa,OAAO,OAAO,KAAK,CAAC,MACpC,MAAM,EAAE,SAAS,WAAW,EAAE,UAAU,UAAa,EAAE,QAAQ,EACjE;CACD,MAAM,aAAa,KAAK;CAExB,MAAMA,SAAmB,EAAE;AAE3B,KAAI,cAAc,WAAW,UAAU,OACrC,QAAO,QAAQ;EACb,MAAM;EACN,IAAI,WAAW;EACf;EACA,mBAAmB,WAAW;EAC/B;AAGH,KAAI,cAAc,WAAW,UAAU,OACrC,QAAO,QAAQ;EACb,MAAM;EACN,IAAI,WAAW;EACf;EACD;AAGH,KAAI,cAAc,WAAW,UAAU,QAAW;EAChD,MAAM,qBACJ,WAAW,SAAS,SAAS,IACzB,WAAW,SAAS,KACjB,MAAO,EAAE,WAAW,WAAW,YAAa,IAC9C,GACD;AACN,SAAO,QAAQ;GACb,MAAM;GACN,IAAI,WAAW;GACf;GACA,mBAAmB;GACnB;GACA,mBAAmB,WAAW;GAC/B;;AAGH,QAAO;EACL;EACA;EAEA,UAAU,QAAgB,OAAqC;GAC7D,MAAM,UACJ,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;GACzE,MAAM,YAAY,KAAK;AACvB,OAAI,CAAC,UACH,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAE/C,MAAM,EAAE,WAAW,aAAa;GAIhC,MAAM,iBAAiB,oBADE,oBAAoB,UADnB,MAAM,qBAAqB,GACmB,EACX,UAAU;AAEvE,QAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;IAC7C,MAAM,UAAU,SAAS;IACzB,MAAM,iBAAiB,QAAQ,MAAM,QAAQ;AAC7C,QAAI,QAAQ,OAAO,kBAAkB,iBAAiB,eACpD,QAAO;;GAKX,IAAI,sBAAsB;GAC1B,IAAI,kBAAkB,OAAO;AAE7B,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;IACxC,MAAM,UAAU,SAAS;IACzB,MAAM,iBAAiB,QAAQ,MAAM,QAAQ;IAE7C,IAAIC;AACJ,QAAI,iBAAiB,QAAQ,IAC3B,YAAW,QAAQ,MAAM;aAChB,kBAAkB,eAC3B,YAAW,iBAAiB;QAE5B,QAAO;AAGT,QAAI,WAAW,iBAAiB;AAC9B,uBAAkB;AAClB,2BAAsB;;;AAI1B,UAAO;;EAGT,gBACE,QACA,MACA,OACoB;AACpB,OAAI,UAAU,KAAM,QAAO,EAAE;GAI7B,MAAM,YAAY,KADhB,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;AAEzE,OAAI,CAAC,UAAW,QAAO,EAAE;GAEzB,MAAM,EAAE,WAAW,aAAa;GAChC,MAAMC,SAA6B,EAAE;AAErC,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;IACxC,MAAM,UAAU,SAAS;IACzB,MAAM,iBAAkB,QAAQ,MAAM,YAAa;IACnD,MAAM,gBACF,QAAQ,MAAM,QAAQ,YAAY,YAAa;AAEnD,QAAI,iBAAiB,QAAQ,eAAe,OAC1C,QAAO,KAAK;KACV,WAAW;KACX,SAAS;KACT,OAAO;KACR,CAAC;;AAIN,UAAO;;EAEV;;AAOH,SAAgB,oBAAoB,UAA0C;CAC5E,MAAM,aAAa,SAAS;CAC5B,MAAMF,SAAmB,EAAE;AAE3B,KAAI,SAAS,mBAAmB,SAAS,gBAAgB,SAAS,GAAG;EACnE,MAAM,IAAI,SAAS,gBAAgB;AACnC,SAAO,QAAQ;GACb,MAAM;GACN,IAAI,EAAE;GACN,KAAK,SAAS;GACd,mBAAmB,EAAE;GACrB,oBAAoB,EAAE;GACtB,mBAAmB,EAAE;GACtB;EAED,MAAM,iBAAiB,SAAS,gBAAgB,MAC7C,MAAM,EAAE,OAAO,QACjB;AACD,MAAI,eACF,QAAO,QAAQ;GACb,MAAM;GACN,IAAI,eAAe;GACnB,KAAK,SAAS;GACd,mBAAmB,eAAe;GAClC,oBAAoB,eAAe;GACnC,mBAAmB,eAAe;GACnC;;AAIL,KAAI,SAAS,mBAAmB,SAAS,gBAAgB,SAAS,GAAG;EACnE,MAAM,IAAI,SAAS,gBAAgB;AACnC,SAAO,QAAQ;GACb,MAAM;GACN,IAAI,EAAE;GACN,KAAK,SAAS;GACd,mBAAmB,EAAE;GACrB,oBAAoB,EAAE;GACtB,mBAAmB,EAAE;GACtB;;CAGH,SAAS,yBACP,mBACA,OACoB;AACpB,MAAI,oBAAoB,WACtB;AAGF,MAAI,MAAM,sBAAsB,MAAM,mBAAmB,SAAS,GAAG;GACnE,IAAI,iBAAiB;AACrB,QAAK,IAAI,IAAI,GAAG,IAAI,MAAM,mBAAmB,QAAQ,KAAK;IACxD,MAAM,kBAAkB,MAAM,mBAAmB;AACjD,QAAI,oBAAoB,OACtB,OAAM,IAAI,MAAM,gDAAgD;IAElE,MAAM,iBAAiB;IACvB,MAAM,eAAe,iBAAiB;IAGtC,MAAM,kBADgB,MAAM,MAAM,mBAAmB,SAAS,KAE3C,sBAAsB;AAEzC,QACE,qBAAqB,mBACpB,oBAAoB,gBAAgB,iBAErC,QAAO,IAAI;AAGb,sBAAkB;AAClB,QAAI,kBAAkB,WAAY;;AAEpC;;AAGF,MAAI,CAAC,MAAM,kBACT,OAAM,IAAI,MAAM,gDAAgD;EAGlE,MAAM,eAAe,KAAK,MACxB,oBAAoB,MAAM,kBAC3B;AAED,MADuB,eAAe,MAAM,qBACtB,WACpB;AAEF,SAAO,eAAe;;AAGxB,QAAO;EACL;EACA;EAEA,UAAU,QAAgB,OAAqC;AAC7D,UAAO,yBAAyB,QAAQ,MAAM;;EAGhD,gBACE,QACA,MACA,OACoB;AACpB,OAAI,UAAU,KAAM,QAAO,EAAE;GAE7B,MAAMG,WAA+B,EAAE;AAEvC,OAAI,MAAM,sBAAsB,MAAM,mBAAmB,SAAS,GAAG;IACnE,IAAI,iBAAiB;AACrB,SAAK,IAAI,IAAI,GAAG,IAAI,MAAM,mBAAmB,QAAQ,KAAK;KACxD,MAAM,kBAAkB,MAAM,mBAAmB;AACjD,SAAI,oBAAoB,OAAW;KACnC,MAAM,iBAAiB;KACvB,MAAM,eAAe,KAAK,IACxB,iBAAiB,iBACjB,WACD;AAED,SAAI,kBAAkB,WAAY;AAElC,SAAI,iBAAiB,QAAQ,eAAe,OAC1C,UAAS,KAAK;MACZ,WAAW,IAAI;MACf,SAAS;MACT,OAAO;MACR,CAAC;AAGJ,uBAAkB;AAClB,SAAI,kBAAkB,WAAY;;AAEpC,WAAO;;GAGT,MAAM,oBAAoB,MAAM,qBAAqB;GACrD,MAAM,oBAAoB,KAAK,MAAM,SAAS,kBAAkB;GAChE,MAAM,kBAAkB,KAAK,MAAM,OAAO,kBAAkB;AAE5D,QAAK,IAAI,IAAI,mBAAmB,KAAK,iBAAiB,KAAK;IACzD,MAAM,YAAY,IAAI;IACtB,MAAM,iBAAiB,IAAI;IAC3B,MAAM,eAAe,KAAK,KAAK,IAAI,KAAK,mBAAmB,WAAW;AAEtE,QAAI,kBAAkB,WAAY;AAClC,QAAI,iBAAiB,QAAQ,eAAe,OAC1C,UAAS,KAAK;KACZ;KACA,SAAS;KACT,OAAO;KACR,CAAC;;AAIN,UAAO;;EAEV"}
|
|
1
|
+
{"version":3,"file":"SegmentIndex.js","names":["tracks: TrackSet","distance: number","ranges: SegmentTimeRange[]","segments: SegmentTimeRange[]"],"sources":["../../../src/elements/EFMedia/SegmentIndex.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport { convertToScaledTime, roundToMilliseconds } from \"./shared/PrecisionUtils.js\";\n\nexport type TrackRole = \"video\" | \"audio\" | \"scrub\";\n\nexport interface TrackRef {\n readonly role: TrackRole;\n readonly id: string | number;\n readonly src: string;\n readonly segmentDurationMs?: number;\n readonly segmentDurationsMs?: number[];\n readonly startTimeOffsetMs?: number;\n}\n\nexport interface TrackSet {\n video?: TrackRef;\n audio?: TrackRef;\n scrub?: TrackRef;\n}\n\nexport interface SegmentTimeRange {\n segmentId: number;\n startMs: number;\n endMs: number;\n}\n\nexport interface SegmentIndex {\n readonly durationMs: number;\n readonly tracks: TrackSet;\n segmentAt(timeMs: number, track: TrackRef): number | undefined;\n segmentsInRange(fromMs: number, toMs: number, track: TrackRef): SegmentTimeRange[];\n}\n\n// ---------------------------------------------------------------------------\n// FragmentIndex — backed by TrackFragmentIndex (local and file-id files)\n// ---------------------------------------------------------------------------\n\nexport function createFragmentIndex(\n data: Record<number, TrackFragmentIndex>,\n src: string,\n): SegmentIndex {\n const longestFragment = Object.values(data).reduce(\n (max, fragment) => Math.max(max, fragment.duration / fragment.timescale),\n 0,\n );\n const durationMs = longestFragment * 1000;\n\n const audioTrack = Object.values(data).find((t) => t.type === \"audio\");\n const videoTrack = Object.values(data).find(\n (t) => t.type === \"video\" && t.track !== undefined && t.track > 0,\n );\n const scrubTrack = data[-1];\n\n const tracks: TrackSet = {};\n\n if (videoTrack && videoTrack.track !== undefined) {\n tracks.video = {\n role: \"video\",\n id: videoTrack.track,\n src,\n startTimeOffsetMs: videoTrack.startTimeOffsetMs,\n };\n }\n\n if (audioTrack && audioTrack.track !== undefined) {\n tracks.audio = {\n role: \"audio\",\n id: audioTrack.track,\n src,\n };\n }\n\n if (scrubTrack && scrubTrack.track !== undefined) {\n const segmentDurationsMs =\n scrubTrack.segments.length > 0\n ? scrubTrack.segments.map((s) => (s.duration / scrubTrack.timescale) * 1000)\n : undefined;\n tracks.scrub = {\n role: \"scrub\",\n id: scrubTrack.track,\n src,\n segmentDurationMs: 30000,\n segmentDurationsMs,\n startTimeOffsetMs: scrubTrack.startTimeOffsetMs,\n };\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n const trackId = typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) {\n throw new Error(`Track ${trackId} not found`);\n }\n const { timescale, segments } = trackData;\n\n const startTimeOffsetMs = track.startTimeOffsetMs || 0;\n const offsetSeekTimeMs = roundToMilliseconds(timeMs + startTimeOffsetMs);\n const scaledSeekTime = convertToScaledTime(offsetSeekTimeMs, timescale);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) {\n return i;\n }\n }\n\n // Gap handling: find nearest segment\n let nearestSegmentIndex = 0;\n let nearestDistance = Number.MAX_SAFE_INTEGER;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n\n let distance: number;\n if (scaledSeekTime < segment.cts) {\n distance = segment.cts - scaledSeekTime;\n } else if (scaledSeekTime >= segmentEndTime) {\n distance = scaledSeekTime - segmentEndTime;\n } else {\n return i;\n }\n\n if (distance < nearestDistance) {\n nearestDistance = distance;\n nearestSegmentIndex = i;\n }\n }\n\n return nearestSegmentIndex;\n },\n\n segmentsInRange(fromMs: number, toMs: number, track: TrackRef): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const trackId = typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) return [];\n\n const { timescale, segments } = trackData;\n const ranges: SegmentTimeRange[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentStartMs = (segment.cts / timescale) * 1000;\n const segmentEndMs = ((segment.cts + segment.duration) / timescale) * 1000;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n ranges.push({\n segmentId: i,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return ranges;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ManifestIndex — backed by ManifestResponse (JIT transcoding)\n// ---------------------------------------------------------------------------\n\nexport function createManifestIndex(manifest: ManifestResponse): SegmentIndex {\n const durationMs = manifest.durationMs;\n const tracks: TrackSet = {};\n\n if (manifest.videoRenditions && manifest.videoRenditions.length > 0) {\n const r = manifest.videoRenditions[0]!;\n tracks.video = {\n role: \"video\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n\n const scrubRendition = manifest.videoRenditions.find((v) => v.id === \"scrub\");\n if (scrubRendition) {\n tracks.scrub = {\n role: \"scrub\",\n id: scrubRendition.id,\n src: manifest.sourceUrl,\n segmentDurationMs: scrubRendition.segmentDurationMs,\n segmentDurationsMs: scrubRendition.segmentDurationsMs,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n };\n }\n }\n\n if (manifest.audioRenditions && manifest.audioRenditions.length > 0) {\n const r = manifest.audioRenditions[0]!;\n tracks.audio = {\n role: \"audio\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n }\n\n function computeSegmentIdForTrack(\n desiredSeekTimeMs: number,\n track: TrackRef,\n ): number | undefined {\n if (desiredSeekTimeMs > durationMs) {\n return undefined;\n }\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = cumulativeTime + segmentDuration;\n\n const isLastSegment = i === track.segmentDurationsMs.length - 1;\n const includesEndTime = isLastSegment && desiredSeekTimeMs === durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1;\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return undefined;\n }\n\n if (!track.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(desiredSeekTimeMs / track.segmentDurationMs);\n const segmentStartMs = segmentIndex * track.segmentDurationMs;\n if (segmentStartMs >= durationMs) {\n return undefined;\n }\n return segmentIndex + 1;\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n return computeSegmentIdForTrack(timeMs, track);\n },\n\n segmentsInRange(fromMs: number, toMs: number, track: TrackRef): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const segments: SegmentTimeRange[] = [];\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) continue;\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = Math.min(cumulativeTime + segmentDuration, durationMs);\n\n if (segmentStartMs >= durationMs) break;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId: i + 1,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return segments;\n }\n\n const segmentDurationMs = track.segmentDurationMs || 1000;\n const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);\n const endSegmentIndex = Math.floor(toMs / segmentDurationMs);\n\n for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {\n const segmentId = i + 1;\n const segmentStartMs = i * segmentDurationMs;\n const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);\n\n if (segmentStartMs >= durationMs) break;\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return segments;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentTransport.js","names":[],"sources":["../../../src/elements/EFMedia/SegmentTransport.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { RenditionId } from \"../../transcoding/types/index.js\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\nimport type { CachedFetcher } from \"./CachedFetcher.js\";\n\nexport interface SegmentTransport {\n fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;\n fetchMediaSegment(
|
|
1
|
+
{"version":3,"file":"SegmentTransport.js","names":[],"sources":["../../../src/elements/EFMedia/SegmentTransport.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { RenditionId } from \"../../transcoding/types/index.js\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\nimport type { CachedFetcher } from \"./CachedFetcher.js\";\n\nexport interface SegmentTransport {\n fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;\n fetchMediaSegment(segmentId: number, track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;\n isCached(segmentId: number, track: TrackRef): boolean;\n}\n\n// ---------------------------------------------------------------------------\n// UrlTransport — each segment has its own URL\n// Used by AssetMediaEngine (via JIT URLs) and JitMediaEngine natively.\n// ---------------------------------------------------------------------------\n\ninterface UrlTransportOptions {\n fetcher: CachedFetcher;\n src: string;\n templates: { initSegment: string; mediaSegment: string };\n audioTrackId: number | undefined;\n videoTrackId: number | undefined;\n}\n\nfunction resolveRenditionId(track: TrackRef): RenditionId {\n if (track.role === \"audio\") return \"audio\";\n if (track.role === \"scrub\") return \"scrub\";\n if (typeof track.id === \"string\") return track.id as RenditionId;\n // For numeric IDs (fragment-based), map to JIT rendition names\n if (track.id === -1) return \"scrub\";\n if (track.id === 2) return \"audio\";\n return \"high\";\n}\n\nexport function createUrlTransport(opts: UrlTransportOptions): SegmentTransport {\n const { fetcher, src, templates, audioTrackId, videoTrackId } = opts;\n\n function buildSegmentUrl(segmentId: \"init\" | number, track: TrackRef): string {\n const renditionId = resolveRenditionId(track);\n const template = segmentId === \"init\" ? templates.initSegment : templates.mediaSegment;\n const trackId =\n typeof track.id === \"number\"\n ? track.id\n : track.role === \"audio\"\n ? audioTrackId\n : videoTrackId;\n return template\n .replace(\"{rendition}\", renditionId)\n .replace(\"{segmentId}\", segmentId.toString())\n .replace(\"{src}\", src)\n .replace(\"{trackId}\", trackId?.toString() ?? \"\");\n }\n\n return {\n async fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer> {\n const url = buildSegmentUrl(\"init\", track);\n return fetcher.fetchArrayBuffer(url, signal);\n },\n\n async fetchMediaSegment(\n segmentId: number,\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const url = buildSegmentUrl(segmentId, track);\n return fetcher.fetchArrayBuffer(url, signal);\n },\n\n isCached(segmentId: number, track: TrackRef): boolean {\n const url = buildSegmentUrl(segmentId, track);\n return fetcher.has(url);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ByteRangeTransport — fetches full track binary, slices segments\n// Used by FileMediaEngine.\n// ---------------------------------------------------------------------------\n\nexport function createByteRangeTransport(\n data: Record<number, TrackFragmentIndex>,\n fileId: string,\n apiHost: string,\n fetcher: CachedFetcher,\n): SegmentTransport {\n function buildTrackUrl(trackId: number): string {\n return `${apiHost}/api/v1/files/${fileId}/tracks/${trackId}`;\n }\n\n function getTrackId(track: TrackRef): number {\n const trackId = typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n if (Number.isNaN(trackId)) {\n throw new Error(`Invalid track ID: ${track.id}`);\n }\n return trackId;\n }\n\n return {\n async fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer> {\n const trackId = getTrackId(track);\n const trackData = data[trackId];\n if (!trackData) throw new Error(`Track ${trackId} not found`);\n\n const { offset, size } = trackData.initSegment;\n const url = buildTrackUrl(trackId);\n const fullTrack = await fetcher.fetchArrayBuffer(url, signal);\n return fullTrack.slice(offset, offset + size);\n },\n\n async fetchMediaSegment(\n segmentId: number,\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const trackId = getTrackId(track);\n const trackData = data[trackId];\n if (!trackData) throw new Error(`Track ${trackId} not found`);\n\n const segment = trackData.segments[segmentId];\n if (!segment) {\n throw new Error(`Segment ${segmentId} not found for track ${trackId}`);\n }\n\n const url = buildTrackUrl(trackId);\n const fullTrack = await fetcher.fetchArrayBuffer(url, signal);\n return fullTrack.slice(segment.offset, segment.offset + segment.size);\n },\n\n isCached(_segmentId: number, track: TrackRef): boolean {\n const trackId = getTrackId(track);\n const url = buildTrackUrl(trackId);\n return fetcher.has(url);\n },\n };\n}\n"],"mappings":";AAwBA,SAAS,mBAAmB,OAA8B;AACxD,KAAI,MAAM,SAAS,QAAS,QAAO;AACnC,KAAI,MAAM,SAAS,QAAS,QAAO;AACnC,KAAI,OAAO,MAAM,OAAO,SAAU,QAAO,MAAM;AAE/C,KAAI,MAAM,OAAO,GAAI,QAAO;AAC5B,KAAI,MAAM,OAAO,EAAG,QAAO;AAC3B,QAAO;;AAGT,SAAgB,mBAAmB,MAA6C;CAC9E,MAAM,EAAE,SAAS,KAAK,WAAW,cAAc,iBAAiB;CAEhE,SAAS,gBAAgB,WAA4B,OAAyB;EAC5E,MAAM,cAAc,mBAAmB,MAAM;EAC7C,MAAM,WAAW,cAAc,SAAS,UAAU,cAAc,UAAU;EAC1E,MAAM,UACJ,OAAO,MAAM,OAAO,WAChB,MAAM,KACN,MAAM,SAAS,UACb,eACA;AACR,SAAO,SACJ,QAAQ,eAAe,YAAY,CACnC,QAAQ,eAAe,UAAU,UAAU,CAAC,CAC5C,QAAQ,SAAS,IAAI,CACrB,QAAQ,aAAa,SAAS,UAAU,IAAI,GAAG;;AAGpD,QAAO;EACL,MAAM,iBAAiB,OAAiB,QAA2C;GACjF,MAAM,MAAM,gBAAgB,QAAQ,MAAM;AAC1C,UAAO,QAAQ,iBAAiB,KAAK,OAAO;;EAG9C,MAAM,kBACJ,WACA,OACA,QACsB;GACtB,MAAM,MAAM,gBAAgB,WAAW,MAAM;AAC7C,UAAO,QAAQ,iBAAiB,KAAK,OAAO;;EAG9C,SAAS,WAAmB,OAA0B;GACpD,MAAM,MAAM,gBAAgB,WAAW,MAAM;AAC7C,UAAO,QAAQ,IAAI,IAAI;;EAE1B;;AAQH,SAAgB,yBACd,MACA,QACA,SACA,SACkB;CAClB,SAAS,cAAc,SAAyB;AAC9C,SAAO,GAAG,QAAQ,gBAAgB,OAAO,UAAU;;CAGrD,SAAS,WAAW,OAAyB;EAC3C,MAAM,UAAU,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;AACvF,MAAI,OAAO,MAAM,QAAQ,CACvB,OAAM,IAAI,MAAM,qBAAqB,MAAM,KAAK;AAElD,SAAO;;AAGT,QAAO;EACL,MAAM,iBAAiB,OAAiB,QAA2C;GACjF,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,YAAY,KAAK;AACvB,OAAI,CAAC,UAAW,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAE7D,MAAM,EAAE,QAAQ,SAAS,UAAU;GACnC,MAAM,MAAM,cAAc,QAAQ;AAElC,WADkB,MAAM,QAAQ,iBAAiB,KAAK,OAAO,EAC5C,MAAM,QAAQ,SAAS,KAAK;;EAG/C,MAAM,kBACJ,WACA,OACA,QACsB;GACtB,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,YAAY,KAAK;AACvB,OAAI,CAAC,UAAW,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAE7D,MAAM,UAAU,UAAU,SAAS;AACnC,OAAI,CAAC,QACH,OAAM,IAAI,MAAM,WAAW,UAAU,uBAAuB,UAAU;GAGxE,MAAM,MAAM,cAAc,QAAQ;AAElC,WADkB,MAAM,QAAQ,iBAAiB,KAAK,OAAO,EAC5C,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,KAAK;;EAGvE,SAAS,YAAoB,OAA0B;GAErD,MAAM,MAAM,cADI,WAAW,MAAM,CACC;AAClC,UAAO,QAAQ,IAAI,IAAI;;EAE1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimingModel.js","names":[],"sources":["../../../src/elements/EFMedia/TimingModel.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\n\nexport interface TimingModel {\n toContainerSeconds(
|
|
1
|
+
{"version":3,"file":"TimingModel.js","names":[],"sources":["../../../src/elements/EFMedia/TimingModel.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\n\nexport interface TimingModel {\n toContainerSeconds(timeMs: number, segmentId: number, track: TrackRef): number;\n}\n\n/**\n * For byte-range sliced segments from full track files (FileMediaEngine).\n * mediabunny sees segment-relative timestamps since we sliced at segment boundaries,\n * so we subtract the segment's CTS to get relative time.\n */\nexport function createByteRangeTiming(data: Record<number, TrackFragmentIndex>): TimingModel {\n return {\n toContainerSeconds(timeMs: number, segmentId: number, track: TrackRef): number {\n const trackId = typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) throw new Error(\"Track not found\");\n\n const segment = trackData.segments[segmentId];\n if (!segment) throw new Error(\"Segment not found\");\n\n const segmentStartMs = (segment.cts / trackData.timescale) * 1000;\n return (timeMs - segmentStartMs) / 1000;\n },\n };\n}\n\n/**\n * For JIT transcoded segments (JitMediaEngine).\n * Segments are self-contained — just convert ms to seconds.\n */\nexport function createJitTiming(): TimingModel {\n return {\n toContainerSeconds(timeMs: number, _segmentId: number, _track: TrackRef): number {\n return timeMs / 1000;\n },\n };\n}\n"],"mappings":";;;;;;AAYA,SAAgB,sBAAsB,MAAuD;AAC3F,QAAO,EACL,mBAAmB,QAAgB,WAAmB,OAAyB;EAE7E,MAAM,YAAY,KADF,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;AAEvF,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,kBAAkB;EAElD,MAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oBAAoB;AAGlD,UAAQ,SADgB,QAAQ,MAAM,UAAU,YAAa,OAC1B;IAEtC;;;;;;AAOH,SAAgB,kBAA+B;AAC7C,QAAO,EACL,mBAAmB,QAAgB,YAAoB,QAA0B;AAC/E,SAAO,SAAS;IAEnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type { AudioSpan, MediaEngine, SegmentTimeRange } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\n\n/**\n * Fetch audio segment data using MediaEngine\n * Pure function with explicit dependencies\n */\nconst fetchAudioSegmentData = async (\n segmentIds: number[],\n mediaEngine: MediaEngine,\n signal: AbortSignal,\n): Promise<Map<number, ArrayBuffer>> => {\n const audioTrack = mediaEngine.tracks.audio;\n if (!audioTrack) {\n throw new Error(\"Audio track not available\");\n }\n\n const segmentData = new Map<number, ArrayBuffer>();\n\n const fetchPromises = segmentIds.map(async (segmentId) => {\n const arrayBuffer = await mediaEngine.transport.fetchMediaSegment(\n segmentId,\n audioTrack,\n signal,\n );\n return [segmentId, arrayBuffer] as [number, ArrayBuffer];\n });\n\n const fetchedSegments = await Promise.all(fetchPromises);\n signal.throwIfAborted();\n\n for (const [segmentId, arrayBuffer] of fetchedSegments) {\n segmentData.set(segmentId, arrayBuffer);\n }\n\n return segmentData;\n};\n\n/**\n * Create audio span blob from init segment and media segments\n * Pure function for blob creation\n */\nconst createAudioSpanBlob = (initSegment: ArrayBuffer, mediaSegments: ArrayBuffer[]): Blob => {\n const chunks = [initSegment, ...mediaSegments];\n return new Blob(chunks, { type: \"audio/mp4\" });\n};\n\n/**\n * Fetch audio spanning a time range\n * Main function that orchestrates segment calculation, fetching, and blob creation\n */\nexport const fetchAudioSpanningTime = async (\n host: EFMedia,\n fromMs: number,\n toMs: number,\n signal: AbortSignal,\n): Promise<AudioSpan | undefined> => {\n // Validate inputs\n if (fromMs >= toMs || fromMs < 0) {\n throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);\n }\n\n // Get media engine using the new async method\n const mediaEngine = await host.getMediaEngine(signal);\n signal.throwIfAborted();\n\n const audioTrack = mediaEngine?.tracks.audio;\n if (!audioTrack) {\n return undefined;\n }\n\n // Fetch the init segment\n const initSegment = await mediaEngine.transport.fetchInitSegment(audioTrack, signal);\n signal.throwIfAborted();\n\n if (!initSegment) {\n return undefined;\n }\n\n // Calculate segments needed\n const segmentRanges = mediaEngine.index.segmentsInRange(fromMs, toMs, audioTrack);\n\n if (segmentRanges.length === 0) {\n throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);\n }\n\n // Fetch segment data\n const segmentIds = segmentRanges.map((r: SegmentTimeRange) => r.segmentId);\n const segmentData = await fetchAudioSegmentData(segmentIds, mediaEngine, signal);\n\n // Create ordered array of segments\n const orderedSegments = segmentIds.map((id: number) => {\n const segment = segmentData.get(id);\n if (!segment) {\n throw new Error(`Missing segment data for segment ID ${id}`);\n }\n return segment;\n });\n\n // Create blob\n const blob = createAudioSpanBlob(initSegment, orderedSegments);\n\n // Calculate actual time boundaries\n const actualStartMs = Math.min(...segmentRanges.map((r: SegmentTimeRange) => r.startMs));\n const actualEndMs = Math.max(...segmentRanges.map((r: SegmentTimeRange) => r.endMs));\n\n return {\n startMs: actualStartMs,\n endMs: actualEndMs,\n blob,\n };\n};\n"],"mappings":";;;;;AAOA,MAAM,wBAAwB,OAC5B,YACA,aACA,WACsC;CACtC,MAAM,aAAa,YAAY,OAAO;AACtC,KAAI,CAAC,WACH,OAAM,IAAI,MAAM,4BAA4B;CAG9C,MAAM,8BAAc,IAAI,KAA0B;CAElD,MAAM,gBAAgB,WAAW,IAAI,OAAO,cAAc;AAMxD,SAAO,CAAC,WALY,MAAM,YAAY,UAAU,kBAC9C,WACA,YACA,OACD,CAC8B;GAC/B;CAEF,MAAM,kBAAkB,MAAM,QAAQ,IAAI,cAAc;AACxD,QAAO,gBAAgB;AAEvB,MAAK,MAAM,CAAC,WAAW,gBAAgB,gBACrC,aAAY,IAAI,WAAW,YAAY;AAGzC,QAAO;;;;;;AAOT,MAAM,uBAAuB,aAA0B,kBAAuC;CAC5F,MAAM,SAAS,CAAC,aAAa,GAAG,cAAc;AAC9C,QAAO,IAAI,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;;;;;;AAOhD,MAAa,yBAAyB,OACpC,MACA,QACA,MACA,WACmC;AAEnC,KAAI,UAAU,QAAQ,SAAS,EAC7B,OAAM,IAAI,MAAM,8BAA8B,OAAO,SAAS,OAAO;CAIvE,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,QAAO,gBAAgB;CAEvB,MAAM,aAAa,aAAa,OAAO;AACvC,KAAI,CAAC,WACH;CAIF,MAAM,cAAc,MAAM,YAAY,UAAU,iBAAiB,YAAY,OAAO;AACpF,QAAO,gBAAgB;AAEvB,KAAI,CAAC,YACH;CAIF,MAAM,gBAAgB,YAAY,MAAM,gBAAgB,QAAQ,MAAM,WAAW;AAEjF,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,oCAAoC,OAAO,GAAG,KAAK,IAAI;CAIzE,MAAM,aAAa,cAAc,KAAK,MAAwB,EAAE,UAAU;CAC1E,MAAM,cAAc,MAAM,sBAAsB,YAAY,aAAa,OAAO;CAYhF,MAAM,OAAO,oBAAoB,aATT,WAAW,KAAK,OAAe;EACrD,MAAM,UAAU,YAAY,IAAI,GAAG;AACnC,MAAI,CAAC,QACH,OAAM,IAAI,MAAM,uCAAuC,KAAK;AAE9D,SAAO;GACP,CAG4D;AAM9D,QAAO;EACL,SAJoB,KAAK,IAAI,GAAG,cAAc,KAAK,MAAwB,EAAE,QAAQ,CAAC;EAKtF,OAJkB,KAAK,IAAI,GAAG,cAAc,KAAK,MAAwB,EAAE,MAAM,CAAC;EAKlF;EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlobalInputCache.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/GlobalInputCache.ts"],"sourcesContent":["import type { Input } from \"mediabunny\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\n\n/**\n * Global cache for MediaBunny Input instances\n * Shared across all MediaEngine instances to prevent duplicate decoding\n * of the same segment data\n */\nclass GlobalInputCache {\n private cache = new LRUCache<string, Input>(50); // 50 Input instances max\n\n /**\n * Generate standardized cache key for Input objects\n * Format: \"input:{src}:{segmentId}:{renditionId}\"\n */\n private generateKey(
|
|
1
|
+
{"version":3,"file":"GlobalInputCache.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/GlobalInputCache.ts"],"sourcesContent":["import type { Input } from \"mediabunny\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\n\n/**\n * Global cache for MediaBunny Input instances\n * Shared across all MediaEngine instances to prevent duplicate decoding\n * of the same segment data\n */\nclass GlobalInputCache {\n private cache = new LRUCache<string, Input>(50); // 50 Input instances max\n\n /**\n * Generate standardized cache key for Input objects\n * Format: \"input:{src}:{segmentId}:{renditionId}\"\n */\n private generateKey(src: string, segmentId: number, renditionId?: string): string {\n return `input:${src}:${segmentId}:${renditionId || \"default\"}`;\n }\n\n /**\n * Get cached Input object\n */\n get(src: string, segmentId: number, renditionId?: string): Input | undefined {\n const key = this.generateKey(src, segmentId, renditionId);\n return this.cache.get(key);\n }\n\n /**\n * Cache Input object\n */\n set(src: string, segmentId: number, input: Input, renditionId?: string): void {\n const key = this.generateKey(src, segmentId, renditionId);\n this.cache.set(key, input);\n }\n\n /**\n * Check if Input is cached\n */\n has(src: string, segmentId: number, renditionId?: string): boolean {\n const key = this.generateKey(src, segmentId, renditionId);\n return this.cache.has(key);\n }\n\n /**\n * Clear all cached Input objects\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get cache statistics for debugging\n */\n getStats() {\n return {\n size: this.cache.size,\n cachedKeys: Array.from((this.cache as any).cache.keys()),\n };\n }\n}\n\n// Single global instance shared across all MediaEngine instances\nexport const globalInputCache = new GlobalInputCache();\n\n// Export for debugging (works in both browser and server)\n(globalThis as typeof globalThis & { debugInputCache: typeof globalInputCache }).debugInputCache =\n globalInputCache;\n"],"mappings":";;;;;;;;AAQA,IAAM,mBAAN,MAAuB;;eACL,IAAI,SAAwB,GAAG;;;;;;CAM/C,AAAQ,YAAY,KAAa,WAAmB,aAA8B;AAChF,SAAO,SAAS,IAAI,GAAG,UAAU,GAAG,eAAe;;;;;CAMrD,IAAI,KAAa,WAAmB,aAAyC;EAC3E,MAAM,MAAM,KAAK,YAAY,KAAK,WAAW,YAAY;AACzD,SAAO,KAAK,MAAM,IAAI,IAAI;;;;;CAM5B,IAAI,KAAa,WAAmB,OAAc,aAA4B;EAC5E,MAAM,MAAM,KAAK,YAAY,KAAK,WAAW,YAAY;AACzD,OAAK,MAAM,IAAI,KAAK,MAAM;;;;;CAM5B,IAAI,KAAa,WAAmB,aAA+B;EACjE,MAAM,MAAM,KAAK,YAAY,KAAK,WAAW,YAAY;AACzD,SAAO,KAAK,MAAM,IAAI,IAAI;;;;;CAM5B,QAAc;AACZ,OAAK,MAAM,OAAO;;;;;CAMpB,WAAW;AACT,SAAO;GACL,MAAM,KAAK,MAAM;GACjB,YAAY,MAAM,KAAM,KAAK,MAAc,MAAM,MAAM,CAAC;GACzD;;;AAKL,MAAa,mBAAmB,IAAI,kBAAkB;AAGtD,AAAC,WAAgF,kBAC/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrecisionUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/PrecisionUtils.ts"],"sourcesContent":["/**\n * Centralized precision utilities for consistent timing calculations across the media pipeline.\n *\n * The key insight is that floating-point precision errors can cause inconsistencies between:\n * 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)\n * 2. Sample finding logic (in SampleBuffer.find)\n * 3. Timeline mapping (in BufferedSeekingInput.seek)\n *\n * All timing calculations must use the same rounding strategy to ensure consistency.\n */\n\n/**\n * Round time to millisecond precision to handle floating-point precision issues.\n * Uses Math.round for consistent behavior across the entire pipeline.\n *\n * This function should be used for ALL time-related calculations that need to be\n * compared between different parts of the system.\n */\nexport const roundToMilliseconds = (timeMs: number): number => {\n // Round to 3 decimal places (microsecond precision)\n return Math.round(timeMs * 1000) / 1000;\n};\n\n/**\n * Convert media time (in seconds) to scaled time units using consistent rounding.\n * This is used in segment selection to convert from milliseconds to timescale units.\n */\nexport const convertToScaledTime = (
|
|
1
|
+
{"version":3,"file":"PrecisionUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/PrecisionUtils.ts"],"sourcesContent":["/**\n * Centralized precision utilities for consistent timing calculations across the media pipeline.\n *\n * The key insight is that floating-point precision errors can cause inconsistencies between:\n * 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)\n * 2. Sample finding logic (in SampleBuffer.find)\n * 3. Timeline mapping (in BufferedSeekingInput.seek)\n *\n * All timing calculations must use the same rounding strategy to ensure consistency.\n */\n\n/**\n * Round time to millisecond precision to handle floating-point precision issues.\n * Uses Math.round for consistent behavior across the entire pipeline.\n *\n * This function should be used for ALL time-related calculations that need to be\n * compared between different parts of the system.\n */\nexport const roundToMilliseconds = (timeMs: number): number => {\n // Round to 3 decimal places (microsecond precision)\n return Math.round(timeMs * 1000) / 1000;\n};\n\n/**\n * Convert media time (in seconds) to scaled time units using consistent rounding.\n * This is used in segment selection to convert from milliseconds to timescale units.\n */\nexport const convertToScaledTime = (timeMs: number, timescale: number): number => {\n const scaledTime = (timeMs / 1000) * timescale;\n return Math.round(scaledTime);\n};\n\n/**\n * Convert scaled time units back to media time (in milliseconds) using consistent rounding.\n * This is the inverse of convertToScaledTime.\n */\nexport const convertFromScaledTime = (scaledTime: number, timescale: number): number => {\n const timeMs = (scaledTime / timescale) * 1000;\n return roundToMilliseconds(timeMs);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,uBAAuB,WAA2B;AAE7D,QAAO,KAAK,MAAM,SAAS,IAAK,GAAG;;;;;;AAOrC,MAAa,uBAAuB,QAAgB,cAA8B;CAChF,MAAM,aAAc,SAAS,MAAQ;AACrC,QAAO,KAAK,MAAM,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThumbnailExtractor.js","names":["mediaEngine: MediaEngine"],"sources":["../../../../src/elements/EFMedia/shared/ThumbnailExtractor.ts"],"sourcesContent":["import { ALL_FORMATS, BlobSource, CanvasSink, Input } from \"mediabunny\";\nimport type { ThumbnailResult } from \"../../../transcoding/types/index.js\";\nimport type { MediaEngine } from \"../MediaEngine.js\";\nimport type { TrackRef } from \"../SegmentIndex.js\";\nimport { globalInputCache } from \"./GlobalInputCache.js\";\nimport { withTimeout, DEFAULT_MEDIABUNNY_TIMEOUT_MS } from \"./timeoutUtils.js\";\n\nexport class ThumbnailExtractor {\n constructor(private mediaEngine: MediaEngine) {}\n\n async extractThumbnails(\n timestamps: number[],\n track: TrackRef,\n durationMs: number,\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n if (timestamps.length === 0) {\n return [];\n }\n\n const validTimestamps = timestamps.filter(
|
|
1
|
+
{"version":3,"file":"ThumbnailExtractor.js","names":["mediaEngine: MediaEngine"],"sources":["../../../../src/elements/EFMedia/shared/ThumbnailExtractor.ts"],"sourcesContent":["import { ALL_FORMATS, BlobSource, CanvasSink, Input } from \"mediabunny\";\nimport type { ThumbnailResult } from \"../../../transcoding/types/index.js\";\nimport type { MediaEngine } from \"../MediaEngine.js\";\nimport type { TrackRef } from \"../SegmentIndex.js\";\nimport { globalInputCache } from \"./GlobalInputCache.js\";\nimport { withTimeout, DEFAULT_MEDIABUNNY_TIMEOUT_MS } from \"./timeoutUtils.js\";\n\nexport class ThumbnailExtractor {\n constructor(private mediaEngine: MediaEngine) {}\n\n async extractThumbnails(\n timestamps: number[],\n track: TrackRef,\n durationMs: number,\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n if (timestamps.length === 0) {\n return [];\n }\n\n const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);\n\n if (validTimestamps.length === 0) {\n console.warn(`ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`);\n return timestamps.map(() => null);\n }\n\n const segmentGroups = this.groupTimestampsBySegment(validTimestamps, track);\n const results = new Map<number, ThumbnailResult | null>();\n\n for (const [segmentId, segmentTimestamps] of segmentGroups) {\n signal?.throwIfAborted();\n\n try {\n const segmentResults = await this.extractSegmentThumbnails(\n segmentId,\n segmentTimestamps,\n track,\n signal,\n );\n\n for (const [timestamp, thumbnail] of segmentResults) {\n results.set(timestamp, thumbnail);\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n for (const timestamp of segmentTimestamps) {\n results.set(timestamp, null);\n }\n }\n }\n\n return timestamps.map((t) => {\n if (t < 0 || t > durationMs) {\n return null;\n }\n return results.get(t) || null;\n });\n }\n\n private groupTimestampsBySegment(timestamps: number[], track: TrackRef): Map<number, number[]> {\n const segmentGroups = new Map<number, number[]>();\n\n for (const timeMs of timestamps) {\n try {\n const segmentId = this.mediaEngine.index.segmentAt(timeMs, track);\n if (segmentId !== undefined) {\n if (!segmentGroups.has(segmentId)) {\n segmentGroups.set(segmentId, []);\n }\n const segmentGroup = segmentGroups.get(segmentId)!;\n segmentGroup.push(timeMs);\n }\n } catch (error) {\n console.warn(\n `ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`,\n error,\n );\n }\n }\n\n return segmentGroups;\n }\n\n private async extractSegmentThumbnails(\n segmentId: number,\n timestamps: number[],\n track: TrackRef,\n signal?: AbortSignal,\n ): Promise<Map<number, ThumbnailResult | null>> {\n const results = new Map<number, ThumbnailResult | null>();\n\n try {\n signal?.throwIfAborted();\n\n const initP = this.mediaEngine.transport.fetchInitSegment(track, signal!);\n const mediaP = this.mediaEngine.transport.fetchMediaSegment(segmentId, track, signal!);\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n\n signal?.throwIfAborted();\n\n const segmentBlob = new Blob([initSegment, mediaSegment]);\n const renditionId = typeof track.id === \"string\" ? track.id : undefined;\n\n let input = globalInputCache.get(track.src, segmentId, renditionId);\n if (!input) {\n input = new Input({\n formats: ALL_FORMATS,\n source: new BlobSource(segmentBlob),\n });\n globalInputCache.set(track.src, segmentId, input, renditionId);\n }\n\n const videoTrack = await withTimeout(\n input.getPrimaryVideoTrack(),\n 5000,\n \"ThumbnailExtractor.getPrimaryVideoTrack\",\n signal,\n );\n if (!videoTrack) {\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n return results;\n }\n\n const sink = new CanvasSink(videoTrack);\n const sortedTimestamps = [...timestamps].sort((a, b) => a - b);\n\n const relativeTimestamps = sortedTimestamps.map((ms) =>\n this.mediaEngine.timing.toContainerSeconds(ms, segmentId, track),\n );\n\n const timestampResults = [];\n const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);\n for await (const result of canvasIterator) {\n const canvasResult = await withTimeout(\n Promise.resolve(result),\n DEFAULT_MEDIABUNNY_TIMEOUT_MS,\n \"ThumbnailExtractor canvasesAtTimestamps iteration\",\n signal,\n );\n timestampResults.push(canvasResult);\n }\n\n for (let i = 0; i < sortedTimestamps.length; i++) {\n const globalTimestamp = sortedTimestamps[i];\n if (globalTimestamp === undefined) {\n continue;\n }\n\n const result = timestampResults[i];\n\n if (result?.canvas) {\n const canvas = result.canvas;\n if (canvas instanceof HTMLCanvasElement || canvas instanceof OffscreenCanvas) {\n results.set(globalTimestamp, {\n timestamp: globalTimestamp,\n thumbnail: canvas,\n });\n } else {\n results.set(globalTimestamp, null);\n }\n } else {\n results.set(globalTimestamp, null);\n }\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n }\n\n return results;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MainVideoInputCache.js","names":["#getCacheKey","#cache","#pendingPromises","#maxCacheSize"],"sources":["../../../../src/elements/EFMedia/videoTasks/MainVideoInputCache.ts"],"sourcesContent":["import type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\n/**\n * Cache for main video BufferedSeekingInput instances\n * Main video segments are typically 2s long, so we can reuse the same input\n * for multiple frames within that segment (e.g., 60 frames at 30fps)\n */\nexport class MainVideoInputCache {\n #cache = new Map<string, BufferedSeekingInput>();\n #pendingPromises = new Map
|
|
1
|
+
{"version":3,"file":"MainVideoInputCache.js","names":["#getCacheKey","#cache","#pendingPromises","#maxCacheSize"],"sources":["../../../../src/elements/EFMedia/videoTasks/MainVideoInputCache.ts"],"sourcesContent":["import type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\n/**\n * Cache for main video BufferedSeekingInput instances\n * Main video segments are typically 2s long, so we can reuse the same input\n * for multiple frames within that segment (e.g., 60 frames at 30fps)\n */\nexport class MainVideoInputCache {\n #cache = new Map<string, BufferedSeekingInput>();\n #pendingPromises = new Map<string, Promise<BufferedSeekingInput | undefined>>();\n #maxCacheSize = 10; // Keep last 10 main inputs (covers 20 seconds at 2s/segment)\n\n /**\n * Create a cache key that uniquely identifies a segment\n */\n #getCacheKey(src: string, segmentId: number, renditionId: string | undefined): string {\n return `${src}:${renditionId || \"default\"}:${segmentId}`;\n }\n\n /**\n * Get or create BufferedSeekingInput for a main video segment.\n *\n * Uses promise deduplication to prevent race conditions when multiple\n * concurrent requests arrive for the same segment. Without this,\n * the first segment often fails when DevTools is closed because:\n * 1. Video display and thumbnail extraction both request segment 0\n * 2. Both find cache empty and start createInputFn()\n * 3. Both create separate instances, causing conflicts\n */\n async getOrCreateInput(\n src: string,\n segmentId: number,\n renditionId: string | undefined,\n createInputFn: () => Promise<BufferedSeekingInput | undefined>,\n ): Promise<BufferedSeekingInput | undefined> {\n const cacheKey = this.#getCacheKey(src, segmentId, renditionId);\n\n // Check if we already have a completed result cached\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Check if there's already a pending request for this segment (deduplication!)\n // This prevents the race condition where multiple concurrent requests\n // each create their own BufferedSeekingInput instance.\n const pending = this.#pendingPromises.get(cacheKey);\n if (pending) {\n return pending;\n }\n\n // Create the promise and cache it IMMEDIATELY to prevent race conditions\n const promise = createInputFn()\n .then((input) => {\n // Clean up pending promise\n this.#pendingPromises.delete(cacheKey);\n\n if (input) {\n // Add to completed cache\n this.#cache.set(cacheKey, input);\n\n // Evict oldest entries if cache is too large (LRU-like behavior)\n if (this.#cache.size > this.#maxCacheSize) {\n const oldestKey = this.#cache.keys().next().value;\n if (oldestKey !== undefined) {\n this.#cache.delete(oldestKey);\n }\n }\n }\n\n return input;\n })\n .catch((error) => {\n // Clean up pending promise on failure so retry is possible\n this.#pendingPromises.delete(cacheKey);\n throw error;\n });\n\n this.#pendingPromises.set(cacheKey, promise);\n return promise;\n }\n\n /**\n * Clear the entire cache (called when video changes)\n */\n clear() {\n this.#cache.clear();\n this.#pendingPromises.clear();\n }\n\n /**\n * Get cache statistics\n */\n getStats() {\n return {\n size: this.#cache.size,\n pendingSize: this.#pendingPromises.size,\n cacheKeys: Array.from(this.#cache.keys()),\n };\n }\n}\n"],"mappings":";;;;;;AAOA,IAAa,sBAAb,MAAiC;CAC/B,yBAAS,IAAI,KAAmC;CAChD,mCAAmB,IAAI,KAAwD;CAC/E,gBAAgB;;;;CAKhB,aAAa,KAAa,WAAmB,aAAyC;AACpF,SAAO,GAAG,IAAI,GAAG,eAAe,UAAU,GAAG;;;;;;;;;;;;CAa/C,MAAM,iBACJ,KACA,WACA,aACA,eAC2C;EAC3C,MAAM,WAAW,MAAKA,YAAa,KAAK,WAAW,YAAY;EAG/D,MAAM,SAAS,MAAKC,MAAO,IAAI,SAAS;AACxC,MAAI,OACF,QAAO;EAMT,MAAM,UAAU,MAAKC,gBAAiB,IAAI,SAAS;AACnD,MAAI,QACF,QAAO;EAIT,MAAM,UAAU,eAAe,CAC5B,MAAM,UAAU;AAEf,SAAKA,gBAAiB,OAAO,SAAS;AAEtC,OAAI,OAAO;AAET,UAAKD,MAAO,IAAI,UAAU,MAAM;AAGhC,QAAI,MAAKA,MAAO,OAAO,MAAKE,cAAe;KACzC,MAAM,YAAY,MAAKF,MAAO,MAAM,CAAC,MAAM,CAAC;AAC5C,SAAI,cAAc,OAChB,OAAKA,MAAO,OAAO,UAAU;;;AAKnC,UAAO;IACP,CACD,OAAO,UAAU;AAEhB,SAAKC,gBAAiB,OAAO,SAAS;AACtC,SAAM;IACN;AAEJ,QAAKA,gBAAiB,IAAI,UAAU,QAAQ;AAC5C,SAAO;;;;;CAMT,QAAQ;AACN,QAAKD,MAAO,OAAO;AACnB,QAAKC,gBAAiB,OAAO;;;;;CAM/B,WAAW;AACT,SAAO;GACL,MAAM,MAAKD,MAAO;GAClB,aAAa,MAAKC,gBAAiB;GACnC,WAAW,MAAM,KAAK,MAAKD,MAAO,MAAM,CAAC;GAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrubInputCache.js","names":["cached","#urlCache","pending","#pendingByUrl","promise","#getCacheKey","#cache","#pendingBySegment","#maxCacheSize"],"sources":["../../../../src/elements/EFMedia/videoTasks/ScrubInputCache.ts"],"sourcesContent":["import type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\n/**\n * Cache for scrub BufferedSeekingInput instances.\n *\n * For JIT media (segmented scrub tracks), caches by src + segment ID.\n * For Asset media (single-file scrub tracks), caches by URL so all segments\n * share the same BufferedSeekingInput instance.\n *\n * Uses promise deduplication to prevent race conditions when multiple\n * concurrent requests arrive for the same segment.\n */\nexport class ScrubInputCache {\n // Changed from Map<number> to Map<string> to include src in key\n #cache = new Map<string, BufferedSeekingInput>();\n #urlCache = new Map<string, BufferedSeekingInput>();\n #pendingBySegment = new Map
|
|
1
|
+
{"version":3,"file":"ScrubInputCache.js","names":["cached","#urlCache","pending","#pendingByUrl","promise","#getCacheKey","#cache","#pendingBySegment","#maxCacheSize"],"sources":["../../../../src/elements/EFMedia/videoTasks/ScrubInputCache.ts"],"sourcesContent":["import type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\n/**\n * Cache for scrub BufferedSeekingInput instances.\n *\n * For JIT media (segmented scrub tracks), caches by src + segment ID.\n * For Asset media (single-file scrub tracks), caches by URL so all segments\n * share the same BufferedSeekingInput instance.\n *\n * Uses promise deduplication to prevent race conditions when multiple\n * concurrent requests arrive for the same segment.\n */\nexport class ScrubInputCache {\n // Changed from Map<number> to Map<string> to include src in key\n #cache = new Map<string, BufferedSeekingInput>();\n #urlCache = new Map<string, BufferedSeekingInput>();\n #pendingBySegment = new Map<string, Promise<BufferedSeekingInput | undefined>>();\n #pendingByUrl = new Map<string, Promise<BufferedSeekingInput | undefined>>();\n #maxCacheSize = 5;\n\n /**\n * Create a cache key that uniquely identifies a segment for a specific video\n */\n #getCacheKey(src: string, segmentId: number): string {\n return `${src}:${segmentId}`;\n }\n\n /**\n * Get or create BufferedSeekingInput for a scrub segment.\n *\n * Uses promise deduplication to prevent race conditions when multiple\n * concurrent requests arrive for the same segment.\n *\n * @param src - The source URL of the video (required to distinguish between videos)\n * @param segmentId - The segment ID\n * @param createInputFn - Factory function to create the input\n * @param scrubUrl - Optional URL for single-file scrub tracks (all segments share same input)\n */\n async getOrCreateInput(\n src: string,\n segmentId: number,\n createInputFn: () => Promise<BufferedSeekingInput | undefined>,\n scrubUrl?: string,\n ): Promise<BufferedSeekingInput | undefined> {\n // For single-file scrub tracks (AssetMediaEngine), use URL-based caching\n // This ensures all segments share the same BufferedSeekingInput\n if (scrubUrl) {\n // Check completed cache\n const cached = this.#urlCache.get(scrubUrl);\n if (cached) {\n return cached;\n }\n\n // Check pending requests (deduplication)\n const pending = this.#pendingByUrl.get(scrubUrl);\n if (pending) {\n return pending;\n }\n\n // Create promise and cache immediately\n const promise = createInputFn()\n .then((input) => {\n this.#pendingByUrl.delete(scrubUrl);\n if (input) {\n this.#urlCache.set(scrubUrl, input);\n }\n return input;\n })\n .catch((error) => {\n this.#pendingByUrl.delete(scrubUrl);\n throw error;\n });\n\n this.#pendingByUrl.set(scrubUrl, promise);\n return promise;\n }\n\n // For segmented scrub tracks (JIT), use src + segment-based caching\n const cacheKey = this.#getCacheKey(src, segmentId);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Check pending requests (deduplication)\n const pending = this.#pendingBySegment.get(cacheKey);\n if (pending) {\n return pending;\n }\n\n // Create promise and cache immediately\n const promise = createInputFn()\n .then((input) => {\n this.#pendingBySegment.delete(cacheKey);\n\n if (input) {\n this.#cache.set(cacheKey, input);\n\n // Evict oldest entries if cache is too large\n if (this.#cache.size > this.#maxCacheSize) {\n const oldestKey = this.#cache.keys().next().value;\n if (oldestKey !== undefined) {\n this.#cache.delete(oldestKey);\n }\n }\n }\n\n return input;\n })\n .catch((error) => {\n this.#pendingBySegment.delete(cacheKey);\n throw error;\n });\n\n this.#pendingBySegment.set(cacheKey, promise);\n return promise;\n }\n\n /**\n * Clear the entire cache (called when video changes)\n */\n clear() {\n this.#cache.clear();\n this.#urlCache.clear();\n this.#pendingBySegment.clear();\n this.#pendingByUrl.clear();\n }\n\n /**\n * Get cache statistics\n */\n getStats() {\n return {\n size: this.#cache.size,\n urlCacheSize: this.#urlCache.size,\n pendingCount: this.#pendingBySegment.size + this.#pendingByUrl.size,\n cacheKeys: Array.from(this.#cache.keys()),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,IAAa,kBAAb,MAA6B;CAE3B,yBAAS,IAAI,KAAmC;CAChD,4BAAY,IAAI,KAAmC;CACnD,oCAAoB,IAAI,KAAwD;CAChF,gCAAgB,IAAI,KAAwD;CAC5E,gBAAgB;;;;CAKhB,aAAa,KAAa,WAA2B;AACnD,SAAO,GAAG,IAAI,GAAG;;;;;;;;;;;;;CAcnB,MAAM,iBACJ,KACA,WACA,eACA,UAC2C;AAG3C,MAAI,UAAU;GAEZ,MAAMA,WAAS,MAAKC,SAAU,IAAI,SAAS;AAC3C,OAAID,SACF,QAAOA;GAIT,MAAME,YAAU,MAAKC,aAAc,IAAI,SAAS;AAChD,OAAID,UACF,QAAOA;GAIT,MAAME,YAAU,eAAe,CAC5B,MAAM,UAAU;AACf,UAAKD,aAAc,OAAO,SAAS;AACnC,QAAI,MACF,OAAKF,SAAU,IAAI,UAAU,MAAM;AAErC,WAAO;KACP,CACD,OAAO,UAAU;AAChB,UAAKE,aAAc,OAAO,SAAS;AACnC,UAAM;KACN;AAEJ,SAAKA,aAAc,IAAI,UAAUC,UAAQ;AACzC,UAAOA;;EAIT,MAAM,WAAW,MAAKC,YAAa,KAAK,UAAU;EAClD,MAAM,SAAS,MAAKC,MAAO,IAAI,SAAS;AACxC,MAAI,OACF,QAAO;EAIT,MAAM,UAAU,MAAKC,iBAAkB,IAAI,SAAS;AACpD,MAAI,QACF,QAAO;EAIT,MAAM,UAAU,eAAe,CAC5B,MAAM,UAAU;AACf,SAAKA,iBAAkB,OAAO,SAAS;AAEvC,OAAI,OAAO;AACT,UAAKD,MAAO,IAAI,UAAU,MAAM;AAGhC,QAAI,MAAKA,MAAO,OAAO,MAAKE,cAAe;KACzC,MAAM,YAAY,MAAKF,MAAO,MAAM,CAAC,MAAM,CAAC;AAC5C,SAAI,cAAc,OAChB,OAAKA,MAAO,OAAO,UAAU;;;AAKnC,UAAO;IACP,CACD,OAAO,UAAU;AAChB,SAAKC,iBAAkB,OAAO,SAAS;AACvC,SAAM;IACN;AAEJ,QAAKA,iBAAkB,IAAI,UAAU,QAAQ;AAC7C,SAAO;;;;;CAMT,QAAQ;AACN,QAAKD,MAAO,OAAO;AACnB,QAAKL,SAAU,OAAO;AACtB,QAAKM,iBAAkB,OAAO;AAC9B,QAAKJ,aAAc,OAAO;;;;;CAM5B,WAAW;AACT,SAAO;GACL,MAAM,MAAKG,MAAO;GAClB,cAAc,MAAKL,SAAU;GAC7B,cAAc,MAAKM,iBAAkB,OAAO,MAAKJ,aAAc;GAC/D,WAAW,MAAM,KAAK,MAAKG,MAAO,MAAM,CAAC;GAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#loadMediaEngine","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","#frequencyDataCache","#analyzeFrequencies","#timeDomainDataCache","#analyzeTimeDomain"],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { MediaEngine } from \"../transcoding/types/index.ts\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { createMediaEngineFromSource } from \"./EFMedia/MediaEngine.js\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { LRUCache } from \"../utils/LRUCache.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * Duplicated here to avoid circular imports from EFTemporal.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll(\"slot\");\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== \"SLOT\") {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n return Array.from(element.children);\n};\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n const children = getChildrenIncludingSlotted(element);\n for (const child of children) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\n/**\n * Simple async value wrapper that mimics Lit Task interface.\n * Used for backwards compatibility with code expecting task-like objects.\n */\nexport class AsyncValue<T> {\n #value: T | undefined = undefined;\n #error: Error | undefined = undefined;\n #status: \"initial\" | \"pending\" | \"complete\" | \"error\" = \"initial\";\n #promise: Promise<T | undefined> = Promise.resolve(undefined);\n #resolvePromise: ((value: T | undefined) => void) | undefined;\n\n // Use properties instead of getters to avoid TypeScript declaration generation bug\n get value(): T | undefined {\n return this.#value;\n }\n\n get error(): Error | undefined {\n return this.#error;\n }\n\n get status(): number {\n // Match TaskStatus enum: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n switch (this.#status) {\n case \"initial\":\n return 0;\n case \"pending\":\n return 1;\n case \"complete\":\n return 2;\n case \"error\":\n return 3;\n }\n }\n\n get taskComplete(): Promise<T | undefined> {\n return this.#promise;\n }\n\n /**\n * Set the value (marks status as complete)\n */\n setValue(value: T): void {\n this.#value = value;\n this.#error = undefined;\n this.#status = \"complete\";\n this.#resolvePromise?.(value);\n }\n\n /**\n * Set an error (marks status as error)\n */\n setError(error: Error): void {\n this.#error = error;\n this.#value = undefined;\n this.#status = \"error\";\n // Don't reject - just resolve with undefined to match old behavior\n this.#resolvePromise?.(undefined);\n }\n\n /**\n * Start a new async operation\n */\n startPending(): void {\n this.#status = \"pending\";\n this.#promise = new Promise((resolve) => {\n this.#resolvePromise = resolve;\n });\n // Prevent unhandled rejection warnings\n this.#promise.catch(() => {});\n }\n\n /**\n * Run an async function and update status accordingly\n */\n async run(fn: () => Promise<T>): Promise<T | undefined> {\n this.startPending();\n try {\n const result = await fn();\n this.setValue(result);\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.setError(error);\n } else {\n this.setError(new Error(String(error)));\n }\n return undefined;\n }\n }\n}\n\n// Audio analysis helper functions\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n let zeroCount = 0;\n let cutoffIndex = totalBins;\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1;\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0;\n } else {\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n override shouldAutoReady(): boolean {\n return false;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n *\n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"file-id\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ): void {\n if (name === \"asset-id\") {\n this.fileId = newValue;\n return;\n }\n super.attributeChangedCallback(name, oldValue, newValue);\n }\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n getFreqWeights() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper method for backwards compatibility\n getShouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n getUrlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n // ============================================================================\n // Media Engine - replaced task with async method + cached wrapper\n // ============================================================================\n\n #mediaEngine: MediaEngine | undefined = undefined;\n #mediaEnginePromise: Promise<MediaEngine | undefined> | undefined = undefined;\n #mediaEngineError: Error | undefined = undefined;\n #mediaEngineSrcKey: string | null = null;\n\n /**\n * Async wrapper that mimics Task interface for backwards compatibility.\n * Code expecting mediaEngineTask.value, .taskComplete, .error, .status will still work.\n */\n mediaEngineTask = new AsyncValue<MediaEngine>();\n\n /**\n * Get or create the MediaEngine for this element.\n * Uses caching based on src/fileId to avoid redundant fetches.\n */\n async getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n if (!this.src && !this.fileId) {\n return undefined;\n }\n\n const srcKey = `${this.src}|${this.fileId}`;\n\n // Return cached if src hasn't changed\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {\n this.setContentReadyState(\"ready\");\n return this.#mediaEngine;\n }\n\n // If already loading for this src, wait for it\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) {\n return this.#mediaEnginePromise;\n }\n\n // Start new load\n this.#mediaEngineSrcKey = srcKey;\n this.mediaEngineTask.startPending();\n this.setContentReadyState(\"loading\");\n\n // Store the handled promise so that concurrent callers at the cache check\n // (line above) get a resolved promise, not a raw rejecting one.\n const loadPromise = this.#loadMediaEngine(signal);\n this.#mediaEnginePromise = loadPromise;\n return loadPromise;\n }\n\n async #loadMediaEngine(\n signal?: AbortSignal,\n ): Promise<MediaEngine | undefined> {\n try {\n this.#mediaEngine = await this.#createMediaEngine(signal);\n this.#mediaEngineError = undefined;\n if (this.#mediaEngine) {\n this.mediaEngineTask.setValue(this.#mediaEngine);\n this.#handleMediaEngineComplete();\n this.setContentReadyState(\"ready\");\n } else {\n // No engine (empty/invalid src) — return to idle\n this.setContentReadyState(\"idle\");\n }\n return this.#mediaEngine;\n } catch (error) {\n this.#mediaEngineError =\n error instanceof Error ? error : new Error(String(error));\n this.mediaEngineTask.setError(this.#mediaEngineError);\n this.setContentReadyState(\"error\");\n\n // Don't throw for expected errors\n const isExpectedError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"404\") ||\n error.message.includes(\"Failed to fetch\")));\n\n if (!isExpectedError) {\n console.error(\"Media engine error:\", error);\n }\n\n return undefined;\n }\n }\n\n async #createMediaEngine(\n signal?: AbortSignal,\n ): Promise<MediaEngine | undefined> {\n const { src, fileId, apiHost, requiredTracks } = this;\n const urlGenerator = this.getUrlGenerator();\n return createMediaEngineFromSource({\n src,\n fileId,\n apiHost,\n requiredTracks,\n fetchFn: (url, init) => this.fetch(url, init),\n urlGenerator,\n signal,\n });\n }\n\n #handleMediaEngineComplete(): void {\n // Update self synchronously\n this.requestUpdate(\"intrinsicDurationMs\");\n this.requestUpdate(\"ownCurrentTimeMs\");\n\n // Defer updates to parent/root timegroup\n if (this.rootTimegroup) {\n queueMicrotask(() => {\n this.rootTimegroup?.requestUpdate(\"ownCurrentTimeMs\");\n this.rootTimegroup?.requestUpdate(\"durationMs\");\n });\n }\n }\n\n // ============================================================================\n // Audio Analysis - replaced tasks with async methods + cached wrappers\n // ============================================================================\n\n #frequencyDataCache = new LRUCache<string, Uint8Array>(100);\n #timeDomainDataCache = new LRUCache<string, Uint8Array>(100);\n\n /**\n * Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility\n */\n frequencyDataTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility\n */\n byteTimeDomainTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Get frequency data for audio visualization at a given time.\n */\n async getFrequencyData(\n timeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;\n const cached = this.#frequencyDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeFrequencies(timeMs, signal);\n if (result) {\n this.#frequencyDataCache.set(cacheKey, result);\n this.frequencyDataTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n /**\n * Get time domain data for audio visualization at a given time.\n */\n async getTimeDomainData(\n timeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.fftSize}:${timeMs}`;\n const cached = this.#timeDomainDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeTimeDomain(timeMs, signal);\n if (result) {\n this.#timeDomainDataCache.set(cacheKey, result);\n this.byteTimeDomainTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n async #analyzeFrequencies(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.tracks.audio) {\n return null;\n }\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs =\n currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Apply frequency weights\n smoothedData.forEach((value, i) => {\n const freqWeight = this.getFreqWeights()[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n const slicedData = smoothedData.slice(\n 0,\n Math.floor(smoothedData.length / 2),\n );\n return this.getShouldInterpolateFrequencies()\n ? processFFTData(slicedData)\n : slicedData;\n }\n\n async #analyzeTimeDomain(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.tracks.audio) {\n return null;\n }\n\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs =\n currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize);\n analyser.getByteTimeDomainData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Use RMS calculation to preserve waveform shape\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let sumSquares = 0;\n framesData.forEach((frame: Uint8Array) => {\n const value = (frame[i] ?? 128) - 128;\n sumSquares += value * value;\n });\n const rms = Math.sqrt(sumSquares / framesData.length);\n smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));\n }\n\n return smoothedData;\n }\n\n // ============================================================================\n // Removed task properties - these are kept as stubs for backwards compatibility\n // ============================================================================\n\n // These tasks are no longer used but kept for API compatibility\n audioSegmentIdTask = new AsyncValue<number | undefined>();\n audioInitSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioInputTask = new AsyncValue<any>();\n audioSeekTask = new AsyncValue<any>();\n audioBufferTask = new AsyncValue<any>();\n\n /**\n * The unique identifier for the media file.\n * This property can be set programmatically or via the \"file-id\" attribute.\n * The \"asset-id\" attribute is also supported for backward compatibility.\n * @domAttribute \"file-id\"\n */\n @property({ type: String, attribute: \"file-id\", reflect: true })\n fileId: string | null = null;\n\n /** @deprecated Use fileId instead */\n get assetId(): string | null {\n return this.fileId;\n }\n set assetId(value: string | null) {\n this.fileId = value;\n }\n\n get intrinsicDurationMs(): number | undefined {\n return this.#mediaEngine?.durationMs;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Trigger media engine load when src or fileId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"fileId\")) {\n this.getMediaEngine().catch(() => {});\n // Source identity changed — cached renderable output is stale\n if (\n changedProperties.get(\"src\") !== undefined ||\n changedProperties.get(\"fileId\") !== undefined\n ) {\n this.emitContentChange(\"source\");\n }\n }\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n this.emitContentChange(\"bounds\");\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration(): boolean {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs(): number {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this._desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n // Create a default signal if not provided (public API convenience)\n const effectiveSignal = signal ?? new AbortController().signal;\n const { fetchAudioSpanningTime } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n return fetchAudioSpanningTime(this, fromMs, toMs, effectiveSignal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.#mediaEngine) {\n return;\n }\n\n try {\n await this.getMediaEngine(signal);\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")));\n\n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n\n // For task abort (element disconnected), silently return\n if (isAbortError) {\n return;\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#loadMediaEngine","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","#frequencyDataCache","#analyzeFrequencies","#timeDomainDataCache","#analyzeTimeDomain"],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { MediaEngine } from \"../transcoding/types/index.ts\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { createMediaEngineFromSource } from \"./EFMedia/MediaEngine.js\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { LRUCache } from \"../utils/LRUCache.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * Duplicated here to avoid circular imports from EFTemporal.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll(\"slot\");\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== \"SLOT\") {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n return Array.from(element.children);\n};\n\nexport const deepGetMediaElements = (element: Element, medias: EFMedia[] = []) => {\n const children = getChildrenIncludingSlotted(element);\n for (const child of children) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\n/**\n * Simple async value wrapper that mimics Lit Task interface.\n * Used for backwards compatibility with code expecting task-like objects.\n */\nexport class AsyncValue<T> {\n #value: T | undefined = undefined;\n #error: Error | undefined = undefined;\n // oxlint-disable-next-line no-unused-private-class-members -- false positive; read via get status()\n #status: \"initial\" | \"pending\" | \"complete\" | \"error\" = \"initial\";\n #promise: Promise<T | undefined> = Promise.resolve(undefined);\n #resolvePromise: ((value: T | undefined) => void) | undefined;\n\n // Use properties instead of getters to avoid TypeScript declaration generation bug\n get value(): T | undefined {\n return this.#value;\n }\n\n get error(): Error | undefined {\n return this.#error;\n }\n\n get status(): number {\n // Match TaskStatus enum: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n switch (this.#status) {\n case \"initial\":\n return 0;\n case \"pending\":\n return 1;\n case \"complete\":\n return 2;\n case \"error\":\n return 3;\n }\n }\n\n get taskComplete(): Promise<T | undefined> {\n return this.#promise;\n }\n\n /**\n * Set the value (marks status as complete)\n */\n setValue(value: T): void {\n this.#value = value;\n this.#error = undefined;\n this.#status = \"complete\";\n this.#resolvePromise?.(value);\n }\n\n /**\n * Set an error (marks status as error)\n */\n setError(error: Error): void {\n this.#error = error;\n this.#value = undefined;\n this.#status = \"error\";\n // Don't reject - just resolve with undefined to match old behavior\n this.#resolvePromise?.(undefined);\n }\n\n /**\n * Start a new async operation\n */\n startPending(): void {\n this.#status = \"pending\";\n this.#promise = new Promise((resolve) => {\n this.#resolvePromise = resolve;\n });\n // Prevent unhandled rejection warnings\n this.#promise.catch(() => {});\n }\n\n /**\n * Run an async function and update status accordingly\n */\n async run(fn: () => Promise<T>): Promise<T | undefined> {\n this.startPending();\n try {\n const result = await fn();\n this.setValue(result);\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.setError(error);\n } else {\n this.setError(new Error(String(error)));\n }\n return undefined;\n }\n }\n}\n\n// Audio analysis helper functions\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(fftData: Uint8Array, zeroThresholdPercent = 0.1): Uint8Array {\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n let zeroCount = 0;\n let cutoffIndex = totalBins;\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1;\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0;\n } else {\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n override shouldAutoReady(): boolean {\n return false;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n *\n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"file-id\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (name === \"asset-id\") {\n this.fileId = newValue;\n return;\n }\n super.attributeChangedCallback(name, oldValue, newValue);\n }\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n getFreqWeights() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper method for backwards compatibility\n getShouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n getUrlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n // ============================================================================\n // Media Engine - replaced task with async method + cached wrapper\n // ============================================================================\n\n #mediaEngine: MediaEngine | undefined = undefined;\n #mediaEnginePromise: Promise<MediaEngine | undefined> | undefined = undefined;\n #mediaEngineError: Error | undefined = undefined;\n #mediaEngineSrcKey: string | null = null;\n\n /**\n * Async wrapper that mimics Task interface for backwards compatibility.\n * Code expecting mediaEngineTask.value, .taskComplete, .error, .status will still work.\n */\n mediaEngineTask = new AsyncValue<MediaEngine>();\n\n /**\n * Get or create the MediaEngine for this element.\n * Uses caching based on src/fileId to avoid redundant fetches.\n */\n async getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n if (!this.src && !this.fileId) {\n return undefined;\n }\n\n const srcKey = `${this.src}|${this.fileId}`;\n\n // Return cached if src hasn't changed\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {\n this.setContentReadyState(\"ready\");\n return this.#mediaEngine;\n }\n\n // If already loading for this src, wait for it\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) {\n return this.#mediaEnginePromise;\n }\n\n // Start new load\n this.#mediaEngineSrcKey = srcKey;\n this.mediaEngineTask.startPending();\n this.setContentReadyState(\"loading\");\n\n // Store the handled promise so that concurrent callers at the cache check\n // (line above) get a resolved promise, not a raw rejecting one.\n const loadPromise = this.#loadMediaEngine(signal);\n this.#mediaEnginePromise = loadPromise;\n return loadPromise;\n }\n\n async #loadMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n try {\n this.#mediaEngine = await this.#createMediaEngine(signal);\n this.#mediaEngineError = undefined;\n if (this.#mediaEngine) {\n this.mediaEngineTask.setValue(this.#mediaEngine);\n this.#handleMediaEngineComplete();\n this.setContentReadyState(\"ready\");\n } else {\n // No engine (empty/invalid src) — return to idle\n this.setContentReadyState(\"idle\");\n }\n return this.#mediaEngine;\n } catch (error) {\n this.#mediaEngineError = error instanceof Error ? error : new Error(String(error));\n this.mediaEngineTask.setError(this.#mediaEngineError);\n this.setContentReadyState(\"error\");\n\n // Don't throw for expected errors\n const isExpectedError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"404\") ||\n error.message.includes(\"Failed to fetch\")));\n\n if (!isExpectedError) {\n console.error(\"Media engine error:\", error);\n }\n\n return undefined;\n }\n }\n\n async #createMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const { src, fileId, apiHost, requiredTracks } = this;\n const urlGenerator = this.getUrlGenerator();\n return createMediaEngineFromSource({\n src,\n fileId,\n apiHost,\n requiredTracks,\n fetchFn: (url, init) => this.fetch(url, init),\n urlGenerator,\n signal,\n });\n }\n\n #handleMediaEngineComplete(): void {\n // Update self synchronously\n this.requestUpdate(\"intrinsicDurationMs\");\n this.requestUpdate(\"ownCurrentTimeMs\");\n\n // Defer updates to parent/root timegroup\n if (this.rootTimegroup) {\n queueMicrotask(() => {\n this.rootTimegroup?.requestUpdate(\"ownCurrentTimeMs\");\n this.rootTimegroup?.requestUpdate(\"durationMs\");\n });\n }\n }\n\n // ============================================================================\n // Audio Analysis - replaced tasks with async methods + cached wrappers\n // ============================================================================\n\n #frequencyDataCache = new LRUCache<string, Uint8Array>(100);\n #timeDomainDataCache = new LRUCache<string, Uint8Array>(100);\n\n /**\n * Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility\n */\n frequencyDataTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility\n */\n byteTimeDomainTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Get frequency data for audio visualization at a given time.\n */\n async getFrequencyData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;\n const cached = this.#frequencyDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeFrequencies(timeMs, signal);\n if (result) {\n this.#frequencyDataCache.set(cacheKey, result);\n this.frequencyDataTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n /**\n * Get time domain data for audio visualization at a given time.\n */\n async getTimeDomainData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.fftSize}:${timeMs}`;\n const cached = this.#timeDomainDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeTimeDomain(timeMs, signal);\n if (result) {\n this.#timeDomainDataCache.set(cacheKey, result);\n this.byteTimeDomainTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n async #analyzeFrequencies(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.tracks.audio) {\n return null;\n }\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Apply frequency weights\n smoothedData.forEach((value, i) => {\n const freqWeight = this.getFreqWeights()[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));\n return this.getShouldInterpolateFrequencies() ? processFFTData(slicedData) : slicedData;\n }\n\n async #analyzeTimeDomain(\n currentTimeMs: number,\n signal?: AbortSignal,\n ): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n\n if (!mediaEngine?.tracks.audio) {\n return null;\n }\n\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n\n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal!);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n\n const frameData = new Uint8Array(this.fftSize);\n analyser.getByteTimeDomainData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Use RMS calculation to preserve waveform shape\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let sumSquares = 0;\n framesData.forEach((frame: Uint8Array) => {\n const value = (frame[i] ?? 128) - 128;\n sumSquares += value * value;\n });\n const rms = Math.sqrt(sumSquares / framesData.length);\n smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));\n }\n\n return smoothedData;\n }\n\n // ============================================================================\n // Removed task properties - these are kept as stubs for backwards compatibility\n // ============================================================================\n\n // These tasks are no longer used but kept for API compatibility\n audioSegmentIdTask = new AsyncValue<number | undefined>();\n audioInitSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioInputTask = new AsyncValue<any>();\n audioSeekTask = new AsyncValue<any>();\n audioBufferTask = new AsyncValue<any>();\n\n /**\n * The unique identifier for the media file.\n * This property can be set programmatically or via the \"file-id\" attribute.\n * The \"asset-id\" attribute is also supported for backward compatibility.\n * @domAttribute \"file-id\"\n */\n @property({ type: String, attribute: \"file-id\", reflect: true })\n fileId: string | null = null;\n\n /** @deprecated Use fileId instead */\n get assetId(): string | null {\n return this.fileId;\n }\n set assetId(value: string | null) {\n this.fileId = value;\n }\n\n get intrinsicDurationMs(): number | undefined {\n return this.#mediaEngine?.durationMs;\n }\n\n protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {\n super.updated(changedProperties);\n\n // Trigger media engine load when src or fileId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"fileId\")) {\n this.getMediaEngine().catch(() => {});\n // Source identity changed — cached renderable output is stale\n if (\n changedProperties.get(\"src\") !== undefined ||\n changedProperties.get(\"fileId\") !== undefined\n ) {\n this.emitContentChange(\"source\");\n }\n }\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\"_trimStartMs\", \"_trimEndMs\", \"_sourceInMs\", \"_sourceOutMs\"];\n\n const hasDurationChange = durationAffectingProps.some((prop) => changedProperties.has(prop));\n\n if (hasDurationChange) {\n this.emitContentChange(\"bounds\");\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration(): boolean {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs(): number {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this._desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n // Create a default signal if not provided (public API convenience)\n const effectiveSignal = signal ?? new AbortController().signal;\n const { fetchAudioSpanningTime } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n return fetchAudioSpanningTime(this, fromMs, toMs, effectiveSignal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.#mediaEngine) {\n return;\n }\n\n try {\n await this.getMediaEngine(signal);\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError =\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error &&\n (error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")));\n\n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n\n // For task abort (element disconnected), silently return\n if (isAbortError) {\n return;\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}
|
|
@@ -22,15 +22,16 @@ let EFPanZoom = class EFPanZoom$1 extends LitElement {
|
|
|
22
22
|
this._dragStartTransform = null;
|
|
23
23
|
this._capturedPointerId = null;
|
|
24
24
|
this._onDocumentWheelCapture = (e) => {
|
|
25
|
-
let
|
|
25
|
+
let isOverThisPanZoom = false;
|
|
26
26
|
if (e.target instanceof Element) {
|
|
27
|
-
|
|
28
|
-
if (
|
|
27
|
+
const targetPanZoom = e.target.closest("ef-pan-zoom");
|
|
28
|
+
if (targetPanZoom === this) isOverThisPanZoom = true;
|
|
29
|
+
else if (!targetPanZoom && e.target.closest("ef-canvas-selection-overlay")) {
|
|
29
30
|
const rect = this.getBoundingClientRect();
|
|
30
|
-
if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom)
|
|
31
|
+
if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) isOverThisPanZoom = true;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
if (
|
|
34
|
+
if (isOverThisPanZoom) e.preventDefault();
|
|
34
35
|
};
|
|
35
36
|
this._onPointerDown = (e) => {
|
|
36
37
|
if (e.button !== 0) return;
|
|
@@ -47,7 +48,7 @@ let EFPanZoom = class EFPanZoom$1 extends LitElement {
|
|
|
47
48
|
};
|
|
48
49
|
try {
|
|
49
50
|
this.setPointerCapture(e.pointerId);
|
|
50
|
-
} catch (
|
|
51
|
+
} catch (_err) {}
|
|
51
52
|
};
|
|
52
53
|
this._onPointerMove = (e) => {
|
|
53
54
|
if (!this._isDragging || !this._dragStartPointerPos || !this._dragStartTransform) return;
|
|
@@ -62,7 +63,7 @@ let EFPanZoom = class EFPanZoom$1 extends LitElement {
|
|
|
62
63
|
if (!this._isDragging) return;
|
|
63
64
|
if (this._capturedPointerId !== null) try {
|
|
64
65
|
this.releasePointerCapture(e.pointerId);
|
|
65
|
-
} catch (
|
|
66
|
+
} catch (_err) {}
|
|
66
67
|
this._isDragging = false;
|
|
67
68
|
this._capturedPointerId = null;
|
|
68
69
|
this._dragStartPointerPos = null;
|
|
@@ -138,7 +139,7 @@ let EFPanZoom = class EFPanZoom$1 extends LitElement {
|
|
|
138
139
|
this.removeEventListener("pointercancel", this._onPointerUp);
|
|
139
140
|
if (this._isDragging && this._capturedPointerId !== null) try {
|
|
140
141
|
this.releasePointerCapture(this._capturedPointerId);
|
|
141
|
-
} catch (
|
|
142
|
+
} catch (_err) {}
|
|
142
143
|
}
|
|
143
144
|
_updateTransform(updates) {
|
|
144
145
|
const newTransform = {
|