@editframe/elements 0.20.3-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 (128) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +51 -29
  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.d.ts +4 -4
  11. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -32
  12. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
  13. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -67
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
  15. package/dist/elements/EFMedia/JitMediaEngine.js +22 -23
  16. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
  17. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -3
  18. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +2 -2
  19. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +9 -7
  20. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  21. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
  22. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +2 -2
  23. package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
  24. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +6 -3
  25. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
  26. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -17
  27. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
  28. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -13
  29. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  30. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
  31. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  32. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
  33. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  34. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
  35. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +2 -16
  36. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  37. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  38. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  39. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +3 -8
  40. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
  41. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -7
  42. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  43. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
  44. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -3
  45. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -71
  46. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +8 -12
  47. package/dist/elements/EFMedia.d.ts +2 -1
  48. package/dist/elements/EFMedia.js +26 -23
  49. package/dist/elements/EFSourceMixin.js +5 -7
  50. package/dist/elements/EFSurface.js +6 -9
  51. package/dist/elements/EFTemporal.js +19 -37
  52. package/dist/elements/EFThumbnailStrip.js +16 -59
  53. package/dist/elements/EFTimegroup.js +96 -91
  54. package/dist/elements/EFVideo.d.ts +6 -2
  55. package/dist/elements/EFVideo.js +142 -107
  56. package/dist/elements/EFWaveform.js +18 -27
  57. package/dist/elements/SampleBuffer.js +2 -5
  58. package/dist/elements/TargetController.js +3 -3
  59. package/dist/elements/durationConverter.js +4 -4
  60. package/dist/elements/updateAnimations.js +14 -35
  61. package/dist/gui/ContextMixin.js +23 -52
  62. package/dist/gui/EFConfiguration.js +7 -7
  63. package/dist/gui/EFControls.js +5 -5
  64. package/dist/gui/EFFilmstrip.js +77 -98
  65. package/dist/gui/EFFitScale.js +5 -6
  66. package/dist/gui/EFFocusOverlay.js +4 -4
  67. package/dist/gui/EFPreview.js +4 -4
  68. package/dist/gui/EFScrubber.js +9 -9
  69. package/dist/gui/EFTimeDisplay.js +5 -5
  70. package/dist/gui/EFToggleLoop.js +4 -4
  71. package/dist/gui/EFTogglePlay.js +5 -5
  72. package/dist/gui/EFWorkbench.js +5 -5
  73. package/dist/gui/TWMixin2.js +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  76. package/dist/otel/BridgeSpanExporter.js +87 -0
  77. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  78. package/dist/otel/setupBrowserTracing.js +30 -0
  79. package/dist/otel/tracingHelpers.d.ts +34 -0
  80. package/dist/otel/tracingHelpers.js +113 -0
  81. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  82. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  83. package/dist/transcoding/types/index.d.ts +6 -4
  84. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  85. package/dist/utils/LRUCache.js +6 -53
  86. package/package.json +10 -2
  87. package/src/elements/EFCaptions.browsertest.ts +2 -0
  88. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
  89. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
  90. package/src/elements/EFMedia/AssetMediaEngine.ts +81 -43
  91. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
  92. package/src/elements/EFMedia/BaseMediaEngine.ts +120 -60
  93. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  94. package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
  95. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
  96. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
  97. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
  98. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +18 -8
  99. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
  100. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
  101. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
  102. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
  103. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
  104. package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
  105. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
  106. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
  107. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
  108. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
  109. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  110. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
  111. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
  112. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +17 -15
  113. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  114. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
  115. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
  116. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -125
  117. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
  118. package/src/elements/EFMedia.ts +18 -2
  119. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
  120. package/src/elements/EFTimegroup.browsertest.ts +10 -8
  121. package/src/elements/EFTimegroup.ts +165 -77
  122. package/src/elements/EFVideo.browsertest.ts +19 -27
  123. package/src/elements/EFVideo.ts +203 -101
  124. package/src/otel/BridgeSpanExporter.ts +150 -0
  125. package/src/otel/setupBrowserTracing.ts +68 -0
  126. package/src/otel/tracingHelpers.ts +251 -0
  127. package/src/transcoding/types/index.ts +6 -4
  128. package/types.json +1 -1
