@editframe/elements 0.20.4-beta.0 → 0.21.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +50 -11
  4. package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
  5. package/dist/elements/ContextProxiesController.js +2 -22
  6. package/dist/elements/EFAudio.js +4 -8
  7. package/dist/elements/EFCaptions.js +59 -84
  8. package/dist/elements/EFImage.js +5 -6
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +35 -30
  11. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
  12. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
  13. package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
  14. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +3 -6
  15. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -1
  16. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  17. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
  18. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  19. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
  20. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  21. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
  22. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +4 -16
  23. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -15
  24. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  25. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  26. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  27. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
  28. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  29. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  30. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  31. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
  32. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
  33. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  34. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
  35. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
  36. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +7 -11
  37. package/dist/elements/EFMedia.js +26 -24
  38. package/dist/elements/EFSourceMixin.js +5 -7
  39. package/dist/elements/EFSurface.js +6 -9
  40. package/dist/elements/EFTemporal.js +19 -37
  41. package/dist/elements/EFThumbnailStrip.js +16 -59
  42. package/dist/elements/EFTimegroup.js +95 -90
  43. package/dist/elements/EFVideo.d.ts +6 -2
  44. package/dist/elements/EFVideo.js +142 -107
  45. package/dist/elements/EFWaveform.js +18 -27
  46. package/dist/elements/SampleBuffer.js +2 -5
  47. package/dist/elements/TargetController.js +3 -3
  48. package/dist/elements/durationConverter.js +4 -4
  49. package/dist/elements/updateAnimations.js +14 -35
  50. package/dist/gui/ContextMixin.js +23 -52
  51. package/dist/gui/EFConfiguration.js +7 -7
  52. package/dist/gui/EFControls.js +5 -5
  53. package/dist/gui/EFFilmstrip.js +77 -98
  54. package/dist/gui/EFFitScale.js +5 -6
  55. package/dist/gui/EFFocusOverlay.js +4 -4
  56. package/dist/gui/EFPreview.js +4 -4
  57. package/dist/gui/EFScrubber.js +9 -9
  58. package/dist/gui/EFTimeDisplay.js +5 -5
  59. package/dist/gui/EFToggleLoop.js +4 -4
  60. package/dist/gui/EFTogglePlay.js +5 -5
  61. package/dist/gui/EFWorkbench.js +5 -5
  62. package/dist/gui/TWMixin2.js +1 -1
  63. package/dist/index.d.ts +1 -0
  64. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  65. package/dist/otel/BridgeSpanExporter.js +87 -0
  66. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  67. package/dist/otel/setupBrowserTracing.js +30 -0
  68. package/dist/otel/tracingHelpers.d.ts +34 -0
  69. package/dist/otel/tracingHelpers.js +113 -0
  70. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  71. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  72. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  73. package/dist/utils/LRUCache.js +6 -53
  74. package/package.json +10 -2
  75. package/src/elements/EFCaptions.browsertest.ts +2 -0
  76. package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
  77. package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
  78. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  79. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
  80. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  81. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
  82. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  83. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
  84. package/src/elements/EFMedia.ts +16 -1
  85. package/src/elements/EFTimegroup.browsertest.ts +10 -8
  86. package/src/elements/EFTimegroup.ts +164 -76
  87. package/src/elements/EFVideo.browsertest.ts +19 -27
  88. package/src/elements/EFVideo.ts +203 -101
  89. package/src/otel/BridgeSpanExporter.ts +150 -0
  90. package/src/otel/setupBrowserTracing.ts +68 -0
  91. package/src/otel/tracingHelpers.ts +251 -0
  92. package/types.json +1 -1
@@ -3,8 +3,7 @@ import { ThumbnailExtractor } from "./shared/ThumbnailExtractor.js";
3
3
  var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
4
4
  static async fetch(host, urlGenerator, url) {
5
5
  const engine = new JitMediaEngine(host, urlGenerator);
6
- const data = await engine.fetchManifest(url);
7
- engine.data = data;
6
+ engine.data = await engine.fetchManifest(url);
8
7
  return engine;
9
8
  }
10
9
  constructor(host, urlGenerator) {
@@ -20,7 +19,7 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
20
19
  return this.data.sourceUrl;
21
20
  }
22
21
  get audioRendition() {
23
- if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) return void 0;
22
+ if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) return;
24
23
  const rendition = this.data.audioRenditions[0];
