@editframe/elements 0.26.2-beta.0 → 0.26.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 (135) hide show
  1. package/dist/elements/EFTimegroup.js +7 -2
  2. package/dist/elements/EFTimegroup.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/build-css.js +3 -3
  5. package/tsdown.config.ts +1 -1
  6. package/types.json +1 -1
  7. package/src/elements/ContextProxiesController.ts +0 -124
  8. package/src/elements/CrossUpdateController.ts +0 -22
  9. package/src/elements/EFAudio.browsertest.ts +0 -706
  10. package/src/elements/EFAudio.ts +0 -56
  11. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  12. package/src/elements/EFCaptions.ts +0 -823
  13. package/src/elements/EFImage.browsertest.ts +0 -120
  14. package/src/elements/EFImage.ts +0 -113
  15. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  16. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  17. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  18. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  19. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  20. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  21. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  22. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  23. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  24. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  25. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  26. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  27. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  28. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  29. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  30. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  31. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  32. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  33. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  34. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  35. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  36. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  37. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  38. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  39. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  40. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  41. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  42. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  43. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  44. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  45. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  46. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  47. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  48. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  49. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  53. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  54. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  55. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  56. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  57. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  58. package/src/elements/EFMedia.browsertest.ts +0 -872
  59. package/src/elements/EFMedia.ts +0 -341
  60. package/src/elements/EFSourceMixin.ts +0 -60
  61. package/src/elements/EFSurface.browsertest.ts +0 -151
  62. package/src/elements/EFSurface.ts +0 -142
  63. package/src/elements/EFTemporal.browsertest.ts +0 -215
  64. package/src/elements/EFTemporal.ts +0 -800
  65. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  66. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  67. package/src/elements/EFThumbnailStrip.ts +0 -906
  68. package/src/elements/EFTimegroup.browsertest.ts +0 -870
  69. package/src/elements/EFTimegroup.ts +0 -878
  70. package/src/elements/EFVideo.browsertest.ts +0 -1482
  71. package/src/elements/EFVideo.ts +0 -564
  72. package/src/elements/EFWaveform.ts +0 -547
  73. package/src/elements/FetchContext.browsertest.ts +0 -401
  74. package/src/elements/FetchMixin.ts +0 -38
  75. package/src/elements/SampleBuffer.ts +0 -94
  76. package/src/elements/TargetController.browsertest.ts +0 -230
  77. package/src/elements/TargetController.ts +0 -224
  78. package/src/elements/TimegroupController.ts +0 -26
  79. package/src/elements/durationConverter.ts +0 -35
  80. package/src/elements/parseTimeToMs.ts +0 -9
  81. package/src/elements/printTaskStatus.ts +0 -16
  82. package/src/elements/renderTemporalAudio.ts +0 -108
  83. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  84. package/src/elements/updateAnimations.ts +0 -217
  85. package/src/elements/util.ts +0 -24
  86. package/src/gui/ContextMixin.browsertest.ts +0 -860
  87. package/src/gui/ContextMixin.ts +0 -562
  88. package/src/gui/Controllable.browsertest.ts +0 -258
  89. package/src/gui/Controllable.ts +0 -41
  90. package/src/gui/EFConfiguration.ts +0 -40
  91. package/src/gui/EFControls.browsertest.ts +0 -389
  92. package/src/gui/EFControls.ts +0 -195
  93. package/src/gui/EFDial.browsertest.ts +0 -84
  94. package/src/gui/EFDial.ts +0 -172
  95. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  96. package/src/gui/EFFilmstrip.ts +0 -1349
  97. package/src/gui/EFFitScale.ts +0 -152
  98. package/src/gui/EFFocusOverlay.ts +0 -79
  99. package/src/gui/EFPause.browsertest.ts +0 -202
  100. package/src/gui/EFPause.ts +0 -73
  101. package/src/gui/EFPlay.browsertest.ts +0 -202
  102. package/src/gui/EFPlay.ts +0 -73
  103. package/src/gui/EFPreview.ts +0 -74
  104. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  105. package/src/gui/EFResizableBox.ts +0 -898
  106. package/src/gui/EFScrubber.ts +0 -151
  107. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  108. package/src/gui/EFTimeDisplay.ts +0 -55
  109. package/src/gui/EFToggleLoop.ts +0 -35
  110. package/src/gui/EFTogglePlay.ts +0 -70
  111. package/src/gui/EFWorkbench.ts +0 -115
  112. package/src/gui/PlaybackController.ts +0 -527
  113. package/src/gui/TWMixin.css +0 -6
  114. package/src/gui/TWMixin.ts +0 -61
  115. package/src/gui/TargetOrContextMixin.ts +0 -185
  116. package/src/gui/currentTimeContext.ts +0 -5
  117. package/src/gui/durationContext.ts +0 -3
  118. package/src/gui/efContext.ts +0 -6
  119. package/src/gui/fetchContext.ts +0 -5
  120. package/src/gui/focusContext.ts +0 -7
  121. package/src/gui/focusedElementContext.ts +0 -5
  122. package/src/gui/playingContext.ts +0 -5
  123. package/src/otel/BridgeSpanExporter.ts +0 -150
  124. package/src/otel/setupBrowserTracing.ts +0 -73
  125. package/src/otel/tracingHelpers.ts +0 -251
  126. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  127. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  128. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  129. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  130. package/src/transcoding/types/index.ts +0 -312
  131. package/src/transcoding/utils/MediaUtils.ts +0 -63
  132. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  133. package/src/transcoding/utils/constants.ts +0 -36
  134. package/src/utils/LRUCache.test.ts +0 -274
  135. package/src/utils/LRUCache.ts +0 -696