@@ -1,13 +1,13 @@
1
1
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
2
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
2
3
  import { EFSourceMixin } from "./EFSourceMixin.js";
3
4
  import { EFTemporal } from "./EFTemporal.js";
4
5
  import { FetchMixin } from "./FetchMixin.js";
5
6
  import { Task } from "@lit/task";
6
7
  import { LitElement, css, html } from "lit";
7
8
  import { customElement, property } from "lit/decorators.js";
8
- import _decorate from "@oxc-project/runtime/helpers/decorate";
9
9
  import { createRef, ref } from "lit/directives/ref.js";
10
- let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitElement), { assetType: "image_files" })) {
10
+ var EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitElement), { assetType: "image_files" })) {
11
11
  constructor(..._args) {
12
12
  super(..._args);
13
13
  this.imageRef = createRef();
@@ -65,8 +65,7 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
65
65
  }
66
66
  render() {
67
67
  const assetPath = this.assetPath();
68
- const isDirectUrl = this.isDirectUrl(assetPath);
69
- return isDirectUrl ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
68
+ return this.isDirectUrl(assetPath) ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
70
69
  }
71
70
  isDirectUrl(src) {
72
71
  return src.startsWith("http://") || src.startsWith("https://");
@@ -80,10 +79,10 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
80
79
  return this.hasExplicitDuration;
81
80
  }
82
81
  };