25
24
  if (!rendition) return void 0;
26
25
  return {
@@ -32,7 +31,7 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
32
31
  };
33
32
  }
34
33
  get videoRendition() {
35
- if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) return void 0;
34
+ if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) return;
36
35
  const rendition = this.data.videoRenditions[0];
37
36
  if (!rendition) return void 0;
38
37
  return {
@@ -57,26 +56,24 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
57
56
  return this.fetchMedia(url);
58
57
  }
59
58
  computeSegmentId(desiredSeekTimeMs, rendition) {
60
- if (desiredSeekTimeMs > this.durationMs) return void 0;
59
+ if (desiredSeekTimeMs > this.durationMs) return;
61
60
  if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
62
61
  let cumulativeTime = 0;
63
62
  for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
64
63
  const segmentDuration = rendition.segmentDurationsMs[i];
65
64
  if (segmentDuration === void 0) throw new Error("Segment duration is required for JIT metadata");
66
- const segmentStartMs$1 = cumulativeTime;
65
+ const segmentStartMs = cumulativeTime;
67
66
  const segmentEndMs = cumulativeTime + segmentDuration;
68
- const isLastSegment = i === rendition.segmentDurationsMs.length - 1;
69
- const includesEndTime = isLastSegment && desiredSeekTimeMs === this.durationMs;
70
- if (desiredSeekTimeMs >= segmentStartMs$1 && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
67
+ const includesEndTime = i === rendition.segmentDurationsMs.length - 1 && desiredSeekTimeMs === this.durationMs;
68
+ if (desiredSeekTimeMs >= segmentStartMs && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
71
69
  cumulativeTime += segmentDuration;
72
70
  if (cumulativeTime >= this.durationMs) break;
73
71
  }
74
- return void 0;
72
+ return;
75
73
  }
76
74
  if (!rendition.segmentDurationMs) throw new Error("Segment duration is required for JIT metadata");
77
75
  const segmentIndex = Math.floor(desiredSeekTimeMs / rendition.segmentDurationMs);
78
- const segmentStartMs = segmentIndex * rendition.segmentDurationMs;
79
- if (segmentStartMs >= this.durationMs) return void 0;
76
+ if (segmentIndex * rendition.segmentDurationMs >= this.durationMs) return;
80
77
  return segmentIndex + 1;
81
78
  }
82
79
  getScrubVideoRendition() {
@@ -91,10 +88,6 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
91
88
  segmentDurationsMs: scrubManifestRendition.segmentDurationsMs
92
89
  };
93
90
  }
94
- /**
95
- * Get preferred buffer configuration for JIT transcoding
96
- * Uses higher buffering since transcoding introduces latency
97
- */
98
91
  getBufferConfig() {
99
92
  return {
100
93
  videoBufferDurationMs: 8e3,
@@ -103,9 +96,6 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
103
96
  maxAudioBufferFetches: 3
104
97
  };
105
98
  }
