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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/EF_FRAMEGEN.js +3 -20
  2. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +4 -4
  3. package/dist/elements/EFMedia/AssetMediaEngine.js +8 -4
  4. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
  5. package/dist/elements/EFMedia/BaseMediaEngine.js +8 -2
  6. package/dist/elements/EFMedia/JitMediaEngine.js +13 -4
  7. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +1 -1
  8. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -2
  9. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  10. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +5 -4
  11. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
  12. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  13. package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
  14. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +5 -2
  15. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
  16. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  17. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
  18. package/dist/elements/EFMedia/shared/BufferUtils.js +3 -1
  19. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
  20. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
  21. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
  22. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -6
  23. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +2 -1
  24. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
  25. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -2
  26. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
  27. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -2
  28. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +4 -5
  29. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +2 -2
  30. package/dist/elements/EFMedia.d.ts +2 -1
  31. package/dist/elements/EFMedia.js +1 -0
  32. package/dist/elements/EFTimegroup.js +1 -1
  33. package/dist/transcoding/types/index.d.ts +6 -4
  34. package/package.json +2 -2
  35. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
  36. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
  37. package/src/elements/EFMedia/AssetMediaEngine.ts +16 -6
  38. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
  39. package/src/elements/EFMedia/BaseMediaEngine.ts +10 -8
  40. package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
  41. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
  42. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
  43. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
  44. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +11 -5
  45. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
  46. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
  47. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
  48. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
  49. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
  50. package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
  51. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
  52. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
  53. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
  54. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
  55. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
  56. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
  57. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +1 -5
  58. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
  59. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
  60. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +10 -19
  61. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
  62. package/src/elements/EFMedia.ts +2 -1
  63. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
  64. package/src/elements/EFTimegroup.ts +1 -1
  65. package/src/transcoding/types/index.ts +6 -4
  66. package/src/utils/LRUCache.test.ts +3 -1
  67. package/types.json +1 -1
@@ -4,12 +4,7 @@ import type {
4
4
  MediaEngine,
5
5
  VideoRendition,
6
6
  } from "../../../transcoding/types";
7
- import {
8
- calculateSegmentRange,
9
- computeSegmentId,
10
- getAudioRendition,
11
- getVideoRendition,
12
- } from "./RenditionHelpers";
7
+ import { calculateSegmentRange, computeSegmentId } from "./RenditionHelpers";
13
8
 