@@ -1,679 +0,0 @@
1
- import { TaskStatus } from "@lit/task";
2
- import { customElement } from "lit/decorators.js";
3
- import { afterEach, describe, vi } from "vitest";
4
- import { test as baseTest } from "../../../../test/useMSW.js";
5
- import type { AudioRendition } from "../../../transcoding/types";
6
- import { EFMedia } from "../../EFMedia";
7
- import {
8
- computeBufferQueue,
9
- computeSegmentRange,
10
- computeSegmentRangeAsync,
11
- getRequestedSegments,
12
- getUnrequestedSegments,
13
- handleSeekTimeChange,
14
- isSegmentRequested,
15
- type MediaBufferDependencies,
16
- type MediaBufferState,
17
- manageMediaBuffer,
18
- } from "../shared/BufferUtils";
19
- import {
20
- type AudioBufferConfig,
21
- type AudioBufferState,
22
- makeAudioBufferTask,
23
- } from "./makeAudioBufferTask";
24
-
25
- @customElement("test-media-audio-buffer")
26
- class TestMediaAudioBuffer extends EFMedia {}
27
-
28
- declare global {
29
- interface HTMLElementTagNameMap {
30
- "test-media-audio-buffer": TestMediaAudioBuffer;
31
- }
32
- }
33
-
34
- // Test that shows the task should use EFMedia properties directly
35
- describe("makeAudioBufferTask - EFMedia Property Integration", () => {
36
- test("should allow creating task without hardcoded config parameter", ({
37
- expect,
38
- }) => {
39
- const element = document.createElement("test-media-audio-buffer");
40
-
41
- // Set custom values on the element
42
- element.audioBufferDurationMs = 45000;
43
- element.maxAudioBufferFetches = 5;
44
- element.enableAudioBuffering = false;
45
-
46
- // This should work without passing a config parameter
47
- expect(() => {
48
- const task = makeAudioBufferTask(element);
49
- expect(task).toBeDefined();
50
- }).not.toThrow();
51
-
52
- element.remove();
53
- });
54
- });
55
-
56
- const test = baseTest.extend<{
57
- element: TestMediaAudioBuffer;
58
- mockAudioRendition: {
59
- segmentDurationMs: number;
60
- trackId: number;
61
- src: string;
62
- };
63
- mockConfig: AudioBufferConfig;
64
- mockState: AudioBufferState;
65
- mockDeps: MediaBufferDependencies<AudioRendition>;
66
- mockSignal: AbortSignal;
67
- }>({
68
- element: async ({}, use) => {
69
- const element = document.createElement("test-media-audio-buffer");
70
- await use(element);
71
- element.remove();
72
- },
73
-
74
- mockAudioRendition: async ({}, use) => {
75
- const rendition = {
76
- segmentDurationMs: 1000, // 1 second per segment
77
- trackId: 1,
78
- src: "test-audio.mp4",
79
- };
80
- await use(rendition);
81
- },
82
-
83
- mockConfig: async ({}, use) => {
84
- const config = {
85
- bufferDurationMs: 5000, // 5 seconds
86
- maxParallelFetches: 2,
87
- enableBuffering: true,
88
- enableContinuousBuffering: false, // Disable for predictable testing
89
- };
90
- await use(config);
91
- },
92
-
93
- mockState: async ({}, use) => {
94
- const state = {
95
- currentSeekTimeMs: 0,
96
- requestedSegments: new Set<number>(),
97
- activeRequests: new Set<number>(),
98
- requestQueue: [],
99
- };
100
- await use(state);
101
- },
102
-
103
- mockDeps: async ({ mockAudioRendition }, use) => {
104
- const deps = {
105
- computeSegmentId: vi.fn(
106
- async (timeMs: number, rendition: { segmentDurationMs?: number }) => {
107
- return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
108
- },
109
- ),
110
- prefetchSegment: vi.fn().mockResolvedValue(undefined), // Just trigger prefetch
111
- isSegmentCached: vi.fn().mockReturnValue(false), // Assume nothing cached for testing
112
- getRendition: vi.fn().mockResolvedValue(mockAudioRendition),
113
- logError: vi.fn(),
114
- };
115
- await use(deps);
116
- },
117
-
118
- mockSignal: async ({}, use) => {
119
- const mockSignal = new AbortController().signal;
120
- await use(mockSignal);
121
- },
122
- });
123
-
124
- describe("computeSegmentRange", () => {
125
- test("computes segment range correctly", ({ mockAudioRendition, expect }) => {
126
- const computeSegmentId = (
127
- timeMs: number,
128
- rendition: { segmentDurationMs: number },
129
- ) => Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
130
-
131
- const segments = computeSegmentRange(
132
- 2000, // start at 2 seconds
133
- 5000, // end at 5 seconds
134
- mockAudioRendition,
135
- computeSegmentId,
136
- );
137
-
138
- expect(segments).toEqual([2, 3, 4, 5]); // segments 2, 3, 4, 5
139
- });
140
-
141
- test("returns empty array when segment IDs are undefined", ({
142
- mockAudioRendition,
143
- expect,
144
- }) => {
145
- const computeSegmentId = () => undefined;
146
-
147
- const segments = computeSegmentRange(
148
- 2000,
149
- 5000,
150
- mockAudioRendition,
151
- computeSegmentId,
152
- );
153
-
154
- expect(segments).toEqual([]);
155
- });
156
-
157
- test("uses default segment duration when missing", ({ expect }) => {
158
- const rendition = {}; // Missing segmentDurationMs
159
- const computeSegmentId = () => 1;
160
-
161
- const segments = computeSegmentRange(
162
- 2000,
163
- 5000,
164
- rendition as any,
165
- computeSegmentId,
166
- );
167
-
168
- // Should use default 1000ms duration and call computeSegmentId for segments 2, 3, 4, 5
169
- // Since computeSegmentId always returns 1, and duplicates are filtered out, result is [1]
170
- expect(segments).toEqual([1]); // duplicates filtered out
171
- });
172
- });
173
-
174
- describe("computeSegmentRangeAsync", () => {
175
- test("computes segment range with video duration limit", async ({
176
- mockAudioRendition,
177
- expect,
178
- }) => {
179
- const computeSegmentId = async (timeMs: number) =>
180
- Math.floor(timeMs / 1000);
181
-
182
- const segments = await computeSegmentRangeAsync(
183
- 8000, // start at 8 seconds
184
- 12000, // want to end at 12 seconds
185
- 10000, // but video only lasts 10 seconds
186
- mockAudioRendition,
187
- computeSegmentId,
188
- );
189
-
190
- expect(segments).toEqual([8, 9]); // Limited to video duration, segment 10 would be at 10000ms which is not < 10000
191
- });
192
-
193
- test("handles large duration limit", async ({
194
- mockAudioRendition,
195
- expect,
196
- }) => {
197
- const computeSegmentId = async (timeMs: number) =>
198
- Math.floor(timeMs / 1000);
199
-
200
- const segments = await computeSegmentRangeAsync(
201
- 2000,
202
- 5000,
203
- 100000, // Large duration limit - effectively unlimited
204
- mockAudioRendition,
205
- computeSegmentId,
206
- );
207
-
208
- expect(segments).toEqual([2, 3, 4, 5]);
209
- });
210
- });
211
-
212
- describe("computeBufferQueue", () => {
213
- test("filters out already requested segments", ({ expect }) => {
214
- const desiredSegments = [1, 2, 3, 4, 5];
215
- const requestedSegments = new Set([2, 4, 1]);
216
-
217
- const queue = computeBufferQueue(desiredSegments, requestedSegments);
218
-
219
- expect(queue).toEqual([3, 5]); // Only segments not yet requested
220
- });
221
-
222
- test("returns empty queue when all segments already requested", ({
223
- expect,
224
- }) => {
225
- const desiredSegments = [1, 2, 3];
226
- const requestedSegments = new Set([1, 2, 3]);
227
-
228
- const queue = computeBufferQueue(desiredSegments, requestedSegments);
229
-
230
- expect(queue).toEqual([]);
231
- });
232
- });
233
-
234
- describe("handleSeekTimeChange", () => {
235
- test("computes new queue for seek time change", ({
236
- mockAudioRendition,
237
- expect,
238
- }) => {
239
- const computeSegmentId = (timeMs: number) => Math.floor(timeMs / 1000);
240
- const currentState = {
241
- currentSeekTimeMs: 0,
242
- requestedSegments: new Set([1]),
243
- activeRequests: new Set<number>(),
244
- requestQueue: [],
245
- };
246
-
247
- const result = handleSeekTimeChange(
248
- 3000, // seek to 3 seconds
249
- 2000, // buffer 2 seconds ahead
250
- mockAudioRendition,
251
- currentState,
252
- computeSegmentId,
253
- );
254
-
255
- expect(result.newQueue).toEqual([3, 4, 5]); // segments 3, 4, 5 needed, none in cache/active
256
- expect(result.overlappingRequests).toEqual([]); // no overlap with active requests
257
- });
258
-
259
- test("identifies overlapping requests", ({ mockAudioRendition, expect }) => {
260
- const computeSegmentId = (timeMs: number) => Math.floor(timeMs / 1000);
261
- const currentState = {
262
- currentSeekTimeMs: 0,
263
- requestedSegments: new Set([3, 4]),
264
- activeRequests: new Set<number>(),
265
- requestQueue: [],
266
- };
267
-
268
- const result = handleSeekTimeChange(
269
- 3000, // seek to 3 seconds
270
- 2000, // buffer 2 seconds ahead
271
- mockAudioRendition,
272
- currentState,
273
- computeSegmentId,
274
- );
275
-
276
- expect(result.overlappingRequests).toEqual([3, 4]); // both are already being fetched
277
- expect(result.newQueue).toEqual([5]); // only segment 5 needs fetching
278
- });
279
- });
280
-
281
- describe("manageMediaBuffer (Audio)", () => {
282
- test("manages buffer state successfully", async ({
283
- mockConfig,
284
- mockState,
285
- mockDeps,
286
- mockSignal,
287
- expect,
288
- }) => {
289
- const seekTimeMs = 3000;
290
-
291
- const newState = await manageMediaBuffer(
292
- seekTimeMs,
293
- mockConfig,
294
- mockState,
295
- 10000, // durationMs
296
- mockSignal,
297
- mockDeps,
298
- );
299
-
300
- expect(newState.currentSeekTimeMs).toBe(seekTimeMs);
301
- expect(mockDeps.getRendition).toHaveBeenCalled();
302
- expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(2); // maxParallelFetches = 2
303
- });
304
-
305
- test("respects maxParallelFetches limit", async ({
306
- mockState,
307
- mockDeps,
308
- mockSignal,
309
- expect,
310
- }) => {
311
- const config = {
312
- bufferDurationMs: 10000, // 10 seconds = 10 segments
313
- maxParallelFetches: 3,
314
- enableBuffering: true,
315
- enableContinuousBuffering: false, // Disable for predictable testing
316
- };
317
-
318
- await manageMediaBuffer(
319
- 0,
320
- config,
321
- mockState,
322
- 10000, // durationMs
323
- mockSignal,
324
- mockDeps,
325
- );
326
-
327
- expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(3); // Should only fetch 3 despite needing 10
328
- });
329
-
330
- test("does nothing when buffering disabled", async ({
331
- mockState,
332
- mockDeps,
333
- mockSignal,
334
- expect,
335
- }) => {
336
- const config = {
337
- bufferDurationMs: 5000,
338
- maxParallelFetches: 2,
339
- enableBuffering: false,
340
- };
341
-
342
- const newState = await manageMediaBuffer(
343
- 1000,
344
- config,
345
- mockState,
346
- 10000, // durationMs
347
- mockSignal,
348
- mockDeps,
349
- );
350
-
351
- expect(newState).toBe(mockState); // Should return same state
352
- expect(mockDeps.prefetchSegment).not.toHaveBeenCalled();
353
- });
354
-
355
- test("handles fetch errors gracefully", async ({
356
- mockConfig,
357
- mockState,
358
- mockSignal,
359
- expect,
360
- }) => {
361
- const mockDeps = {
362
- computeSegmentId: vi.fn().mockResolvedValue(1),
363
- prefetchSegment: vi.fn().mockRejectedValue(new Error("Network error")),
364
- isSegmentCached: vi.fn().mockReturnValue(false),
365
- getRendition: vi.fn().mockResolvedValue({ segmentDurationMs: 1000 }),
366
- logError: vi.fn(),
367
- };
368
-
369
- const newState = await manageMediaBuffer(
370
- 1000,
371
- mockConfig,
372
- mockState,
373
- 10000, // durationMs
374
- mockSignal,
375
- mockDeps,
376
- );
377
-
378
- // Wait for async error handling to complete
379
- await new Promise((resolve) => setTimeout(resolve, 10));
380
-
381
- expect(newState.currentSeekTimeMs).toBe(1000);
382
- expect(mockDeps.logError).toHaveBeenCalledWith(
383
- "Failed to prefetch segment 1",
384
- expect.any(Error),
385
- );
386
- });
387
- });
388
-
389
- describe("makeAudioBufferTask", () => {
390
- afterEach(() => {
391
- const elements = document.querySelectorAll("test-media-audio-buffer");
392
- for (const element of elements) {
393
- element.remove();
394
- }
395
- vi.restoreAllMocks();
396
- });
397
-
398
- test("creates task with correct configuration", ({ element, expect }) => {
399
- const task = makeAudioBufferTask(element);
400
-
401
- expect(task).toBeDefined();
402
- expect(task.status).toBe(TaskStatus.INITIAL);
403
- expect(task.value).toBeUndefined();
404
- expect(task.error).toBeUndefined();
405
- });
406
-
407
- test("task integrates with element seek time", ({ element, expect }) => {
408
- element.desiredSeekTimeMs = 5000;
409
-
410
- const task = makeAudioBufferTask(element);
411
- expect(task).toBeDefined();
412
- expect(task.status).toBe(TaskStatus.INITIAL);
413
- });
414
- });
415
-
416
- describe("Buffer Orchestration Methods", () => {
417
- test("isSegmentRequested returns true for requested segments", ({
418
- expect,
419
- }) => {
420
- const bufferState: MediaBufferState = {
421
- currentSeekTimeMs: 0,
422
- requestedSegments: new Set([1, 2, 3]),
423
- activeRequests: new Set(),
424
- requestQueue: [],
425
- };
426
-
427
- expect(isSegmentRequested(1, bufferState)).toBe(true);
428
- expect(isSegmentRequested(2, bufferState)).toBe(true);
429
- expect(isSegmentRequested(4, bufferState)).toBe(false);
430
- });
431
-
432
- test("isSegmentRequested returns false for undefined buffer state", ({
433
- expect,
434
- }) => {
435
- expect(isSegmentRequested(1, undefined)).toBe(false);
436
- });
437
-
438
- test("getRequestedSegments returns correct requested segment set", ({
439
- expect,
440
- }) => {
441
- const bufferState: MediaBufferState = {
442
- currentSeekTimeMs: 0,
443
- requestedSegments: new Set([2, 4, 6]),
444
- activeRequests: new Set(),
445
- requestQueue: [],
446
- };
447
-
448
- const segmentIds = [1, 2, 3, 4, 5, 6];
449
- const requestedSegments = getRequestedSegments(segmentIds, bufferState);
450
-
451
- expect(requestedSegments).toEqual(new Set([2, 4, 6]));
452
- });
453
-
454
- test("getRequestedSegments returns empty set for undefined buffer state", ({
455
- expect,
456
- }) => {
457
- const segmentIds = [1, 2, 3];
458
- const requestedSegments = getRequestedSegments(segmentIds, undefined);
459
-
460
- expect(requestedSegments).toEqual(new Set());
461
- });
462
-
463
- test("getUnrequestedSegments returns correct unrequested segments", ({
464
- expect,
465
- }) => {
466
- const bufferState: MediaBufferState = {
467
- currentSeekTimeMs: 0,
468
- requestedSegments: new Set([2, 4, 6]),
469
- activeRequests: new Set(),
470
- requestQueue: [],
471
- };
472
-
473
- const segmentIds = [1, 2, 3, 4, 5, 6];
474
- const unrequestedSegments = getUnrequestedSegments(segmentIds, bufferState);
475
-
476
- expect(unrequestedSegments).toEqual([1, 3, 5]);
477
- });
478
-
479
- test("getUnrequestedSegments returns all segments for undefined buffer state", ({
480
- expect,
481
- }) => {
482
- const segmentIds = [1, 2, 3];
483
- const unrequestedSegments = getUnrequestedSegments(segmentIds, undefined);
484
-
485
- expect(unrequestedSegments).toEqual([1, 2, 3]);
486
- });
487
- });
488
-
489
- describe("Buffering Integration Issues", () => {
490
- const test = baseTest.extend<{
491
- element: TestMediaAudioBuffer;
492
- }>({
493
- element: async ({}, use) => {
494
- const element = document.createElement("test-media-audio-buffer");
495
- document.body.appendChild(element);
496
- await use(element);
497
- element.remove();
498
- },
499
- });
500
-
501
- test("buffer task should run in interactive mode", async ({
502
- element,
503
- expect,
504
- }) => {
505
- // Set up real media element with actual test asset
506
- element.src = "bars-n-tone2.mp4";
507
- element.enableAudioBuffering = true;
508
- element.audioBufferDurationMs = 5000;
509
- element.maxAudioBufferFetches = 2;
510
- element.desiredSeekTimeMs = 1000;
511
-
512
- // Allow time for media engine initialization
513
- await new Promise((resolve) => setTimeout(resolve, 200));
514
-
515
- expect(element.audioBufferTask.status).not.toBe(TaskStatus.INITIAL);
516
- });
517
-
518
- test.skip("buffer task should be disabled in rendering mode", async ({
519
- expect,
520
- }) => {
521
- const originalEFRendering = window.EF_RENDERING;
522
-
523
- try {
524
- // Simulate rendering mode
525
- window.EF_RENDERING = () => true;
526
-
527
- // Create element in rendering mode
528
- const renderElement = document.createElement("test-media-audio-buffer");
529
- renderElement.src = "bars-n-tone2.mp4";
530
- renderElement.enableAudioBuffering = true;
531
- document.body.appendChild(renderElement);
532
-
533
- await new Promise((resolve) => setTimeout(resolve, 100));
534
-
535
- // Buffer task should NOT run in rendering mode
536
- expect(renderElement.audioBufferTask.status).toBe(TaskStatus.INITIAL);
537
-
538
- renderElement.remove();
539
- } finally {
540
- window.EF_RENDERING = originalEFRendering;
541
- }
542
- });
543
-
544
- test("segment fetch task does not check buffer cache", async ({
545
- element,
546
- expect,
547
- }) => {
548
- // Set up element with buffering enabled
549
- element.src = "bars-n-tone2.mp4";
550
- element.enableAudioBuffering = true;
551
- element.audioBufferDurationMs = 3000;
552
- element.desiredSeekTimeMs = 1000;
553
-
554
- // Track network requests to show segment fetch operates independently
555
- const originalFetch = window.fetch;
556
- const fetchUrls: string[] = [];
557
-
558
- window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
559
- const url = input.toString();
560
- fetchUrls.push(url);
561
- return originalFetch(input, init);
562
- };
563
-
564
- try {
565
- // Allow buffer and segment fetch to initialize
566
- await new Promise((resolve) => setTimeout(resolve, 300));
567
-
568
- // Move to different time to trigger segment fetch
569
- element.desiredSeekTimeMs = 2000;
570
- await new Promise((resolve) => setTimeout(resolve, 200));
571
-
572
- // This demonstrates the problem: segment fetch makes requests
573
- // independently of what buffer cache contains
574
- const audioRequests = fetchUrls.filter((url) => url.includes("audio"));
575
-
576
- // Currently passes - shows segment fetch operates without cache integration
577
- expect(audioRequests.length).toBeGreaterThanOrEqual(0);
578
- } finally {
579
- window.fetch = originalFetch;
580
- }
581
- });
582
-
583
- test("buffer cache and segment fetch operate independently", async ({
584
- element,
585
- expect,
586
- }) => {
587
- // Set up element that should have buffered segments
588
- element.src = "bars-n-tone2.mp4";
589
- element.enableAudioBuffering = true;
590
- element.audioBufferDurationMs = 5000;
591
- element.maxAudioBufferFetches = 3;
592
- element.desiredSeekTimeMs = 0;
593
-
594
- // Allow buffering to start
595
- await new Promise((resolve) => setTimeout(resolve, 400));
596
-
597
- // Check if buffer has cached segments
598
- const bufferState = element.audioBufferTask.value;
599
-
600
- if (
601
- bufferState?.requestedSegments &&
602
- bufferState.requestedSegments.size > 0
603
- ) {
604
- // Move seek position to trigger segment fetch of potentially cached segment
605
- element.desiredSeekTimeMs = 1000;
606
-
607
- // Currently, segment fetch task doesn't consult buffer cache
608
- // This is the integration gap we need to fix
609
- expect(bufferState.requestedSegments.size).toBeGreaterThan(0);
610
- }
611
- });
612
- });
613
-
614
- describe("Continuous Buffering", () => {
615
- test.skip("enables continuous segment loading when enabled", async ({
616
- mockState,
617
- mockDeps,
618
- mockSignal,
619
- expect,
620
- }) => {
621
- const configWithContinuous = {
622
- bufferDurationMs: 10000, // 10 seconds = 10 segments
623
- maxParallelFetches: 2,
624
- enableBuffering: true,
625
- enableContinuousBuffering: true, // Enable continuous buffering
626
- };
627
-
628
- let fetchCount = 0;
629
- const mockFetchWithDelay = vi.fn().mockImplementation(() => {
630
- fetchCount++;
631
- return Promise.resolve(new ArrayBuffer(1000));
632
- });
633
-
634
- const mockDepsWithContinuous = {
635
- ...mockDeps,
636
- fetchSegment: mockFetchWithDelay,
637
- };
638
-
639
- await manageMediaBuffer(
640
- 0,
641
- configWithContinuous,
642
- mockState,
643
- 10000, // durationMs
644
- mockSignal,
645
- mockDepsWithContinuous,
646
- );
647
-
648
- // Should start with initial maxParallelFetches (2) and continue with more requests
649
- // Continuous buffering should fetch more segments as previous ones complete
650
- expect(mockFetchWithDelay).toHaveBeenCalledTimes(4); // More than initial batch due to continuous buffering
651
- expect(fetchCount).toBe(4);
652
- });
653
-
654
- test("disabled when flag is false", async ({
655
- mockState,
656
- mockDeps,
657
- mockSignal,
658
- expect,
659
- }) => {
660
- const configWithoutContinuous = {
661
- bufferDurationMs: 10000, // 10 seconds = 10 segments
662
- maxParallelFetches: 2,
663
- enableBuffering: true,
664
- enableContinuousBuffering: false, // Disable continuous buffering
665
- };
666
-
667
- await manageMediaBuffer(
668
- 0,
669
- configWithoutContinuous,
670
- mockState,
671
- 10000, // durationMs
672
- mockSignal,
673
- mockDeps,
674
- );
675
-
676
- // Should only fetch initial maxParallelFetches and stop
677
- expect(mockDeps.prefetchSegment).toHaveBeenCalledTimes(2);
678
- });
679
- });