@editframe/elements 0.26.3-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.
- package/package.json +2 -2
- package/scripts/build-css.js +3 -3
- package/tsdown.config.ts +1 -1
- package/src/elements/ContextProxiesController.ts +0 -124
- package/src/elements/CrossUpdateController.ts +0 -22
- package/src/elements/EFAudio.browsertest.ts +0 -706
- package/src/elements/EFAudio.ts +0 -56
- package/src/elements/EFCaptions.browsertest.ts +0 -1960
- package/src/elements/EFCaptions.ts +0 -823
- package/src/elements/EFImage.browsertest.ts +0 -120
- package/src/elements/EFImage.ts +0 -113
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
- package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
- package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
- package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
- package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
- package/src/elements/EFMedia.browsertest.ts +0 -872
- package/src/elements/EFMedia.ts +0 -341
- package/src/elements/EFSourceMixin.ts +0 -60
- package/src/elements/EFSurface.browsertest.ts +0 -151
- package/src/elements/EFSurface.ts +0 -142
- package/src/elements/EFTemporal.browsertest.ts +0 -215
- package/src/elements/EFTemporal.ts +0 -800
- package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
- package/src/elements/EFThumbnailStrip.ts +0 -906
- package/src/elements/EFTimegroup.browsertest.ts +0 -934
- package/src/elements/EFTimegroup.ts +0 -882
- package/src/elements/EFVideo.browsertest.ts +0 -1482
- package/src/elements/EFVideo.ts +0 -564
- package/src/elements/EFWaveform.ts +0 -547
- package/src/elements/FetchContext.browsertest.ts +0 -401
- package/src/elements/FetchMixin.ts +0 -38
- package/src/elements/SampleBuffer.ts +0 -94
- package/src/elements/TargetController.browsertest.ts +0 -230
- package/src/elements/TargetController.ts +0 -224
- package/src/elements/TimegroupController.ts +0 -26
- package/src/elements/durationConverter.ts +0 -35
- package/src/elements/parseTimeToMs.ts +0 -9
- package/src/elements/printTaskStatus.ts +0 -16
- package/src/elements/renderTemporalAudio.ts +0 -108
- package/src/elements/updateAnimations.browsertest.ts +0 -1884
- package/src/elements/updateAnimations.ts +0 -217
- package/src/elements/util.ts +0 -24
- package/src/gui/ContextMixin.browsertest.ts +0 -860
- package/src/gui/ContextMixin.ts +0 -562
- package/src/gui/Controllable.browsertest.ts +0 -258
- package/src/gui/Controllable.ts +0 -41
- package/src/gui/EFConfiguration.ts +0 -40
- package/src/gui/EFControls.browsertest.ts +0 -389
- package/src/gui/EFControls.ts +0 -195
- package/src/gui/EFDial.browsertest.ts +0 -84
- package/src/gui/EFDial.ts +0 -172
- package/src/gui/EFFilmstrip.browsertest.ts +0 -712
- package/src/gui/EFFilmstrip.ts +0 -1349
- package/src/gui/EFFitScale.ts +0 -152
- package/src/gui/EFFocusOverlay.ts +0 -79
- package/src/gui/EFPause.browsertest.ts +0 -202
- package/src/gui/EFPause.ts +0 -73
- package/src/gui/EFPlay.browsertest.ts +0 -202
- package/src/gui/EFPlay.ts +0 -73
- package/src/gui/EFPreview.ts +0 -74
- package/src/gui/EFResizableBox.browsertest.ts +0 -79
- package/src/gui/EFResizableBox.ts +0 -898
- package/src/gui/EFScrubber.ts +0 -151
- package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
- package/src/gui/EFTimeDisplay.ts +0 -55
- package/src/gui/EFToggleLoop.ts +0 -35
- package/src/gui/EFTogglePlay.ts +0 -70
- package/src/gui/EFWorkbench.ts +0 -115
- package/src/gui/PlaybackController.ts +0 -527
- package/src/gui/TWMixin.css +0 -6
- package/src/gui/TWMixin.ts +0 -61
- package/src/gui/TargetOrContextMixin.ts +0 -185
- package/src/gui/currentTimeContext.ts +0 -5
- package/src/gui/durationContext.ts +0 -3
- package/src/gui/efContext.ts +0 -6
- package/src/gui/fetchContext.ts +0 -5
- package/src/gui/focusContext.ts +0 -7
- package/src/gui/focusedElementContext.ts +0 -5
- package/src/gui/playingContext.ts +0 -5
- package/src/otel/BridgeSpanExporter.ts +0 -150
- package/src/otel/setupBrowserTracing.ts +0 -73
- package/src/otel/tracingHelpers.ts +0 -251
- package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
- package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
- package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
- package/src/transcoding/types/index.ts +0 -312
- package/src/transcoding/utils/MediaUtils.ts +0 -63
- package/src/transcoding/utils/UrlGenerator.ts +0 -68
- package/src/transcoding/utils/constants.ts +0 -36
- package/src/utils/LRUCache.test.ts +0 -274
- 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
|
-
});
|