@editframe/elements 0.38.1 → 0.40.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 (78) hide show
  1. package/dist/EF_FRAMEGEN.js +1 -0
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/elements/EFCaptions.d.ts +2 -2
  4. package/dist/elements/EFCaptions.js +1 -1
  5. package/dist/elements/EFCaptions.js.map +1 -1
  6. package/dist/elements/EFImage.js +3 -4
  7. package/dist/elements/EFImage.js.map +1 -1
  8. package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
  9. package/dist/elements/EFMedia/CachedFetcher.js +99 -0
  10. package/dist/elements/EFMedia/CachedFetcher.js.map +1 -0
  11. package/dist/elements/EFMedia/MediaEngine.d.ts +19 -0
  12. package/dist/elements/EFMedia/MediaEngine.js +129 -0
  13. package/dist/elements/EFMedia/MediaEngine.js.map +1 -0
  14. package/dist/elements/EFMedia/SegmentIndex.d.ts +32 -0
  15. package/dist/elements/EFMedia/SegmentIndex.js +185 -0
  16. package/dist/elements/EFMedia/SegmentIndex.js.map +1 -0
  17. package/dist/elements/EFMedia/SegmentTransport.d.ts +12 -0
  18. package/dist/elements/EFMedia/SegmentTransport.js +69 -0
  19. package/dist/elements/EFMedia/SegmentTransport.js.map +1 -0
  20. package/dist/elements/EFMedia/TimingModel.d.ts +10 -0
  21. package/dist/elements/EFMedia/TimingModel.js +28 -0
  22. package/dist/elements/EFMedia/TimingModel.js.map +1 -0
  23. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +7 -6
  24. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  25. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +13 -34
  26. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  27. package/dist/elements/EFMedia.d.ts +4 -3
  28. package/dist/elements/EFMedia.js +14 -31
  29. package/dist/elements/EFMedia.js.map +1 -1
  30. package/dist/elements/EFSourceMixin.js +1 -1
  31. package/dist/elements/EFSourceMixin.js.map +1 -1
  32. package/dist/elements/EFTemporal.js +2 -1
  33. package/dist/elements/EFTemporal.js.map +1 -1
  34. package/dist/elements/EFTimegroup.js +2 -1
  35. package/dist/elements/EFTimegroup.js.map +1 -1
  36. package/dist/elements/EFVideo.js +204 -187
  37. package/dist/elements/EFVideo.js.map +1 -1
  38. package/dist/gui/EFConfiguration.d.ts +0 -7
  39. package/dist/gui/EFConfiguration.js +0 -5
  40. package/dist/gui/EFConfiguration.js.map +1 -1
  41. package/dist/gui/EFWorkbench.d.ts +2 -0
  42. package/dist/gui/EFWorkbench.js +68 -1
  43. package/dist/gui/EFWorkbench.js.map +1 -1
  44. package/dist/gui/PlaybackController.d.ts +2 -0
  45. package/dist/gui/PlaybackController.js +11 -1
  46. package/dist/gui/PlaybackController.js.map +1 -1
  47. package/dist/gui/ef-theme.css +11 -0
  48. package/dist/gui/timeline/tracks/AudioTrack.js +28 -30
  49. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
  50. package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +1 -0
  51. package/dist/gui/timeline/tracks/EFThumbnailStrip.js +41 -8
  52. package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
  53. package/dist/gui/timeline/tracks/VideoTrack.js +2 -2
  54. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
  55. package/dist/gui/timeline/tracks/waveformUtils.js +19 -19
  56. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
  57. package/dist/preview/QualityUpgradeScheduler.d.ts +8 -0
  58. package/dist/preview/QualityUpgradeScheduler.js +13 -1
  59. package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
  60. package/dist/preview/renderTimegroupToVideo.js +3 -3
  61. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  62. package/dist/preview/renderVideoToVideo.js +5 -6
  63. package/dist/preview/renderVideoToVideo.js.map +1 -1
  64. package/dist/transcoding/types/index.d.ts +6 -94
  65. package/dist/transcoding/utils/UrlGenerator.d.ts +3 -12
  66. package/dist/transcoding/utils/UrlGenerator.js +3 -29
  67. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  68. package/package.json +2 -2
  69. package/test/setup.ts +1 -1
  70. package/test/useAssetMSW.ts +0 -100
  71. package/dist/elements/EFMedia/AssetMediaEngine.js +0 -284
  72. package/dist/elements/EFMedia/AssetMediaEngine.js.map +0 -1
  73. package/dist/elements/EFMedia/BaseMediaEngine.js +0 -200
  74. package/dist/elements/EFMedia/BaseMediaEngine.js.map +0 -1
  75. package/dist/elements/EFMedia/FileMediaEngine.js +0 -122
  76. package/dist/elements/EFMedia/FileMediaEngine.js.map +0 -1
  77. package/dist/elements/EFMedia/JitMediaEngine.js +0 -157
  78. package/dist/elements/EFMedia/JitMediaEngine.js.map +0 -1
@@ -3,30 +3,23 @@ import { DEFAULT_MEDIABUNNY_TIMEOUT_MS, withTimeout } from "./timeoutUtils.js";
3
3
  import { ALL_FORMATS, BlobSource, CanvasSink, Input } from "mediabunny";