83
- _decorate([property({
82
+ __decorate([property({
84
83
  type: String,
85
84
  attribute: "asset-id",
86
85
  reflect: true
87
86
  })], EFImage.prototype, "assetId", null);
88
- EFImage = _decorate([customElement("ef-image")], EFImage);
87
+ EFImage = __decorate([customElement("ef-image")], EFImage);
89
88
  export { EFImage };
@@ -2,8 +2,7 @@ import { AssetMediaEngine } from "./AssetMediaEngine.js";
2
2
  var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
3
3
  static async fetchByAssetId(host, _urlGenerator, assetId, apiHost) {
4
4
  const url = `${apiHost}/api/v1/isobmff_files/${assetId}/index`;
5
- const response = await host.fetch(url);
6
- const data = await response.json();
5
+ const data = await (await host.fetch(url)).json();
7
6
  return new AssetIdMediaEngine(host, assetId, data, apiHost);
8
7
  }
9
8
  constructor(host, assetId, data, apiHost) {
@@ -11,8 +10,7 @@ var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
11
10
  this.assetId = assetId;
12
11
  this.apiHost = apiHost;
13
12
  this.data = data;
14
- const longestFragment = Object.values(this.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0);
15
- this.durationMs = longestFragment * 1e3;
13
+ this.durationMs = Object.values(this.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
16
14
  }
17
15
  get initSegmentPaths() {
18
16
  const paths = {};
@@ -13,14 +13,14 @@ export declare class AssetMediaEngine extends BaseMediaEngine implements MediaEn
13
13
  get audioTrackIndex(): import('../../../../assets/src/index.ts').AudioTrackFragmentIndex | undefined;
14
14
  get videoTrackIndex(): import('../../../../assets/src/index.ts').VideoTrackFragmentIndex | undefined;
15
15
  get videoRendition(): {
16
- trackId: number | undefined;
16
+ trackId: number;
17
17
  src: string;
18
18
  startTimeOffsetMs: number | undefined;
19
- };
19
+ } | undefined;
20
20
  get audioRendition(): {
21
- trackId: number | undefined;
21
+ trackId: number;
22
22
  src: string;
23
- };
23
+ } | undefined;
24
24
  get initSegmentPaths(): InitSegmentPaths;
25
25
  get templates(): {
26
26
  initSegment: string;
@@ -1,3 +1,4 @@
1
+ import { withSpan } from "../../otel/tracingHelpers.js";
1
2
  import { BaseMediaEngine } from "./BaseMediaEngine.js";
2
3
  import { convertToScaledTime, roundToMilliseconds } from "./shared/PrecisionUtils.js";
3
4
  var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
@@ -10,10 +11,8 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
10
11
  static async fetch(host, urlGenerator, src) {
11
12
  const engine = new AssetMediaEngine(host, src);
12
13
  const url = urlGenerator.generateTrackFragmentIndexUrl(src);
13
- const data = await engine.fetchManifest(url);
14
- engine.data = data;
15
- const longestFragment = Object.values(engine.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0);
16
- engine.durationMs = longestFragment * 1e3;
14
+ engine.data = await engine.fetchManifest(url);
15
+ engine.durationMs = Object.values(engine.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
17
16
  if (src.startsWith("/")) engine.src = src.slice(1);
18
17
  return engine;
19
18
  }
@@ -24,15 +23,19 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
24
23
  return Object.values(this.data).find((track) => track.type === "video");
25
24
  }
26
25
  get videoRendition() {
26
+ const videoTrack = this.videoTrackIndex;
27
+ if (!videoTrack || videoTrack.track === void 0) return;
27
28
  return {
28
- trackId: this.videoTrackIndex?.track,
29
+ trackId: videoTrack.track,
29
30
  src: this.src,
30
- startTimeOffsetMs: this.videoTrackIndex?.startTimeOffsetMs
31
+ startTimeOffsetMs: videoTrack.startTimeOffsetMs
31
32
  };
32
33
  }
33
34
  get audioRendition() {
35
+ const audioTrack = this.audioTrackIndex;
36
+ if (!audioTrack || audioTrack.track === void 0) return;
34
37
  return {
35
- trackId: this.audioTrackIndex?.track,
38
+ trackId: audioTrack.track,
36
39
  src: this.src
37
40
  };
38
41
  }
@@ -63,25 +66,37 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
63
66
  return `/@ef-track/${this.src}?trackId=${trackId}&segmentId=${segmentId}`;
64
67
  }
65
68
  async fetchInitSegment(rendition, signal) {
66
- if (!rendition.trackId) throw new Error("[fetchInitSegment] Track ID is required for asset metadata");
67
- const url = this.buildInitSegmentUrl(rendition.trackId);
68
- const initSegment = this.data[rendition.trackId]?.initSegment;
69
- if (!initSegment) throw new Error("Init segment not found");
70
- const headers = { Range: `bytes=${initSegment.offset}-${initSegment.offset + initSegment.size - 1}` };
71
- return this.fetchMediaWithHeaders(url, headers, signal);
69
+ return withSpan("assetEngine.fetchInitSegment", {
70
+ trackId: rendition.trackId || -1,
71
+ src: rendition.src
72
+ }, void 0, async (span) => {
73
+ if (!rendition.trackId) throw new Error("[fetchInitSegment] Track ID is required for asset metadata");
74
+ const url = this.buildInitSegmentUrl(rendition.trackId);
75
+ const initSegment = this.data[rendition.trackId]?.initSegment;
76
+ if (!initSegment) throw new Error("Init segment not found");
77
+ span.setAttribute("offset", initSegment.offset);
78
+ span.setAttribute("size", initSegment.size);
79
+ const headers = { Range: `bytes=${initSegment.offset}-${initSegment.offset + initSegment.size - 1}` };
80
+ return this.fetchMediaWithHeaders(url, headers, signal);
81
+ });
72
82
  }
73
83
  async fetchMediaSegment(segmentId, rendition, signal) {
74
- if (!rendition.trackId) throw new Error("[fetchMediaSegment] Track ID is required for asset metadata");
75
- if (segmentId === void 0) throw new Error("Segment ID is not available");
76
- const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
77
- const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
78
- if (!mediaSegment) throw new Error("Media segment not found");
79
- const headers = { Range: `bytes=${mediaSegment.offset}-${mediaSegment.offset + mediaSegment.size - 1}` };
80
- return this.fetchMediaWithHeaders(url, headers, signal);
81
- }
82
- /**
83
- * Calculate audio segments for variable-duration segments using track fragment index
84
- */
84
+ return withSpan("assetEngine.fetchMediaSegment", {
85
+ segmentId,
86
+ trackId: rendition.trackId || -1,
87
+ src: rendition.src
88
+ }, void 0, async (span) => {
89
+ if (!rendition.trackId) throw new Error("[fetchMediaSegment] Track ID is required for asset metadata");
90
+ if (segmentId === void 0) throw new Error("Segment ID is not available");
91
+ const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
92
+ const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
93
+ if (!mediaSegment) throw new Error("Media segment not found");
94
+ span.setAttribute("offset", mediaSegment.offset);
95
+ span.setAttribute("size", mediaSegment.size);
96
+ const headers = { Range: `bytes=${mediaSegment.offset}-${mediaSegment.offset + mediaSegment.size - 1}` };
97
+ return this.fetchMediaWithHeaders(url, headers, signal);
98
+ });
99
+ }
85
100
  calculateAudioSegmentRange(fromMs, toMs, rendition, _durationMs) {
86
101
  if (fromMs >= toMs || !rendition.trackId) {
87
102
  console.warn(`calculateAudioSegmentRange: invalid fromMs ${fromMs} toMs ${toMs} rendition ${JSON.stringify(rendition)}`);
@@ -145,13 +160,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
145
160
  }
146
161
  return nearestSegmentIndex;
147
162
  }
148
- getScrubVideoRendition() {
149
- return void 0;
150
- }
151
- /**
152
- * Get preferred buffer configuration for this media engine
153
- * AssetMediaEngine uses lower buffering since segments are already optimized
154
- */
163
+ getScrubVideoRendition() {}
155
164
  getBufferConfig() {
156
165
  return {
157
166
  videoBufferDurationMs: 2e3,
@@ -162,7 +171,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
162
171
  }
163
172
  convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
164
173
  {
165
- if (!rendition.trackId) throw new Error("[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata");
174
+ if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
166
175
  const trackData = this.data[rendition.trackId];
167
176
  if (!trackData) throw new Error("Track not found");
168
177
  const segment = trackData.segments?.[segmentId];
@@ -10,8 +10,16 @@ export declare abstract class BaseMediaEngine {
10
10
  constructor(host: EFMedia);
11
11
  abstract get videoRendition(): VideoRendition | undefined;
12
12
  abstract get audioRendition(): AudioRendition | undefined;
13
- getVideoRendition(): VideoRendition;
14
- getAudioRendition(): AudioRendition;
13
+ /**
14
+ * Get video rendition if available. Returns undefined for audio-only assets.
15
+ * Callers should handle undefined gracefully.
16
+ */
17
+ getVideoRendition(): VideoRendition | undefined;
18
+ /**
19
+ * Get audio rendition if available. Returns undefined for video-only assets.
20
+ * Callers should handle undefined gracefully.
21
+ */
22
+ getAudioRendition(): AudioRendition | undefined;
15
23
  /**
16
24
  * Generate cache key for segment requests
17
25
  */
@@ -1,3 +1,4 @@
1
+ import { withSpan } from "../../otel/tracingHelpers.js";
1
2
  import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
2
3
  import { SizeAwareLRUCache } from "../../utils/LRUCache.js";
3
4
  const mediaCache = new SizeAwareLRUCache(100 * 1024 * 1024);
@@ -7,53 +8,72 @@ var BaseMediaEngine = class {
7
8
  this.host = host;
8
9
  }
9
10
  getVideoRendition() {
10
- if (!this.videoRendition) throw new Error("No video rendition available");
11
11
  return this.videoRendition;
12
12
  }
13
13
  getAudioRendition() {
14
- if (!this.audioRendition) throw new Error("No audio rendition available");
15
14
  return this.audioRendition;
16
15
  }
17
- /**
18
- * Generate cache key for segment requests
19
- */
20
16
  getSegmentCacheKey(segmentId, rendition) {
21
17
  return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;
22
18
  }
23
- /**
24
- * Unified fetch method with caching and global deduplication
25
- * All requests (media, manifest, init segments) go through this method
26
- */
27
19
  async fetchWithCache(url, options) {
28
- const { responseType, headers, signal } = options;
29
- const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
30
- const cached = mediaCache.get(cacheKey);
31
- if (cached) {
32
- if (signal) return this.handleAbortForCachedRequest(cached, signal);
33
- return cached;
34
- }
35
- const promise = globalRequestDeduplicator.executeRequest(cacheKey, async () => {
36
- try {
37
- const response = await this.host.fetch(url, { headers });
38
- if (responseType === "json") return response.json();
39
- return response.arrayBuffer();
40
- } catch (error) {
20
+ return withSpan("mediaEngine.fetchWithCache", {
21
+ url: url.length > 100 ? `${url.substring(0, 100)}...` : url,
22
+ responseType: options.responseType,
23
+ hasHeaders: !!options.headers
24
+ }, void 0, async (span) => {
25
+ const t0 = performance.now();
26
+ const { responseType, headers, signal } = options;
27
+ const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
28
+ const t1 = performance.now();
29
+ const cached = mediaCache.get(cacheKey);
30
+ const t2 = performance.now();
31
+ span.setAttribute("cacheLookupMs", Math.round((t2 - t1) * 1e3) / 1e3);
32
+ if (cached) {
33
+ span.setAttribute("cacheHit", true);
34
+ if (signal) {
35
+ const t3 = performance.now();
36
+ const result$1 = await this.handleAbortForCachedRequest(cached, signal);
37
+ const t4 = performance.now();
38
+ span.setAttribute("handleAbortMs", Math.round((t4 - t3) * 100) / 100);
39
+ span.setAttribute("totalCacheHitMs", Math.round((t4 - t0) * 100) / 100);
40
+ return result$1;
41
+ }
42
+ span.setAttribute("totalCacheHitMs", Math.round((t2 - t0) * 100) / 100);
43
+ return cached;
44
+ }
45
+ span.setAttribute("cacheHit", false);
46
+ const promise = globalRequestDeduplicator.executeRequest(cacheKey, async () => {
47
+ const fetchStart = performance.now();
48
+ try {
49
+ const response = await this.host.fetch(url, { headers });
50
+ const fetchEnd = performance.now();
51
+ span.setAttribute("fetchMs", fetchEnd - fetchStart);
52
+ if (responseType === "json") return response.json();
53
+ const buffer = await response.arrayBuffer();
54
+ span.setAttribute("sizeBytes", buffer.byteLength);
55
+ return buffer;
56
+ } catch (error) {
57
+ if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
58
+ throw error;
59
+ }
60
+ });
61
+ mediaCache.set(cacheKey, promise);
62
+ promise.catch((error) => {
41
63
  if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
42
- throw error;
64
+ });
65
+ if (signal) {
66
+ const result$1 = await this.handleAbortForCachedRequest(promise, signal);
67
+ const tEnd$1 = performance.now();
68
+ span.setAttribute("totalFetchMs", Math.round((tEnd$1 - t0) * 100) / 100);
69
+ return result$1;
43
70
  }
71
+ const result = await promise;
72
+ const tEnd = performance.now();
73
+ span.setAttribute("totalFetchMs", Math.round((tEnd - t0) * 100) / 100);
74
+ return result;
44
75
  });
45
- mediaCache.set(cacheKey, promise);
46
- promise.catch((error) => {
47
- if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
48
- });
49
- if (signal) return this.handleAbortForCachedRequest(promise, signal);
50
- return promise;
51
- }
52
- /**
53
- * Handles abort logic for a cached request without affecting the underlying fetch
54
- * This allows multiple instances to share the same cached request while each
55
- * manages their own abort behavior
56
- */
76
+ }
57
77
  handleAbortForCachedRequest(promise, signal) {
58
78
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
59
79
  return Promise.race([promise, new Promise((_, reject) => {
@@ -93,39 +113,22 @@ var BaseMediaEngine = class {
93
113
  async fetchMediaCacheWithHeaders(url, headers, signal) {
94
114
  return this.fetchMediaWithHeaders(url, headers, signal);
95
115
  }
96
- /**
97
- * Fetch media segment with built-in deduplication
98
- * Now uses global deduplication for all requests
99
- */
100
116
  async fetchMediaSegmentWithDeduplication(segmentId, rendition, _signal) {
101
117
  const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
102
118
  return globalRequestDeduplicator.executeRequest(cacheKey, async () => {
103
119
  return this.fetchMediaSegment(segmentId, rendition);
104
120
  });
105
121
  }
106
- /**
107
- * Check if a segment is currently being fetched
108
- */
109
122
  isSegmentBeingFetched(segmentId, rendition) {
110
123
  const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
111
124
  return globalRequestDeduplicator.isPending(cacheKey);
112
125
  }
113
- /**
114
- * Get count of active segment requests (for debugging/monitoring)
115
- */
116
126
  getActiveSegmentRequestCount() {
117
127
  return globalRequestDeduplicator.getPendingCount();
118
128
  }
119
- /**
120
- * Cancel all active segment requests (for cleanup)
121
- */
122
129
  cancelAllSegmentRequests() {
123
130
  globalRequestDeduplicator.clear();
124
131
  }
125
- /**
126
- * Calculate audio segments needed for a time range
127
- * Each media engine implements this based on their segment structure
128
- */
129
132
  calculateAudioSegmentRange(fromMs, toMs, rendition, durationMs) {
130
133
  if (fromMs >= toMs) return [];
131
134
  const segments = [];
@@ -163,37 +166,24 @@ var BaseMediaEngine = class {
163
166
  }
164
167
  return segments;
165
168
  }
166
- /**
167
- * Check if a segment is cached for a given rendition
168
- * This needs to check the URL-based cache since that's where segments are actually stored
169
- */
170
169
  isSegmentCached(segmentId, rendition) {
171
170
  try {
172
171
  const maybeJitEngine = this;
173
172
  if (maybeJitEngine.urlGenerator && typeof maybeJitEngine.urlGenerator.generateSegmentUrl === "function") {
174
173
  if (!rendition.id) return false;
175
174
  const segmentUrl = maybeJitEngine.urlGenerator.generateSegmentUrl(segmentId, rendition.id, maybeJitEngine);
176
- const urlIsCached = mediaCache.has(segmentUrl);
177
- return urlIsCached;
175
+ return mediaCache.has(segmentUrl);
178
176
  }
179
177
  const cacheKey = `${rendition.src}-${rendition.id || "default"}-${segmentId}-${rendition.trackId}`;
180
- const isCached = mediaCache.has(cacheKey);
181
- return isCached;
178
+ return mediaCache.has(cacheKey);
182
179
  } catch (error) {
183
180
  console.warn(`🎬 BaseMediaEngine: Error checking if segment ${segmentId} is cached:`, error);
184
181
  return false;
185
182
  }
186
183
  }
187
- /**
188
- * Get cached segment IDs from a list for a given rendition
189
- */
190
184
  getCachedSegments(segmentIds, rendition) {
191
185
  return new Set(segmentIds.filter((id) => this.isSegmentCached(id, rendition)));
192
186
  }
193
- /**
194
- * Extract thumbnail canvases at multiple timestamps efficiently
195
- * Default implementation provides helpful error information
196
- */
197
187
  async extractThumbnails(timestamps) {
198
188
  const engineName = this.constructor.name;
199
189
  console.warn(`${engineName}: extractThumbnails not properly implemented. This MediaEngine type does not support thumbnail generation. Supported engines: JitMediaEngine. Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? "" : "s"}.`);