14
9
  const test = baseTest.extend<{
15
10
  mockMediaEngine: MediaEngine;
@@ -36,6 +31,8 @@ const test = baseTest.extend<{
36
31
  src: "https://example.com/media.mp4",
37
32
  videoRendition: mockVideoRendition,
38
33
  audioRendition: mockAudioRendition,
34
+ getVideoRendition: () => mockVideoRendition,
35
+ getAudioRendition: () => mockAudioRendition,
39
36
  fetchMediaSegment: vi.fn(),
40
37
  } as unknown as MediaEngine;
41
38
  await use(mockMediaEngine);
@@ -60,30 +57,38 @@ const test = baseTest.extend<{
60
57
  },
61
58
 
62
59
  mockMediaEngineWithoutAudio: async ({}, use) => {
60
+ const videoRendition = {
61
+ trackId: 1,
62
+ src: "video-track.mp4",
63
+ segmentDurationMs: 1000,
64
+ } as VideoRendition;
65
+
63
66
  const mockMediaEngine = {
64
67
  durationMs: 10000,
65
68
  src: "https://example.com/media.mp4",
66
- videoRendition: {
67
- trackId: 1,
68
- src: "video-track.mp4",
69
- segmentDurationMs: 1000,
70
- } as VideoRendition,
71
- audioRendition: null,
69
+ videoRendition,
70
+ audioRendition: undefined,
71
+ getVideoRendition: () => videoRendition,
72
+ getAudioRendition: () => undefined,
72
73
  fetchMediaSegment: vi.fn(),
73
74
  } as unknown as MediaEngine;
74
75
  await use(mockMediaEngine);
75
76
  },
76
77
 
77
78
  mockMediaEngineWithoutVideo: async ({}, use) => {
79
+ const audioRendition = {
80
+ trackId: 2,
81
+ src: "audio-track.mp4",
82
+ segmentDurationMs: 1000,
83
+ } as AudioRendition;
84
+
78
85
  const mockMediaEngine = {
79
86
  durationMs: 10000,
80
87
  src: "https://example.com/media.mp4",
81
- videoRendition: null,
82
- audioRendition: {
83
- trackId: 2,
84
- src: "audio-track.mp4",
85
- segmentDurationMs: 1000,
86
- } as AudioRendition,
88
+ videoRendition: undefined,
89
+ audioRendition,
90
+ getVideoRendition: () => undefined,
91
+ getAudioRendition: () => audioRendition,
87
92
  fetchMediaSegment: vi.fn(),
88
93
  } as unknown as MediaEngine;
89
94
  await use(mockMediaEngine);
@@ -91,45 +96,39 @@ const test = baseTest.extend<{
91
96
  });
92
97
 
93
98
  describe("RenditionHelpers", () => {
94
- describe("getAudioRendition", () => {
95
- test("returns audio rendition when available", ({
96
- mockMediaEngine,
99
+ describe("MediaEngine Rendition Access", () => {
100
+ test("mediaEngine.getAudioRendition() returns undefined for video-only assets", ({
101
+ mockMediaEngineWithoutAudio,
97
102
  expect,
98
103
  }) => {
99
- const result = getAudioRendition(mockMediaEngine);
100
- expect(result).toBe(mockMediaEngine.audioRendition);
101
- expect(result.trackId).toBe(2);
102
- expect(result.src).toBe("audio-track.mp4");
104
+ const result = mockMediaEngineWithoutAudio.getAudioRendition();
105
+ expect(result).toBeUndefined();
103
106
  });
104
107
 
105
- test("throws error when audio rendition is not available", ({
106
- mockMediaEngineWithoutAudio,
108
+ test("mediaEngine.getVideoRendition() returns undefined for audio-only assets", ({
109
+ mockMediaEngineWithoutVideo,
107
110
  expect,
108
111
  }) => {
109
- expect(() => getAudioRendition(mockMediaEngineWithoutAudio)).toThrow(
110
- "No audio track available in source",
111
- );
112
+ const result = mockMediaEngineWithoutVideo.getVideoRendition();
113
+ expect(result).toBeUndefined();
112
114
  });
113
- });
114
115
 
115
- describe("getVideoRendition", () => {
116
- test("returns video rendition when available", ({
116
+ test("mediaEngine.getAudioRendition() returns rendition when available", ({
117
117
  mockMediaEngine,
118
118
  expect,
119
119
  }) => {
120
- const result = getVideoRendition(mockMediaEngine);
121
- expect(result).toBe(mockMediaEngine.videoRendition);
122
- expect(result.trackId).toBe(1);
123
- expect(result.src).toBe("video-track.mp4");
120
+ const result = mockMediaEngine.getAudioRendition();
121
+ expect(result).toBeDefined();
122
+ expect(result?.trackId).toBe(2);
124
123
  });
125
124
 
126
- test("throws error when video rendition is not available", ({
127
- mockMediaEngineWithoutVideo,
125
+ test("mediaEngine.getVideoRendition() returns rendition when available", ({
126
+ mockMediaEngine,
128
127
  expect,
129
128
  }) => {
130
- expect(() => getVideoRendition(mockMediaEngineWithoutVideo)).toThrow(
131
- "No video track available in source",
132
- );
129
+ const result = mockMediaEngine.getVideoRendition();
130
+ expect(result).toBeDefined();
131
+ expect(result?.trackId).toBe(1);
133
132
  });
134
133
  });
135
134
 
@@ -1,31 +1,8 @@
1
1
  import type {
2
2
  AudioRendition,
3
- MediaEngine,
4
3
  VideoRendition,
5
4
  } from "../../../transcoding/types";
6
5
 
7
- /**
8
- * Get audio rendition from media engine, throwing if not available
9
- */
10
- export const getAudioRendition = (mediaEngine: MediaEngine): AudioRendition => {
11
- const audioRendition = mediaEngine.audioRendition;
12
- if (!audioRendition) {
13
- throw new Error("No audio track available in source");
14
- }
15
- return audioRendition;
16
- };
17
-
18
- /**
19
- * Get video rendition from media engine, throwing if not available
20
- */
21
- export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
- const videoRendition = mediaEngine.videoRendition;
23
- if (!videoRendition) {
24
- throw new Error("No video track available in source");
25
- }
26
- return videoRendition;
27
- };
28
-
29
6
  /**
30
7
  * Calculate which segment contains a given timestamp
31
8
  * Returns 1-based segment ID, or undefined if segmentDurationMs is not available
@@ -1,6 +1,6 @@
1
1
  import { Task } from "@lit/task";
2
2
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE";
3
- import type { MediaEngine, VideoRendition } from "../../../transcoding/types";
3
+ import type { MediaEngine } from "../../../transcoding/types";
4
4
  import type { EFMedia } from "../../EFMedia";
5
5
  import { AssetIdMediaEngine } from "../AssetIdMediaEngine";
6
6
  import { AssetMediaEngine } from "../AssetMediaEngine";
@@ -18,14 +18,6 @@ export const getLatestMediaEngine = async (
18
18
  return mediaEngine;
19
19
  };
20
20
 
21
- export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
- const videoRendition = mediaEngine.videoRendition;
23
- if (!videoRendition) {
24
- throw new Error("No video track available in source");
25
- }
26
- return videoRendition;
27
- };
28
-
29
21
  /**
30
22
  * Core logic for creating a MediaEngine with explicit dependencies.
31
23
  * Pure function that requires all dependencies to be provided.
@@ -1,4 +1,6 @@
1
1
  import { Task } from "@lit/task";
2
+
3
+ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
4
  import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
5
  import type { VideoRendition } from "../../../transcoding/types";
4
6
  import type { EFVideo } from "../../EFVideo";
@@ -19,8 +21,7 @@ export const makeScrubVideoBufferTask = (host: EFVideo) => {
19
21
  };
20
22
 
21
23
  return new Task(host, {
22
- // Make lazy - only run when element becomes timeline-active
23
- autoRun: false,
24
+ autoRun: EF_INTERACTIVE,
24
25
  args: () => [host.mediaEngineTask.value] as const,
25
26
  onError: (error) => {
26
27
  console.error("scrubVideoBufferTask error", error);
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -14,10 +13,6 @@ export const makeScrubVideoInitSegmentFetchTask = (
14
13
  },
15
14
  onComplete: (_value) => {},
16
15
  task: async ([_mediaEngine], { signal }) => {
17
- if (EF_RENDERING()) {
18
- return new ArrayBuffer(0);
19
- }
20
-
21
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
22
17
 
23
18
  // Get scrub rendition using the proper interface method
@@ -1,5 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
2
+
3
3
  import { EFMedia } from "../../EFMedia";
4
4
  import type { EFVideo } from "../../EFVideo";
5
5
  import { BufferedSeekingInput } from "../BufferedSeekingInput";
@@ -20,10 +20,6 @@ export const makeScrubVideoInputTask = (host: EFVideo): InputTask => {
20
20
  },
21
21
  onComplete: (_value) => {},
22
22
  task: async () => {
23
- if (EF_RENDERING()) {
24
- console.info("Scrub not available in rendering mode");
25
- }
26
-
27
23
  const initSegment =
28
24
  await host.scrubVideoInitSegmentFetchTask.taskComplete;
29
25
  const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -18,10 +17,6 @@ export const makeScrubVideoSegmentFetchTask = (
18
17
  },
19
18
  onComplete: (_value) => {},
20
19
  task: async (_, { signal }) => {
21
- if (EF_RENDERING()) {
22
- return new ArrayBuffer(0);
23
- }
24
-
25
20
  const mediaEngine = await getLatestMediaEngine(host, signal);
26
21
  const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
27
22
  if (segmentId === undefined) {
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -14,10 +13,6 @@ export const makeScrubVideoSegmentIdTask = (
14
13
  },
15
14
  onComplete: (_value) => {},
16
15
  task: async ([, targetSeekTimeMs], { signal }) => {
17
- if (EF_RENDERING()) {
18
- return undefined;
19
- }
20
-
21
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
22
17
  signal.throwIfAborted(); // Abort if a new seek started
23
18
 
@@ -1,6 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
2
  import type { VideoSample } from "mediabunny";
3
- import { EF_RENDERING } from "../../../EF_RENDERING";
4
3
  import type { VideoRendition } from "../../../transcoding/types";
5
4
  import type { EFVideo } from "../../EFVideo";
6
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -25,16 +24,6 @@ export const makeUnifiedVideoSeekTask = (
25
24
  const mediaEngine = await getLatestMediaEngine(host, signal);
26
25
  if (!mediaEngine) return undefined;
27
26
 
28
- // In rendering mode, skip all the scrub logic and go straight to main
29
- if (EF_RENDERING()) {
30
- return await getMainVideoSample(
31
- host,
32
- mediaEngine,
33
- desiredSeekTimeMs,
34
- signal,
35
- );
36
- }
37
-
38
27
  // FIRST: Check if main quality content is already cached
39
28
  const mainRendition = mediaEngine.videoRendition;
40
29
  if (mainRendition) {
@@ -186,20 +175,23 @@ async function getMainVideoSample(
186
175
  ): Promise<VideoSample | undefined> {
187
176
  try {
188
177
  // Use existing main video task chain
178
+ const videoRendition = mediaEngine.getVideoRendition();
179
+ if (!videoRendition) {
180
+ throw new Error(
181
+ "Video rendition unavailable after checking videoRendition exists",
182
+ );
183
+ }
184
+
189
185
  const segmentId = mediaEngine.computeSegmentId(
190
186
  desiredSeekTimeMs,
191
- mediaEngine.getVideoRendition(),
187
+ videoRendition,
192
188
  );
193
189
  if (segmentId === undefined) return undefined;
194
190
 
195
191
  // Fetch main video segment
196
192
  const [initSegment, mediaSegment] = await Promise.all([
197
- mediaEngine.fetchInitSegment(mediaEngine.getVideoRendition(), signal),
198
- mediaEngine.fetchMediaSegment(
199
- segmentId,
200
- mediaEngine.getVideoRendition(),
201
- signal,
202
- ),
193
+ mediaEngine.fetchInitSegment(videoRendition, signal),
194
+ mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal),
203
195
  ]);
204
196
 
205
197
  if (!initSegment || !mediaSegment) return undefined;
@@ -209,7 +201,6 @@ async function getMainVideoSample(
209
201
  const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
210
202
  const { EFMedia } = await import("../../EFMedia.js");
211
203
 
212
- const videoRendition = mediaEngine.videoRendition;
213
204
  const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
214
205
 
215
206
  const mainInput = new BufferedSeekingInput(
@@ -9,10 +9,7 @@ import {
9
9
  type MediaBufferState,
10
10
  manageMediaBuffer,
11
11
  } from "../shared/BufferUtils";
12
- import {
13
- getLatestMediaEngine,
14
- getVideoRendition,
15
- } from "../tasks/makeMediaEngineTask";
12
+ import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
16
13
 
17
14
  /**
18
15
  * Configuration for video buffering - extends the generic interface
@@ -90,7 +87,7 @@ export const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {
90
87
  getRendition: async () => {
91
88
  // Get real video rendition from media engine
92
89
  const mediaEngine = await getLatestMediaEngine(host, signal);
93
- return getVideoRendition(mediaEngine);
90
+ return mediaEngine.getVideoRendition();
94
91
  },
95
92
  logError: console.error,
96
93
  },
@@ -283,12 +283,13 @@ export class EFMedia extends EFTargetable(
283
283
  /**
284
284
  * Main integration method for EFTimegroup audio playback
285
285
  * Now powered by clean, testable utility functions
286
+ * Returns undefined if no audio rendition is available
286
287
  */
287
288
  async fetchAudioSpanningTime(
288
289
  fromMs: number,
289
290
  toMs: number,
290
291
  signal: AbortSignal = new AbortController().signal,
291
- ): Promise<AudioSpan> {
292
+ ): Promise<AudioSpan | undefined> {
292
293
  return fetchAudioSpanningTime(this, fromMs, toMs, signal);
293
294
  }
294
295
  }
@@ -132,7 +132,8 @@ describe("MediaEngine Thumbnail Extraction", () => {
132
132
  // Get segment duration to ensure timestamps are in same segment
133
133
  const videoRendition =
134
134
  mediaEngine.getScrubVideoRendition() || mediaEngine.getVideoRendition();
135
- const segmentDurationMs = videoRendition.segmentDurationMs || 2000;
135
+ expect(videoRendition).toBeDefined();
136
+ const segmentDurationMs = videoRendition!.segmentDurationMs || 2000;
136
137
 
137
138
  // Pick timestamps within the first segment - avoid edge cases near boundaries
138
139
  const timestamps = [
@@ -602,7 +602,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
602
602
  }
603
603
 
604
604
  if (!audio) {
605
- throw new Error("Failed to fetch audio");
605
+ return;
606
606
  }
607
607
 
608
608
  const bufferSource = audioContext.createBufferSource();
@@ -228,14 +228,16 @@ export interface MediaEngine {
228
228
  ) => number | undefined;
229
229
 
230
230
  /**
231
- * Get the video rendition, or throws if no video rendition is available
231
+ * Get the video rendition if available, otherwise return undefined.
232
+ * Callers should handle undefined appropriately.
232
233
  */
233
- getVideoRendition: () => VideoRendition;
234
+ getVideoRendition: () => VideoRendition | undefined;
234
235
 
235
236
  /**
236
- * Get the audio rendition, or throws if no audio rendition is available
237
+ * Get the audio rendition if available, otherwise return undefined.
238
+ * Callers should handle undefined appropriately.
237
239
  */
238
- getAudioRendition: () => AudioRendition;
240
+ getAudioRendition: () => AudioRendition | undefined;
239
241
 
240
242
  /**
241
243
  * Check if a segment is cached for a given rendition
@@ -244,7 +244,9 @@ describe("OrderedLRUCache", () => {
244
244
  expect(allItems.map((item) => item.key)).toEqual([100, 200, 300]); // 200±500 = [-300,700] contains all
245
245
  });
246
246
 
247
- test("performance characteristics with large dataset", () => {
247
+ // This test is cute, but we can't perfectly controll wall time
248
+ // in CI, so it's flaky.
249
+ test.skip("performance characteristics with large dataset", () => {
248
250
  const cache = new OrderedLRUCache<number, string>(1000);
249
251
 
250
252
  // Insert many items