4
4
 
5
5
  //#region src/elements/EFMedia/shared/ThumbnailExtractor.ts
6
- /**
7
- * Shared thumbnail extraction logic for all MediaEngine implementations
8
- * Eliminates code duplication and provides consistent behavior
9
- */
10
6
  var ThumbnailExtractor = class {
11
7
  constructor(mediaEngine) {
12
8
  this.mediaEngine = mediaEngine;
13
9
  }
14
- /**
15
- * Extract thumbnails at multiple timestamps efficiently using segment batching
16
- */
17
- async extractThumbnails(timestamps, rendition, durationMs, signal) {
10
+ async extractThumbnails(timestamps, track, durationMs, signal) {
18
11
  if (timestamps.length === 0) return [];
19
12
  const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);
20
13
  if (validTimestamps.length === 0) {
21
14
  console.warn(`ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`);
22
15
  return timestamps.map(() => null);
23
16
  }
24
- const segmentGroups = this.groupTimestampsBySegment(validTimestamps, rendition);
17
+ const segmentGroups = this.groupTimestampsBySegment(validTimestamps, track);
25
18
  const results = /* @__PURE__ */ new Map();
26
19
  for (const [segmentId, segmentTimestamps] of segmentGroups) {
27
20
  signal?.throwIfAborted();
28
21
  try {
29
- const segmentResults = await this.extractSegmentThumbnails(segmentId, segmentTimestamps, rendition, signal);
22
+ const segmentResults = await this.extractSegmentThumbnails(segmentId, segmentTimestamps, track, signal);
30
23
  for (const [timestamp, thumbnail] of segmentResults) results.set(timestamp, thumbnail);
31
24
  } catch (error) {
32
25
  if (error instanceof DOMException && error.name === "AbortError") throw error;
@@ -39,45 +32,38 @@ var ThumbnailExtractor = class {
39
32
  return results.get(t) || null;
40
33
  });
41
34
  }
42
- /**
43
- * Group timestamps by segment ID for efficient batch processing
44
- */
45
- groupTimestampsBySegment(timestamps, rendition) {
35
+ groupTimestampsBySegment(timestamps, track) {
46
36
  const segmentGroups = /* @__PURE__ */ new Map();
47
37
  for (const timeMs of timestamps) try {
48
- const segmentId = this.mediaEngine.computeSegmentId(timeMs, rendition);
38
+ const segmentId = this.mediaEngine.index.segmentAt(timeMs, track);
49
39
  if (segmentId !== void 0) {
50
40
  if (!segmentGroups.has(segmentId)) segmentGroups.set(segmentId, []);
51
- const segmentGroup = segmentGroups.get(segmentId) ?? [];
52
- if (!segmentGroup) segmentGroups.set(segmentId, []);
53
- segmentGroup.push(timeMs);
41
+ segmentGroups.get(segmentId).push(timeMs);
54
42
  }
55
43
  } catch (error) {
56
44
  console.warn(`ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`, error);
57
45
  }
58
46
  return segmentGroups;
59
47
  }
60
- /**
61
- * Extract thumbnails for a specific segment using CanvasSink
62
- */
63
- async extractSegmentThumbnails(segmentId, timestamps, rendition, signal) {
48
+ async extractSegmentThumbnails(segmentId, timestamps, track, signal) {
64
49
  const results = /* @__PURE__ */ new Map();
65
50
  try {
66
51
  signal?.throwIfAborted();
67
- const initP = this.mediaEngine.fetchInitSegment(rendition, signal);
68
- const mediaP = this.mediaEngine.fetchMediaSegment(segmentId, rendition, signal);
52
+ const initP = this.mediaEngine.transport.fetchInitSegment(track, signal);
53
+ const mediaP = this.mediaEngine.transport.fetchMediaSegment(segmentId, track, signal);
69
54
  initP.catch(() => {});
70
55
  mediaP.catch(() => {});
71
56
  const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
72
57
  signal?.throwIfAborted();
73
58
  const segmentBlob = new Blob([initSegment, mediaSegment]);
74
- let input = globalInputCache.get(rendition.src, segmentId, rendition.id);
59
+ const renditionId = typeof track.id === "string" ? track.id : void 0;
60
+ let input = globalInputCache.get(track.src, segmentId, renditionId);
75
61
  if (!input) {
76
62
  input = new Input({
77
63
  formats: ALL_FORMATS,
78
64
  source: new BlobSource(segmentBlob)
79
65
  });
80
- globalInputCache.set(rendition.src, segmentId, input, rendition.id);
66
+ globalInputCache.set(track.src, segmentId, input, renditionId);
81
67
  }
82
68
  const videoTrack = await withTimeout(input.getPrimaryVideoTrack(), 5e3, "ThumbnailExtractor.getPrimaryVideoTrack", signal);
83
69
  if (!videoTrack) {
@@ -86,7 +72,7 @@ var ThumbnailExtractor = class {
86
72
  }
87
73
  const sink = new CanvasSink(videoTrack);
88
74
  const sortedTimestamps = [...timestamps].sort((a, b) => a - b);
89
- const relativeTimestamps = this.convertToSegmentRelativeTimestamps(sortedTimestamps, segmentId, rendition);
75
+ const relativeTimestamps = sortedTimestamps.map((ms) => this.mediaEngine.timing.toContainerSeconds(ms, segmentId, track));
90
76
  const timestampResults = [];
91
77
  const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);
92
78
  for await (const result of canvasIterator) {
@@ -113,13 +99,6 @@ var ThumbnailExtractor = class {
113
99
  }
114
100
  return results;
115
101
  }
116
- /**
117
- * Convert global timestamps to segment-relative timestamps for mediabunny
118
- * This is where the main difference between JIT and Asset engines lies
119
- */
120
- convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
121
- return this.mediaEngine.convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition);
122
- }
123
102
  };
124
103
 
125
104
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"ThumbnailExtractor.js","names":["mediaEngine: BaseMediaEngine"],"sources":["../../../../src/elements/EFMedia/shared/ThumbnailExtractor.ts"],"sourcesContent":["import { ALL_FORMATS, BlobSource, CanvasSink, Input } from \"mediabunny\";\nimport type {\n ThumbnailResult,\n VideoRendition,\n} from \"../../../transcoding/types/index.js\";\nimport type { BaseMediaEngine } from \"../BaseMediaEngine.js\";\nimport { globalInputCache } from \"./GlobalInputCache.js\";\nimport { withTimeout, DEFAULT_MEDIABUNNY_TIMEOUT_MS } from \"./timeoutUtils.js\";\n\n/**\n * Shared thumbnail extraction logic for all MediaEngine implementations\n * Eliminates code duplication and provides consistent behavior\n */\nexport class ThumbnailExtractor {\n constructor(private mediaEngine: BaseMediaEngine) {}\n\n /**\n * Extract thumbnails at multiple timestamps efficiently using segment batching\n */\n async extractThumbnails(\n timestamps: number[],\n rendition: VideoRendition,\n durationMs: number,\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n if (timestamps.length === 0) {\n return [];\n }\n\n // Validate and filter timestamps within bounds\n const validTimestamps = timestamps.filter(\n (timeMs) => timeMs >= 0 && timeMs <= durationMs,\n );\n\n if (validTimestamps.length === 0) {\n console.warn(\n `ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`,\n );\n return timestamps.map(() => null);\n }\n\n // Group timestamps by segment for batch processing\n const segmentGroups = this.groupTimestampsBySegment(\n validTimestamps,\n rendition,\n );\n\n // Extract batched by segment using CanvasSink\n const results = new Map<number, ThumbnailResult | null>();\n\n for (const [segmentId, segmentTimestamps] of segmentGroups) {\n // Check abort before processing each segment\n signal?.throwIfAborted();\n\n try {\n const segmentResults = await this.extractSegmentThumbnails(\n segmentId,\n segmentTimestamps,\n rendition,\n signal,\n );\n\n for (const [timestamp, thumbnail] of segmentResults) {\n results.set(timestamp, thumbnail);\n }\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n // Mark all timestamps in this segment as failed\n for (const timestamp of segmentTimestamps) {\n results.set(timestamp, null);\n }\n }\n }\n\n // Return in original order, null for any that failed or were out of bounds\n return timestamps.map((t) => {\n // If timestamp was out of bounds, return null\n if (t < 0 || t > durationMs) {\n return null;\n }\n return results.get(t) || null;\n });\n }\n\n /**\n * Group timestamps by segment ID for efficient batch processing\n */\n private groupTimestampsBySegment(\n timestamps: number[],\n rendition: VideoRendition,\n ): Map<number, number[]> {\n const segmentGroups = new Map<number, number[]>();\n\n for (const timeMs of timestamps) {\n try {\n const segmentId = this.mediaEngine.computeSegmentId(timeMs, rendition);\n if (segmentId !== undefined) {\n if (!segmentGroups.has(segmentId)) {\n segmentGroups.set(segmentId, []);\n }\n const segmentGroup = segmentGroups.get(segmentId) ?? [];\n if (!segmentGroup) {\n segmentGroups.set(segmentId, []);\n }\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 /**\n * Extract thumbnails for a specific segment using CanvasSink\n */\n private async extractSegmentThumbnails(\n segmentId: number,\n timestamps: number[],\n rendition: VideoRendition,\n signal?: AbortSignal,\n ): Promise<Map<number, ThumbnailResult | null>> {\n const results = new Map<number, ThumbnailResult | null>();\n\n try {\n // Check abort before starting segment fetch\n signal?.throwIfAborted();\n\n const initP = this.mediaEngine.fetchInitSegment(rendition, signal!);\n const mediaP = this.mediaEngine.fetchMediaSegment(\n segmentId,\n rendition,\n signal!,\n );\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n\n // Check abort after potentially slow network operations\n signal?.throwIfAborted();\n\n // Create Input for this segment using global shared cache\n const segmentBlob = new Blob([initSegment, mediaSegment]);\n\n let input = globalInputCache.get(rendition.src, segmentId, rendition.id);\n if (!input) {\n input = new Input({\n formats: ALL_FORMATS,\n source: new BlobSource(segmentBlob),\n });\n globalInputCache.set(rendition.src, segmentId, input, rendition.id);\n }\n\n // Set up CanvasSink for batched extraction\n const videoTrack = await withTimeout(\n input.getPrimaryVideoTrack(),\n 5000,\n \"ThumbnailExtractor.getPrimaryVideoTrack\",\n signal,\n );\n if (!videoTrack) {\n // No video track - return nulls for all timestamps\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n return results;\n }\n\n const sink = new CanvasSink(videoTrack);\n\n // IMPORTANT: Sort timestamps for mediabunny - it expects monotonically sorted timestamps\n // Create array of {original, sorted} to map back after extraction\n const sortedTimestamps = [...timestamps].sort((a, b) => a - b);\n\n // Convert sorted global timestamps to segment-relative (in seconds for mediabunny)\n const relativeTimestamps = this.convertToSegmentRelativeTimestamps(\n sortedTimestamps,\n segmentId,\n rendition,\n );\n\n // Batch extract all thumbnails for this segment (in sorted order)\n const timestampResults = [];\n const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);\n for await (const result of canvasIterator) {\n // Wrap each iteration with timeout to prevent hangs\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 // Map results back to original (sorted) timestamps\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 (\n canvas instanceof HTMLCanvasElement ||\n canvas instanceof OffscreenCanvas\n ) {\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 aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Thumbnail extraction can fail for various non-fatal reasons (network issues,\n // missing segments, transcoding not ready). Log as warning and return nulls.\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n // Return nulls for all timestamps on error\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n }\n\n return results;\n }\n\n /**\n * Convert global timestamps to segment-relative timestamps for mediabunny\n * This is where the main difference between JIT and Asset engines lies\n */\n private convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n segmentId: number,\n rendition: VideoRendition,\n ): number[] {\n return this.mediaEngine.convertToSegmentRelativeTimestamps(\n globalTimestamps,\n segmentId,\n rendition,\n );\n }\n}\n"],"mappings":"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"}
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(\n (timeMs) => timeMs >= 0 && timeMs <= durationMs,\n );\n\n if (validTimestamps.length === 0) {\n console.warn(\n `ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`,\n );\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(\n timestamps: number[],\n track: TrackRef,\n ): 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(\n segmentId,\n track,\n signal!,\n );\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 (\n canvas instanceof HTMLCanvasElement ||\n canvas instanceof OffscreenCanvas\n ) {\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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"}
@@ -3,9 +3,10 @@ import { EFSourceMixinInterface } from "./EFSourceMixin.js";
3
3
  import { TemporalMixinInterface } from "./EFTemporal.js";
4
4
  import { FetchMixinInterface } from "./FetchMixin.js";
5
5
  import { ControllableInterface } from "../gui/Controllable.js";
6
- import { AudioSpan, MediaEngine } from "../transcoding/types/index.js";
7
6
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
8
- import * as lit0 from "lit";
7
+ import { MediaEngine } from "./EFMedia/MediaEngine.js";
8
+ import { AudioSpan } from "../transcoding/types/index.js";
9
+ import * as lit1 from "lit";
9
10
  import { LitElement, PropertyValueMap } from "lit";
10
11
 
11
12
  //#region src/elements/EFMedia.d.ts
@@ -58,7 +59,7 @@ declare class EFMedia extends EFMedia_base {
58
59
  */
59
60
  get requiredTracks(): "audio" | "video" | "both";
60
61
  static get observedAttributes(): string[];
61
- static styles: lit0.CSSResult[];
62
+ static styles: lit1.CSSResult[];
62
63
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
63
64
  /**
64
65
  * Duration in milliseconds for audio buffering ahead of current time
@@ -3,8 +3,9 @@ import { withSpan } from "../otel/tracingHelpers.js";
3
3
  import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
4
4
  import { EFTemporal } from "./EFTemporal.js";
5
5
  import { isContextMixin } from "../gui/ContextMixin.js";
6
- import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
7
6
  import { LRUCache } from "../utils/LRUCache.js";
7
+ import { createMediaEngineFromSource } from "./EFMedia/MediaEngine.js";
8
+ import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
8
9
  import { EFSourceMixin } from "./EFSourceMixin.js";
9
10
  import { FetchMixin } from "./FetchMixin.js";
10
11
  import { renderTemporalAudio } from "./renderTemporalAudio.js";
@@ -252,6 +253,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
252
253
  * Uses caching based on src/fileId to avoid redundant fetches.
253
254
  */
254
255
  async getMediaEngine(signal) {
256
+ if (!this.src && !this.fileId) return;
255
257
  const srcKey = `${this.src}|${this.fileId}`;
256
258
  if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {
257
259
  this.setContentReadyState("ready");
@@ -285,34 +287,15 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
285
287
  }
286
288
  async #createMediaEngine(signal) {
287
289
  const { src, fileId, apiHost, requiredTracks } = this;
288
- const urlGenerator = this.getUrlGenerator();
289
- if (fileId !== null && fileId !== void 0 && fileId.trim() !== "") {
290
- if (!apiHost) throw new Error("API host is required for file-id mode");
291
- const { FileMediaEngine } = await import("./EFMedia/FileMediaEngine.js");
292
- return await FileMediaEngine.fetchByFileId(this, urlGenerator, fileId, apiHost, requiredTracks, signal);
293
- }
294
- if (!src || typeof src !== "string" || src.trim() === "") return;
295
- const lowerSrc = src.toLowerCase();
296
- const isRemoteUrl = lowerSrc.startsWith("http://") || lowerSrc.startsWith("https://");
297
- const configuration = this.closest("ef-configuration");
298
- if (configuration?.mediaEngine === "jit") {
299
- let manifestSrc = src;
300
- if (!isRemoteUrl && configuration.apiHost) manifestSrc = `${configuration.apiHost.replace(/\/$/, "")}${src.replace(/^\.\//, "/src/")}`;
301
- const url$1 = urlGenerator.generateManifestUrl(manifestSrc);
302
- const { JitMediaEngine: JitMediaEngine$1 } = await import("./EFMedia/JitMediaEngine.js");
303
- return JitMediaEngine$1.fetch(this, urlGenerator, url$1, signal);
304
- }
305
- if (configuration?.mediaEngine === "local") {
306
- const { AssetMediaEngine } = await import("./EFMedia/AssetMediaEngine.js");
307
- return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);
308
- }
309
- if (!isRemoteUrl) {
310
- const { AssetMediaEngine } = await import("./EFMedia/AssetMediaEngine.js");
311
- return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);
312
- }
313
- const url = urlGenerator.generateManifestUrl(src);
314
- const { JitMediaEngine } = await import("./EFMedia/JitMediaEngine.js");
315
- return JitMediaEngine.fetch(this, urlGenerator, url, signal);
290
+ return createMediaEngineFromSource({
291
+ src,
292
+ fileId,
293
+ apiHost,
294
+ requiredTracks,
295
+ fetchFn: (url, init) => this.fetch(url, init),
296
+ urlGenerator: this.getUrlGenerator(),
297
+ signal
298
+ });
316
299
  }
317
300
  #handleMediaEngineComplete() {
318
301
  this.requestUpdate("intrinsicDurationMs");
@@ -367,7 +350,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
367
350
  async #analyzeFrequencies(currentTimeMs, signal) {
368
351
  const mediaEngine = await this.getMediaEngine(signal);
369
352
  signal?.throwIfAborted();
370
- if (!mediaEngine?.audioRendition) return null;
353
+ if (!mediaEngine?.tracks.audio) return null;
371
354
  const frameIntervalMs = 1e3 / 30;
372
355
  const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;
373
356
  const fromMs = Math.max(0, earliestFrameMs);
@@ -449,7 +432,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
449
432
  async #analyzeTimeDomain(currentTimeMs, signal) {
450
433
  const mediaEngine = await this.getMediaEngine(signal);
451
434
  signal?.throwIfAborted();
452
- if (!mediaEngine?.audioRendition) return null;
435
+ if (!mediaEngine?.tracks.audio) return null;
453
436
  const frameIntervalMs = 1e3 / 30;
454
437
  const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;
455
438
  const fromMs = Math.max(0, earliestFrameMs);
@@ -1 +1 @@
1
- {"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#loadMediaEngine","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","url","JitMediaEngine","#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 { 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 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\n // Check for file-id mode first\n if (fileId !== null && fileId !== undefined && fileId.trim() !== \"\") {\n if (!apiHost) {\n throw new Error(\"API host is required for file-id mode\");\n }\n const { FileMediaEngine } = await import(\"./EFMedia/FileMediaEngine.js\");\n const engine = await FileMediaEngine.fetchByFileId(\n this,\n urlGenerator,\n fileId,\n apiHost,\n requiredTracks,\n signal,\n );\n return engine;\n }\n\n // Check for null/undefined/empty/whitespace src\n if (!src || typeof src !== \"string\" || src.trim() === \"\") {\n return undefined;\n }\n\n const lowerSrc = src.toLowerCase();\n const isRemoteUrl =\n lowerSrc.startsWith(\"http://\") || lowerSrc.startsWith(\"https://\");\n\n // Check configuration for explicit engine preference\n const configuration = this.closest(\"ef-configuration\");\n\n // \"jit\" mode: Force JitMediaEngine for all sources (including local files)\n if (configuration?.mediaEngine === \"jit\") {\n let manifestSrc = src;\n if (!isRemoteUrl && configuration.apiHost) {\n const baseUrl = configuration.apiHost.replace(/\\/$/, \"\");\n const normalizedPath = src.replace(/^\\.\\//, \"/src/\");\n manifestSrc = `${baseUrl}${normalizedPath}`;\n }\n const url = urlGenerator.generateManifestUrl(manifestSrc);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n\n // \"local\" mode: Force AssetMediaEngine for all sources\n if (configuration?.mediaEngine === \"local\") {\n const { AssetMediaEngine } =\n await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(\n this,\n urlGenerator,\n src,\n requiredTracks,\n signal,\n );\n }\n\n // \"cloud\" mode (default): AssetMediaEngine for local paths, JitMediaEngine for remote URLs\n if (!isRemoteUrl) {\n const { AssetMediaEngine } =\n await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(\n this,\n urlGenerator,\n src,\n requiredTracks,\n signal,\n );\n }\n\n // Default: Use JitMediaEngine for remote URLs (transcoding service)\n const url = urlGenerator.generateManifestUrl(src);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\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?.audioRendition) {\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?.audioRendition) {\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 = (\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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"}
@@ -49,7 +49,7 @@ function EFSourceMixin(superClass, options) {
49
49
  async #doLoadMd5(src, signal) {
50
50
  let normalizedSrc = src.startsWith("/") ? src.slice(1) : src;
51
51
  normalizedSrc = normalizedSrc.replace(/^\/+/, "");
52
- const md5Path = `/api/v1/files/local/md5?src=${encodeURIComponent(normalizedSrc)}`;
52
+ const md5Path = `/api/v1/files/md5?src=${encodeURIComponent(normalizedSrc)}`;
53
53
  const response = await fetch(md5Path, { signal });
54
54
  if (!response.ok) return;
55
55
  return (await response.json()).md5 ?? void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"EFSourceMixin.js","names":["#md5Value","#md5LastSrc","#md5Promise","#doLoadMd5"],"sources":["../../src/elements/EFSourceMixin.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\nimport { property } from \"lit/decorators/property.js\";\n\nexport declare class EFSourceMixinInterface {\n apiHost?: string;\n productionSrc(): string;\n src: string;\n}\n\ninterface EFSourceMixinOptions {\n assetType: string;\n}\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function EFSourceMixin<T extends Constructor<LitElement>>(\n superClass: T,\n options: EFSourceMixinOptions,\n) {\n class EFSourceElement extends superClass {\n get apiHost() {\n const apiHost =\n (this.closest(\"ef-configuration\") as any)?.apiHost ??\n (this.closest(\"ef-workbench\") as any)?.apiHost ??\n (this.closest(\"ef-preview\") as any)?.apiHost;\n\n // Return undefined instead of defaulting to external URL\n // This allows components to use current origin when apiHost is not set\n return apiHost;\n }\n\n @property({ type: String, reflect: true })\n src = \"\";\n\n #md5Value: string | undefined = undefined;\n #md5Promise: Promise<string | undefined> | null = null;\n #md5LastSrc: string | null = null;\n\n productionSrc() {\n if (!this.#md5Value) {\n throw new Error(\n `MD5 sum not available for ${this}. Cannot generate production URL`,\n );\n }\n\n if (!this.apiHost) {\n throw new Error(\n `apiHost not available for ${this}. Cannot generate production URL`,\n );\n }\n\n return `${this.apiHost}/api/v1/${options.assetType}/${this.#md5Value}`;\n }\n\n /**\n * Load MD5 sum for the current source\n */\n async loadMd5Sum(signal?: AbortSignal): Promise<string | undefined> {\n if (this.#md5LastSrc === this.src && this.#md5Value) {\n return this.#md5Value;\n }\n\n if (this.#md5Promise && this.#md5LastSrc === this.src) {\n return this.#md5Promise;\n }\n\n this.#md5LastSrc = this.src;\n this.#md5Promise = this.#doLoadMd5(this.src, signal);\n\n try {\n this.#md5Value = await this.#md5Promise;\n return this.#md5Value;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.error(\"EFSourceMixin md5Sum error\", error);\n return undefined;\n } finally {\n this.#md5Promise = null;\n }\n }\n\n async #doLoadMd5(\n src: string,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = src.startsWith(\"/\") ? src.slice(1) : src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n const md5Path = `/api/v1/files/local/md5?src=${encodeURIComponent(normalizedSrc)}`;\n const response = await fetch(md5Path, { signal });\n if (!response.ok) {\n return undefined;\n }\n const data = await response.json();\n return data.md5 ?? undefined;\n }\n\n /** @internal Exposes md5 state for md5SumLoader proxy without private field access in handler */\n _getMd5Value(): string | undefined {\n return this.#md5Value;\n }\n\n /** @internal Exposes md5 promise state for md5SumLoader proxy */\n _getMd5Promise(): Promise<string | undefined> | null {\n return this.#md5Promise;\n }\n\n /**\n * Compatibility wrapper for code expecting md5SumLoader.value\n */\n md5SumLoader = new Proxy(\n {\n run: () => this.loadMd5Sum(),\n host: this,\n } as unknown as {\n run: () => Promise<string | undefined>;\n host: {\n _getMd5Value: () => string | undefined;\n _getMd5Promise: () => Promise<string | undefined> | null;\n };\n value: string | undefined;\n taskComplete: Promise<string | undefined>;\n },\n {\n get(target, prop) {\n if (prop === \"value\") {\n return target.host._getMd5Value();\n }\n if (prop === \"taskComplete\") {\n const p = target.host._getMd5Promise();\n return p || Promise.resolve(target.host._getMd5Value());\n }\n return (target as any)[prop];\n },\n },\n );\n }\n\n return EFSourceElement as Constructor<EFSourceMixinInterface> & T;\n}\n"],"mappings":";;;;AAaA,SAAgB,cACd,YACA,SACA;CACA,MAAM,wBAAwB,WAAW;;;cAajC;uBAiFS,IAAI,MACjB;IACE,WAAW,KAAK,YAAY;IAC5B,MAAM;IACP,EASD,EACE,IAAI,QAAQ,MAAM;AAChB,QAAI,SAAS,QACX,QAAO,OAAO,KAAK,cAAc;AAEnC,QAAI,SAAS,eAEX,QADU,OAAO,KAAK,gBAAgB,IAC1B,QAAQ,QAAQ,OAAO,KAAK,cAAc,CAAC;AAEzD,WAAQ,OAAe;MAE1B,CACF;;EAtHD,IAAI,UAAU;AAQZ,UANG,KAAK,QAAQ,mBAAmB,EAAU,WAC1C,KAAK,QAAQ,eAAe,EAAU,WACtC,KAAK,QAAQ,aAAa,EAAU;;EAUzC,YAAgC;EAChC,cAAkD;EAClD,cAA6B;EAE7B,gBAAgB;AACd,OAAI,CAAC,MAAKA,SACR,OAAM,IAAI,MACR,6BAA6B,KAAK,kCACnC;AAGH,OAAI,CAAC,KAAK,QACR,OAAM,IAAI,MACR,6BAA6B,KAAK,kCACnC;AAGH,UAAO,GAAG,KAAK,QAAQ,UAAU,QAAQ,UAAU,GAAG,MAAKA;;;;;EAM7D,MAAM,WAAW,QAAmD;AAClE,OAAI,MAAKC,eAAgB,KAAK,OAAO,MAAKD,SACxC,QAAO,MAAKA;AAGd,OAAI,MAAKE,cAAe,MAAKD,eAAgB,KAAK,IAChD,QAAO,MAAKC;AAGd,SAAKD,aAAc,KAAK;AACxB,SAAKC,aAAc,MAAKC,UAAW,KAAK,KAAK,OAAO;AAEpD,OAAI;AACF,UAAKH,WAAY,MAAM,MAAKE;AAC5B,WAAO,MAAKF;YACL,OAAO;AACd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,YAAQ,MAAM,8BAA8B,MAAM;AAClD;aACQ;AACR,UAAKE,aAAc;;;EAIvB,OAAMC,UACJ,KACA,QAC6B;GAE7B,IAAI,gBAAgB,IAAI,WAAW,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG;AACzD,mBAAgB,cAAc,QAAQ,QAAQ,GAAG;GAEjD,MAAM,UAAU,+BAA+B,mBAAmB,cAAc;GAChF,MAAM,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;AACjD,OAAI,CAAC,SAAS,GACZ;AAGF,WADa,MAAM,SAAS,MAAM,EACtB,OAAO;;;EAIrB,eAAmC;AACjC,UAAO,MAAKH;;;EAId,iBAAqD;AACnD,UAAO,MAAKE;;;aA5Eb,SAAS;EAAE,MAAM;EAAQ,SAAS;EAAM,CAAC;AA8G5C,QAAO"}
1
+ {"version":3,"file":"EFSourceMixin.js","names":["#md5Value","#md5LastSrc","#md5Promise","#doLoadMd5"],"sources":["../../src/elements/EFSourceMixin.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\nimport { property } from \"lit/decorators/property.js\";\n\nexport declare class EFSourceMixinInterface {\n apiHost?: string;\n productionSrc(): string;\n src: string;\n}\n\ninterface EFSourceMixinOptions {\n assetType: string;\n}\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function EFSourceMixin<T extends Constructor<LitElement>>(\n superClass: T,\n options: EFSourceMixinOptions,\n) {\n class EFSourceElement extends superClass {\n get apiHost() {\n const apiHost =\n (this.closest(\"ef-configuration\") as any)?.apiHost ??\n (this.closest(\"ef-workbench\") as any)?.apiHost ??\n (this.closest(\"ef-preview\") as any)?.apiHost;\n\n // Return undefined instead of defaulting to external URL\n // This allows components to use current origin when apiHost is not set\n return apiHost;\n }\n\n @property({ type: String, reflect: true })\n src = \"\";\n\n #md5Value: string | undefined = undefined;\n #md5Promise: Promise<string | undefined> | null = null;\n #md5LastSrc: string | null = null;\n\n productionSrc() {\n if (!this.#md5Value) {\n throw new Error(\n `MD5 sum not available for ${this}. Cannot generate production URL`,\n );\n }\n\n if (!this.apiHost) {\n throw new Error(\n `apiHost not available for ${this}. Cannot generate production URL`,\n );\n }\n\n return `${this.apiHost}/api/v1/${options.assetType}/${this.#md5Value}`;\n }\n\n /**\n * Load MD5 sum for the current source\n */\n async loadMd5Sum(signal?: AbortSignal): Promise<string | undefined> {\n if (this.#md5LastSrc === this.src && this.#md5Value) {\n return this.#md5Value;\n }\n\n if (this.#md5Promise && this.#md5LastSrc === this.src) {\n return this.#md5Promise;\n }\n\n this.#md5LastSrc = this.src;\n this.#md5Promise = this.#doLoadMd5(this.src, signal);\n\n try {\n this.#md5Value = await this.#md5Promise;\n return this.#md5Value;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.error(\"EFSourceMixin md5Sum error\", error);\n return undefined;\n } finally {\n this.#md5Promise = null;\n }\n }\n\n async #doLoadMd5(\n src: string,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = src.startsWith(\"/\") ? src.slice(1) : src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n const md5Path = `/api/v1/files/md5?src=${encodeURIComponent(normalizedSrc)}`;\n const response = await fetch(md5Path, { signal });\n if (!response.ok) {\n return undefined;\n }\n const data = await response.json();\n return data.md5 ?? undefined;\n }\n\n /** @internal Exposes md5 state for md5SumLoader proxy without private field access in handler */\n _getMd5Value(): string | undefined {\n return this.#md5Value;\n }\n\n /** @internal Exposes md5 promise state for md5SumLoader proxy */\n _getMd5Promise(): Promise<string | undefined> | null {\n return this.#md5Promise;\n }\n\n /**\n * Compatibility wrapper for code expecting md5SumLoader.value\n */\n md5SumLoader = new Proxy(\n {\n run: () => this.loadMd5Sum(),\n host: this,\n } as unknown as {\n run: () => Promise<string | undefined>;\n host: {\n _getMd5Value: () => string | undefined;\n _getMd5Promise: () => Promise<string | undefined> | null;\n };\n value: string | undefined;\n taskComplete: Promise<string | undefined>;\n },\n {\n get(target, prop) {\n if (prop === \"value\") {\n return target.host._getMd5Value();\n }\n if (prop === \"taskComplete\") {\n const p = target.host._getMd5Promise();\n return p || Promise.resolve(target.host._getMd5Value());\n }\n return (target as any)[prop];\n },\n },\n );\n }\n\n return EFSourceElement as Constructor<EFSourceMixinInterface> & T;\n}\n"],"mappings":";;;;AAaA,SAAgB,cACd,YACA,SACA;CACA,MAAM,wBAAwB,WAAW;;;cAajC;uBAgFS,IAAI,MACjB;IACE,WAAW,KAAK,YAAY;IAC5B,MAAM;IACP,EASD,EACE,IAAI,QAAQ,MAAM;AAChB,QAAI,SAAS,QACX,QAAO,OAAO,KAAK,cAAc;AAEnC,QAAI,SAAS,eAEX,QADU,OAAO,KAAK,gBAAgB,IAC1B,QAAQ,QAAQ,OAAO,KAAK,cAAc,CAAC;AAEzD,WAAQ,OAAe;MAE1B,CACF;;EArHD,IAAI,UAAU;AAQZ,UANG,KAAK,QAAQ,mBAAmB,EAAU,WAC1C,KAAK,QAAQ,eAAe,EAAU,WACtC,KAAK,QAAQ,aAAa,EAAU;;EAUzC,YAAgC;EAChC,cAAkD;EAClD,cAA6B;EAE7B,gBAAgB;AACd,OAAI,CAAC,MAAKA,SACR,OAAM,IAAI,MACR,6BAA6B,KAAK,kCACnC;AAGH,OAAI,CAAC,KAAK,QACR,OAAM,IAAI,MACR,6BAA6B,KAAK,kCACnC;AAGH,UAAO,GAAG,KAAK,QAAQ,UAAU,QAAQ,UAAU,GAAG,MAAKA;;;;;EAM7D,MAAM,WAAW,QAAmD;AAClE,OAAI,MAAKC,eAAgB,KAAK,OAAO,MAAKD,SACxC,QAAO,MAAKA;AAGd,OAAI,MAAKE,cAAe,MAAKD,eAAgB,KAAK,IAChD,QAAO,MAAKC;AAGd,SAAKD,aAAc,KAAK;AACxB,SAAKC,aAAc,MAAKC,UAAW,KAAK,KAAK,OAAO;AAEpD,OAAI;AACF,UAAKH,WAAY,MAAM,MAAKE;AAC5B,WAAO,MAAKF;YACL,OAAO;AACd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAER,YAAQ,MAAM,8BAA8B,MAAM;AAClD;aACQ;AACR,UAAKE,aAAc;;;EAIvB,OAAMC,UACJ,KACA,QAC6B;GAE7B,IAAI,gBAAgB,IAAI,WAAW,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG;AACzD,mBAAgB,cAAc,QAAQ,QAAQ,GAAG;GACjD,MAAM,UAAU,yBAAyB,mBAAmB,cAAc;GAC1E,MAAM,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;AACjD,OAAI,CAAC,SAAS,GACZ;AAGF,WADa,MAAM,SAAS,MAAM,EACtB,OAAO;;;EAIrB,eAAmC;AACjC,UAAO,MAAKH;;;EAId,iBAAqD;AACnD,UAAO,MAAKE;;;aA3Eb,SAAS;EAAE,MAAM;EAAQ,SAAS;EAAM,CAAC;AA6G5C,QAAO"}
@@ -385,7 +385,8 @@ const EFTemporal = (superClass) => {
385
385
  const trimChanged = changedProperties.has("_trimStartMs") || changedProperties.has("_trimEndMs");
386
386
  const becameReady = changedProperties.has("contentReadyState") && changedProperties.get("contentReadyState") !== "ready" && this.contentReadyState === "ready";
387
387
  if (sourceChanged || trimChanged || becameReady) {
388
- if (this.rootTimegroup) this.rootTimegroup.requestFrameRender();
388
+ const isRenderClone = this.rootTimegroup?.hasAttribute("data-no-playback-controller");
389
+ if (this.rootTimegroup && !isRenderClone) this.rootTimegroup.requestFrameRender();
389
390
  else if (this.playbackController) this.playbackController.runThrottledFrameTask();
390
391
  }
391
392
  }