106
- /**
107
- * Extract thumbnail canvases using same rendition priority as video playback for frame alignment
108
- */
109
99
  async extractThumbnails(timestamps) {
110
100
  let rendition;
111
101
  try {
@@ -33,12 +33,10 @@ const makeAudioBufferTask = (host) => {
33
33
  };
34
34
  return manageMediaBuffer(seekTimeMs, currentConfig, currentState, host.intrinsicDurationMs || 1e4, signal, {
35
35
  computeSegmentId: async (timeMs, rendition) => {
36
- const mediaEngine$1 = await getLatestMediaEngine(host, signal);
37
- return mediaEngine$1.computeSegmentId(timeMs, rendition);
36
+ return (await getLatestMediaEngine(host, signal)).computeSegmentId(timeMs, rendition);
38
37
  },
39
38
  prefetchSegment: async (segmentId, rendition) => {
40
- const mediaEngine$1 = await getLatestMediaEngine(host, signal);
41
- await mediaEngine$1.fetchMediaSegment(segmentId, rendition);
39
+ await (await getLatestMediaEngine(host, signal)).fetchMediaSegment(segmentId, rendition);
42
40
  },
43
41
  isSegmentCached: (segmentId, rendition) => {
44
42
  const mediaEngine$1 = host.mediaEngineTask.value;
@@ -46,8 +44,7 @@ const makeAudioBufferTask = (host) => {
46
44
  return mediaEngine$1.isSegmentCached(segmentId, rendition);
47
45
  },
48
46
  getRendition: async () => {
49
- const mediaEngine$1 = await getLatestMediaEngine(host, signal);
50
- const audioRendition = mediaEngine$1.audioRendition;
47
+ const audioRendition = (await getLatestMediaEngine(host, signal)).audioRendition;
51
48
  if (!audioRendition) throw new Error("Audio rendition not available");
52
49
  return audioRendition;
53
50
  },
@@ -1,7 +1,7 @@
1
1
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { LRUCache } from "../../../utils/LRUCache.js";
3
3
  import { Task } from "@lit/task";
4
- const DECAY_WEIGHT = .8;
4
+ var DECAY_WEIGHT = .8;
5
5
  function processFFTData(fftData, zeroThresholdPercent = .1) {
6
6
  const totalBins = fftData.length;
7
7
  const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
@@ -10,7 +10,7 @@ const makeAudioInitSegmentFetchTask = (host) => {
10
10
  task: async ([_mediaEngine], { signal }) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
12
  const audioRendition = mediaEngine.getAudioRendition();
13
- if (!audioRendition) return void 0;
13
+ if (!audioRendition) return;
14
14
  return mediaEngine.fetchInitSegment(audioRendition, signal);
15
15
  }
16
16
  });
@@ -10,16 +10,17 @@ const makeAudioInputTask = (host) => {
10
10
  onComplete: (_value) => {},
11
11
  task: async (_, { signal }) => {
12
12
  const mediaEngine = await host.mediaEngineTask.taskComplete;
13
+ if (signal.aborted) return void 0;
13
14
  const audioRendition = mediaEngine?.audioRendition;
14
- if (!audioRendition) return void 0;
15
+ if (!audioRendition) return;
15
16
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
16
- signal.throwIfAborted();
17
+ if (signal.aborted) return void 0;
17
18
  const segment = await host.audioSegmentFetchTask.taskComplete;
18
- signal.throwIfAborted();
19
- if (!initSegment || !segment) return void 0;
19
+ if (signal.aborted) return void 0;
20
+ if (!initSegment || !segment) return;
20
21
  const startTimeOffsetMs = audioRendition.startTimeOffsetMs;
21
22
  const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
22
- signal.throwIfAborted();
23
+ if (signal.aborted) return void 0;
23
24
  return new BufferedSeekingInput(arrayBuffer, {
24
25
  videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
25
26
  audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
@@ -13,9 +13,7 @@ const makeAudioSeekTask = (host) => {
13
13
  else console.error("audioSeekTask unknown error", error);
14
14
  },
15
15
  onComplete: (_value) => {},
16
- task: async () => {
17
- return void 0;
18
- }
16
+ task: async () => {}
19
17
  });
20
18
  };
21
19
  export { makeAudioSeekTask };
@@ -11,7 +11,7 @@ const makeAudioSegmentFetchTask = (host) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
12
  const segmentId = await host.audioSegmentIdTask.taskComplete;
13
13
  const audioRendition = mediaEngine.getAudioRendition();
14
- if (!audioRendition || segmentId === void 0) return void 0;
14
+ if (!audioRendition || segmentId === void 0) return;
15
15
  return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
16
16
  }
17
17
  });
@@ -11,7 +11,7 @@ const makeAudioSegmentIdTask = (host) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
12
  signal.throwIfAborted();
13
13
  const audioRendition = mediaEngine.getAudioRendition();
14
- if (!audioRendition) return void 0;
14
+ if (!audioRendition) return;
15
15
  return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
16
16
  }
17
17
  });
@@ -2,7 +2,7 @@ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { LRUCache } from "../../../utils/LRUCache.js";
3
3
  import { IgnorableError } from "../../EFMedia.js";
4
4
  import { Task } from "@lit/task";
5
- const DECAY_WEIGHT = .8;
5
+ var DECAY_WEIGHT = .8;
6
6
  function makeAudioTimeDomainAnalysisTask(element) {
7
7
  const cache = new LRUCache(1e3);
8
8
  return new Task(element, {
@@ -1,8 +1,4 @@
1
- /**
2
- * Fetch audio segment data using MediaEngine
3
- * Pure function with explicit dependencies
4
- */
5
- const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
1
+ var fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
6
2
  const audioRendition = mediaEngine.audioRendition;
7
3
  if (!audioRendition) throw new Error("Audio rendition not available");
8
4
  const segmentData = /* @__PURE__ */ new Map();
@@ -15,24 +11,16 @@ const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
15
11
  for (const [segmentId, arrayBuffer] of fetchedSegments) segmentData.set(segmentId, arrayBuffer);
16
12
  return segmentData;
17
13
  };
18
- /**
19
- * Create audio span blob from init segment and media segments
20
- * Pure function for blob creation
21
- */
22
- const createAudioSpanBlob = (initSegment, mediaSegments) => {
14
+ var createAudioSpanBlob = (initSegment, mediaSegments) => {
23
15
  const chunks = [initSegment, ...mediaSegments];
24
16
  return new Blob(chunks, { type: "audio/mp4" });
25
17
  };
26
- /**
27
- * Fetch audio spanning a time range
28
- * Main function that orchestrates segment calculation, fetching, and blob creation
29
- */
30
18
  const fetchAudioSpanningTime = async (host, fromMs, toMs, signal) => {
31
19
  if (fromMs >= toMs || fromMs < 0) throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
32
20
  const mediaEngine = await host.mediaEngineTask.taskComplete;
33
21
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
34
- if (!mediaEngine?.audioRendition) return void 0;
35
- if (!initSegment) return void 0;
22
+ if (!mediaEngine?.audioRendition) return;
23
+ if (!initSegment) return;
36
24
  const segmentRanges = mediaEngine.calculateAudioSegmentRange(fromMs, toMs, mediaEngine.audioRendition, host.intrinsicDurationMs || 1e4);
37
25
  if (segmentRanges.length === 0) throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);
38
26
  const segmentIds = segmentRanges.map((r) => r.segmentId);
@@ -1,6 +1,3 @@
1
- /**
2
- * Async version of computeSegmentRange for when computeSegmentId is async
3
- */
4
1
  const computeSegmentRangeAsync = async (startTimeMs, endTimeMs, durationMs, rendition, computeSegmentId) => {
5
2
  const segments = [];
6
3
  const segmentDurationMs = rendition.segmentDurationMs || 1e3;
@@ -15,24 +12,15 @@ const computeSegmentRangeAsync = async (startTimeMs, endTimeMs, durationMs, rend
15
12
  }
16
13
  return segments.filter((id, index, arr) => arr.indexOf(id) === index);
17
14
  };
18
- /**
19
- * Compute buffer queue based on desired segments and what we've already requested
20
- * Pure function - determines what new segments should be prefetched
21
- */
22
15
  const computeBufferQueue = (desiredSegments, requestedSegments) => {
23
16
  return desiredSegments.filter((segmentId) => !requestedSegments.has(segmentId));
24
17
  };
25
- /**
26
- * Core media buffering orchestration logic - prefetch only, no data storage
27
- * Integrates with BaseMediaEngine's existing caching and request deduplication
28
- */
29
18
  const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, signal, deps) => {
30
19
  if (!config.enableBuffering) return currentState;
31
20
  const rendition = await deps.getRendition();
32
21
  if (!rendition) return currentState;
33
22
  const endTimeMs = seekTimeMs + config.bufferDurationMs;
34
- const desiredSegments = await computeSegmentRangeAsync(seekTimeMs, endTimeMs, durationMs, rendition, deps.computeSegmentId);
35
- const uncachedSegments = desiredSegments.filter((segmentId) => !deps.isSegmentCached(segmentId, rendition));
23
+ const uncachedSegments = (await computeSegmentRangeAsync(seekTimeMs, endTimeMs, durationMs, rendition, deps.computeSegmentId)).filter((segmentId) => !deps.isSegmentCached(segmentId, rendition));
36
24
  const newQueue = computeBufferQueue(uncachedSegments, currentState.requestedSegments);
37
25
  const newRequestedSegments = new Set(currentState.requestedSegments);
38
26
  const newActiveRequests = new Set(currentState.activeRequests);
@@ -60,12 +48,11 @@ const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, s
60
48
  };
61
49
  const initialBatchSize = Math.min(config.maxParallelFetches, newQueue.length);
62
50
  for (let i = 0; i < initialBatchSize; i++) startNextSegment();
63
- const result = {
51
+ return {
64
52
  currentSeekTimeMs: seekTimeMs,
65
53
  requestedSegments: newRequestedSegments,
66
54
  activeRequests: newActiveRequests,
67
55
  requestQueue: remainingQueue
68
56
  };
69
- return result;
70
57
  };
71
58
  export { manageMediaBuffer };
@@ -1,50 +1,26 @@
1
1
  import { LRUCache } from "../../../utils/LRUCache.js";
2
- /**
3
- * Global cache for MediaBunny Input instances
4
- * Shared across all MediaEngine instances to prevent duplicate decoding
5
- * of the same segment data
6
- */
7
2
  var GlobalInputCache = class {
8
3
  constructor() {
9
4
  this.cache = new LRUCache(50);
10
5
  }
11
- /**
12
- * Generate standardized cache key for Input objects
13
- * Format: "input:{src}:{segmentId}:{renditionId}"
14
- */
15
6
  generateKey(src, segmentId, renditionId) {
16
7
  return `input:${src}:${segmentId}:${renditionId || "default"}`;
17
8
  }
18
- /**
19
- * Get cached Input object
20
- */
21
9
  get(src, segmentId, renditionId) {
22
10
  const key = this.generateKey(src, segmentId, renditionId);
23
11
  return this.cache.get(key);
24
12
  }
25
- /**
26
- * Cache Input object
27
- */
28
13
  set(src, segmentId, input, renditionId) {
29
14
  const key = this.generateKey(src, segmentId, renditionId);
30
15
  this.cache.set(key, input);
31
16
  }
32
- /**
33
- * Check if Input is cached
34
- */
35
17
  has(src, segmentId, renditionId) {
36
18
  const key = this.generateKey(src, segmentId, renditionId);
37
19
  return this.cache.has(key);
38
20
  }
39
- /**
40
- * Clear all cached Input objects
41
- */
42
21
  clear() {
43
22
  this.cache.clear();
44
23
  }
45
- /**
46
- * Get cache statistics for debugging
47
- */
48
24
  getStats() {
49
25
  return {
50
26
  size: this.cache.size,
@@ -1,27 +1,6 @@
1
- /**
2
- * Centralized precision utilities for consistent timing calculations across the media pipeline.
3
- *
4
- * The key insight is that floating-point precision errors can cause inconsistencies between:
5
- * 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)
6
- * 2. Sample finding logic (in SampleBuffer.find)
7
- * 3. Timeline mapping (in BufferedSeekingInput.seek)
8
- *
9
- * All timing calculations must use the same rounding strategy to ensure consistency.
10
- */
11
- /**
12
- * Round time to millisecond precision to handle floating-point precision issues.
13
- * Uses Math.round for consistent behavior across the entire pipeline.
14
- *
15
- * This function should be used for ALL time-related calculations that need to be
16
- * compared between different parts of the system.
17
- */
18
1
  const roundToMilliseconds = (timeMs) => {
19
2
  return Math.round(timeMs * 1e3) / 1e3;
20
3
  };
21
- /**
22
- * Convert media time (in seconds) to scaled time units using consistent rounding.
23
- * This is used in segment selection to convert from milliseconds to timescale units.
24
- */
25
4
  const convertToScaledTime = (timeMs, timescale) => {
26
5
  const scaledTime = timeMs / 1e3 * timescale;
27
6
  return Math.round(scaledTime);
@@ -1,16 +1,9 @@
1
1
  import { globalInputCache } from "./GlobalInputCache.js";
2
2
  import { ALL_FORMATS, BlobSource, CanvasSink, Input } from "mediabunny";
3
- /**
4
- * Shared thumbnail extraction logic for all MediaEngine implementations
5
- * Eliminates code duplication and provides consistent behavior
6
- */
7
3
  var ThumbnailExtractor = class {
8
4
  constructor(mediaEngine) {
9
5
  this.mediaEngine = mediaEngine;
10
6
  }
11
- /**
12
- * Extract thumbnails at multiple timestamps efficiently using segment batching
13
- */
14
7
  async extractThumbnails(timestamps, rendition, durationMs) {
15
8
  if (timestamps.length === 0) return [];
16
9
  const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);
@@ -32,9 +25,6 @@ var ThumbnailExtractor = class {
32
25
  return results.get(t) || null;
33
26
  });
34
27
  }
35
- /**
36
- * Group timestamps by segment ID for efficient batch processing
37
- */
38
28
  groupTimestampsBySegment(timestamps, rendition) {
39
29
  const segmentGroups = /* @__PURE__ */ new Map();
40
30
  for (const timeMs of timestamps) try {
@@ -50,9 +40,6 @@ var ThumbnailExtractor = class {
50
40
  }
51
41
  return segmentGroups;
52
42
  }
53
- /**
54
- * Extract thumbnails for a specific segment using CanvasSink
55
- */
56
43
  async extractSegmentThumbnails(segmentId, timestamps, rendition) {
57
44
  const results = /* @__PURE__ */ new Map();
58
45
  try {
@@ -95,10 +82,6 @@ var ThumbnailExtractor = class {
95
82
  }
96
83
  return results;
97
84
  }
98
- /**
99
- * Convert global timestamps to segment-relative timestamps for mediabunny
100
- * This is where the main difference between JIT and Asset engines lies
101
- */
102
85
  convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
103
86
  return this.mediaEngine.convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition);
104
87
  }
@@ -9,10 +9,6 @@ const getLatestMediaEngine = async (host, signal) => {
9
9
  if (!mediaEngine) throw new Error("Media engine is not available");
10
10
  return mediaEngine;
11
11
  };
12
- /**
13
- * Core logic for creating a MediaEngine with explicit dependencies.
14
- * Pure function that requires all dependencies to be provided.
15
- */
16
12
  const createMediaEngine = (host) => {
17
13
  const { src, assetId, urlGenerator, apiHost } = host;
18
14
  if (assetId !== null && assetId !== void 0 && assetId.trim() !== "") {
@@ -25,15 +21,10 @@ const createMediaEngine = (host) => {
25
21
  }
26
22
  const lowerSrc = src.toLowerCase();
27
23
  if (!lowerSrc.startsWith("http://") && !lowerSrc.startsWith("https://")) return AssetMediaEngine.fetch(host, urlGenerator, src);
28
- const configuration = host.closest("ef-configuration");
29
- if (configuration?.mediaEngine === "local") return AssetMediaEngine.fetch(host, urlGenerator, src);
24
+ if (host.closest("ef-configuration")?.mediaEngine === "local") return AssetMediaEngine.fetch(host, urlGenerator, src);
30
25
  const url = urlGenerator.generateManifestUrl(src);
31
26
  return JitMediaEngine.fetch(host, urlGenerator, url);
32
27
  };
33
- /**
34
- * Handle completion of media engine task - triggers necessary updates.
35
- * Extracted for testability.
36
- */
37
28
  const handleMediaEngineComplete = (host) => {
38
29
  host.requestUpdate("intrinsicDurationMs");
39
30
  host.requestUpdate("ownCurrentTimeMs");
@@ -0,0 +1,29 @@
1
+ import { BufferedSeekingInput } from '../BufferedSeekingInput';
2
+ /**
3
+ * Cache for main video BufferedSeekingInput instances
4
+ * Main video segments are typically 2s long, so we can reuse the same input
5
+ * for multiple frames within that segment (e.g., 60 frames at 30fps)
6
+ */
7
+ export declare class MainVideoInputCache {
8
+ private cache;
9
+ private maxCacheSize;
10
+ /**
11
+ * Create a cache key that uniquely identifies a segment
12
+ */
13
+ private getCacheKey;
14
+ /**
15
+ * Get or create BufferedSeekingInput for a main video segment
16
+ */
17
+ getOrCreateInput(src: string, segmentId: number, renditionId: string | undefined, createInputFn: () => Promise<BufferedSeekingInput | undefined>): Promise<BufferedSeekingInput | undefined>;
18
+ /**
19
+ * Clear the entire cache (called when video changes)
20
+ */
21
+ clear(): void;
22
+ /**
23
+ * Get cache statistics
24
+ */
25
+ getStats(): {
26
+ size: number;
27
+ cacheKeys: string[];
28
+ };
29
+ }
@@ -0,0 +1,32 @@
1
+ var MainVideoInputCache = class {
2
+ constructor() {
3
+ this.cache = /* @__PURE__ */ new Map();
4
+ this.maxCacheSize = 10;
5
+ }
6
+ getCacheKey(src, segmentId, renditionId) {
7
+ return `${src}:${renditionId || "default"}:${segmentId}`;
8
+ }
9
+ async getOrCreateInput(src, segmentId, renditionId, createInputFn) {
10
+ const cacheKey = this.getCacheKey(src, segmentId, renditionId);
11
+ const cached = this.cache.get(cacheKey);
12
+ if (cached) return cached;
13
+ const input = await createInputFn();
14
+ if (!input) return;
15
+ this.cache.set(cacheKey, input);
16
+ if (this.cache.size > this.maxCacheSize) {
17
+ const oldestKey = this.cache.keys().next().value;
18
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
19
+ }
20
+ return input;
21
+ }
22
+ clear() {
23
+ this.cache.clear();
24
+ }
25
+ getStats() {
26
+ return {
27
+ size: this.cache.size,
28
+ cacheKeys: Array.from(this.cache.keys())
29
+ };
30
+ }
31
+ };
32
+ export { MainVideoInputCache };
@@ -1,21 +1,13 @@
1
- /**
2
- * Cache for scrub BufferedSeekingInput instances
3
- * Since scrub segments are 30s long, we can reuse the same input for many seeks
4
- * within that time range, making scrub seeking very efficient
5
- */
6
1
  var ScrubInputCache = class {
7
2
  constructor() {
8
3
  this.cache = /* @__PURE__ */ new Map();
9
4
  this.maxCacheSize = 5;
10
5
  }
11
- /**
12
- * Get or create BufferedSeekingInput for a scrub segment
13
- */
14
6
  async getOrCreateInput(segmentId, createInputFn) {
15
7
  const cached = this.cache.get(segmentId);
16
8
  if (cached) return cached;
17
9
  const input = await createInputFn();
18
- if (!input) return void 0;
10
+ if (!input) return;
19
11
  this.cache.set(segmentId, input);
20
12
  if (this.cache.size > this.maxCacheSize) {
21
13
  const oldestKey = this.cache.keys().next().value;
@@ -23,15 +15,9 @@ var ScrubInputCache = class {
23
15
  }
24
16
  return input;
25
17
  }
26
- /**
27
- * Clear the entire cache (called when video changes)
28
- */
29
18
  clear() {
30
19
  this.cache.clear();
31
20
  }
32
- /**
33
- * Get cache statistics
34
- */
35
21
  getStats() {
36
22
  return {
37
23
  size: this.cache.size,
@@ -2,11 +2,6 @@ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
3
  import { manageMediaBuffer } from "../shared/BufferUtils.js";
4
4
  import { Task } from "@lit/task";
5
- /**
6
- * Scrub video buffer task - aggressively preloads the ENTIRE scrub track
7
- * Unlike main video buffering, this loads the full duration with higher concurrency
8
- * for instant visual feedback during seeking
9
- */
10
5
  const makeScrubVideoBufferTask = (host) => {
11
6
  let currentState = {
12
7
  currentSeekTimeMs: 0,
@@ -39,7 +34,7 @@ const makeScrubVideoBufferTask = (host) => {
39
34
  } catch (error) {
40
35
  console.warn("ScrubBuffer: Failed to cache scrub init segment:", error);
41
36
  }
42
- const newState = await manageMediaBuffer(0, {
37
+ return await manageMediaBuffer(0, {
43
38
  bufferDurationMs: mediaEngine.durationMs,
44
39
  maxParallelFetches: 10,
45
40
  enableBuffering: true,
@@ -59,7 +54,6 @@ const makeScrubVideoBufferTask = (host) => {
59
54
  console.warn(`ScrubBuffer: ${message}`, error);
60
55
  }
61
56
  });
62
- return newState;
63
57
  } catch (error) {
64
58
  if (signal.aborted) return currentState;
65
59
  console.warn("ScrubBuffer failed:", error);
@@ -8,19 +8,22 @@ const makeScrubVideoInputTask = (host) => {
8
8
  console.error("scrubVideoInputTask error", error);
9
9
  },
10
10
  onComplete: (_value) => {},
11
- task: async () => {
11
+ task: async (_, { signal }) => {
12
12
  const initSegment = await host.scrubVideoInitSegmentFetchTask.taskComplete;
13
+ if (signal.aborted) return void 0;
13
14
  const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
15
+ if (signal.aborted) return void 0;
14
16
  if (!initSegment || !segment) throw new Error("Scrub init segment or segment is not available");
15
17
  const mediaEngine = await host.mediaEngineTask.taskComplete;
16
- const scrubRendition = mediaEngine.getScrubVideoRendition();
17
- const startTimeOffsetMs = scrubRendition?.startTimeOffsetMs;
18
- const input = new BufferedSeekingInput(await new Blob([initSegment, segment]).arrayBuffer(), {
18
+ if (signal.aborted) return void 0;
19
+ const startTimeOffsetMs = mediaEngine.getScrubVideoRendition()?.startTimeOffsetMs;
20
+ const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
21
+ if (signal.aborted) return void 0;
22
+ return new BufferedSeekingInput(arrayBuffer, {
19
23
  videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
20
24
  audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
21
25
  startTimeOffsetMs
22
26
  });
23
- return input;
24
27
  }
25
28
  });
26
29
  };
@@ -1,6 +1,6 @@
1
1
  import { ScrubInputCache } from "./ScrubInputCache.js";
2
2
  import { Task } from "@lit/task";
3
- const scrubInputCache = new ScrubInputCache();
3
+ var scrubInputCache = new ScrubInputCache();
4
4
  const makeScrubVideoSeekTask = (host) => {
5
5
  return new Task(host, {
6
6
  args: () => [host.desiredSeekTimeMs],
@@ -11,22 +11,21 @@ const makeScrubVideoSeekTask = (host) => {
11
11
  task: async ([desiredSeekTimeMs], { signal }) => {
12
12
  signal.throwIfAborted();
13
13
  const mediaEngine = host.mediaEngineTask.value;
14
- if (!mediaEngine) return void 0;
14
+ if (!mediaEngine) return;
15
15
  const scrubRendition = mediaEngine.getScrubVideoRendition();
16
- if (!scrubRendition) return void 0;
16
+ if (!scrubRendition) return;
17
17
  const scrubRenditionWithSrc = {
18
18
  ...scrubRendition,
19
19
  src: mediaEngine.src
20
20
  };
21
21
  const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
22
- if (segmentId === void 0) return void 0;
23
- const isCached = mediaEngine.isSegmentCached(segmentId, scrubRenditionWithSrc);
24
- if (!isCached) return void 0;
22
+ if (segmentId === void 0) return;
23
+ if (!mediaEngine.isSegmentCached(segmentId, scrubRenditionWithSrc)) return;
25
24
  signal.throwIfAborted();
26
25
  try {
27
26
  const scrubInput = await scrubInputCache.getOrCreateInput(segmentId, async () => {
28
27
  const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc)]);
29
- if (!initSegment || !mediaSegment || signal.aborted) return void 0;
28
+ if (!initSegment || !mediaSegment || signal.aborted) return;
30
29
  const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
31
30
  const { EFMedia } = await import("../../EFMedia.js");
32
31
  return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
@@ -35,16 +34,16 @@ const makeScrubVideoSeekTask = (host) => {
35
34
  startTimeOffsetMs: scrubRendition.startTimeOffsetMs
36
35
  });
37
36
  });
38
- if (!scrubInput) return void 0;
37
+ if (!scrubInput) return;
38
+ if (signal.aborted) return;
39
39
  const videoTrack = await scrubInput.getFirstVideoTrack();
40
- if (!videoTrack) return void 0;
41
- signal.throwIfAborted();
42
- const sample = await scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
43
- return sample;
40
+ if (!videoTrack) return;
41
+ if (signal.aborted) return;
42
+ return await scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
44
43
  } catch (error) {
45
44
  if (signal.aborted) return void 0;
46
45
  console.warn("Failed to get scrub video sample:", error);
47
- return void 0;
46
+ return;
48
47
  }
49
48
  }
50
49
  });
@@ -11,7 +11,7 @@ const makeScrubVideoSegmentIdTask = (host) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
12
  signal.throwIfAborted();
13
13
  const scrubRendition = mediaEngine.getScrubVideoRendition();
14
- if (!scrubRendition) return void 0;
14
+ if (!scrubRendition) return;
15
15
  return mediaEngine.computeSegmentId(targetSeekTimeMs, {
16
16
  ...scrubRendition,
17
17
  src: mediaEngine.src