@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.
Files changed (132) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-css.js +3 -3
  3. package/tsdown.config.ts +1 -1
  4. package/src/elements/ContextProxiesController.ts +0 -124
  5. package/src/elements/CrossUpdateController.ts +0 -22
  6. package/src/elements/EFAudio.browsertest.ts +0 -706
  7. package/src/elements/EFAudio.ts +0 -56
  8. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  9. package/src/elements/EFCaptions.ts +0 -823
  10. package/src/elements/EFImage.browsertest.ts +0 -120
  11. package/src/elements/EFImage.ts +0 -113
  12. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  13. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  14. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  15. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  16. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  17. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  18. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  19. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  20. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  21. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  22. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  23. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  24. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  25. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  26. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  27. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  28. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  29. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  30. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  31. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  32. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  33. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  34. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  35. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  36. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  37. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  38. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  39. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  40. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  41. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  42. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  43. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  44. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  45. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  46. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  47. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  49. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  53. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  54. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  55. package/src/elements/EFMedia.browsertest.ts +0 -872
  56. package/src/elements/EFMedia.ts +0 -341
  57. package/src/elements/EFSourceMixin.ts +0 -60
  58. package/src/elements/EFSurface.browsertest.ts +0 -151
  59. package/src/elements/EFSurface.ts +0 -142
  60. package/src/elements/EFTemporal.browsertest.ts +0 -215
  61. package/src/elements/EFTemporal.ts +0 -800
  62. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  63. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  64. package/src/elements/EFThumbnailStrip.ts +0 -906
  65. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  66. package/src/elements/EFTimegroup.ts +0 -882
  67. package/src/elements/EFVideo.browsertest.ts +0 -1482
  68. package/src/elements/EFVideo.ts +0 -564
  69. package/src/elements/EFWaveform.ts +0 -547
  70. package/src/elements/FetchContext.browsertest.ts +0 -401
  71. package/src/elements/FetchMixin.ts +0 -38
  72. package/src/elements/SampleBuffer.ts +0 -94
  73. package/src/elements/TargetController.browsertest.ts +0 -230
  74. package/src/elements/TargetController.ts +0 -224
  75. package/src/elements/TimegroupController.ts +0 -26
  76. package/src/elements/durationConverter.ts +0 -35
  77. package/src/elements/parseTimeToMs.ts +0 -9
  78. package/src/elements/printTaskStatus.ts +0 -16
  79. package/src/elements/renderTemporalAudio.ts +0 -108
  80. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  81. package/src/elements/updateAnimations.ts +0 -217
  82. package/src/elements/util.ts +0 -24
  83. package/src/gui/ContextMixin.browsertest.ts +0 -860
  84. package/src/gui/ContextMixin.ts +0 -562
  85. package/src/gui/Controllable.browsertest.ts +0 -258
  86. package/src/gui/Controllable.ts +0 -41
  87. package/src/gui/EFConfiguration.ts +0 -40
  88. package/src/gui/EFControls.browsertest.ts +0 -389
  89. package/src/gui/EFControls.ts +0 -195
  90. package/src/gui/EFDial.browsertest.ts +0 -84
  91. package/src/gui/EFDial.ts +0 -172
  92. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  93. package/src/gui/EFFilmstrip.ts +0 -1349
  94. package/src/gui/EFFitScale.ts +0 -152
  95. package/src/gui/EFFocusOverlay.ts +0 -79
  96. package/src/gui/EFPause.browsertest.ts +0 -202
  97. package/src/gui/EFPause.ts +0 -73
  98. package/src/gui/EFPlay.browsertest.ts +0 -202
  99. package/src/gui/EFPlay.ts +0 -73
  100. package/src/gui/EFPreview.ts +0 -74
  101. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  102. package/src/gui/EFResizableBox.ts +0 -898
  103. package/src/gui/EFScrubber.ts +0 -151
  104. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  105. package/src/gui/EFTimeDisplay.ts +0 -55
  106. package/src/gui/EFToggleLoop.ts +0 -35
  107. package/src/gui/EFTogglePlay.ts +0 -70
  108. package/src/gui/EFWorkbench.ts +0 -115
  109. package/src/gui/PlaybackController.ts +0 -527
  110. package/src/gui/TWMixin.css +0 -6
  111. package/src/gui/TWMixin.ts +0 -61
  112. package/src/gui/TargetOrContextMixin.ts +0 -185
  113. package/src/gui/currentTimeContext.ts +0 -5
  114. package/src/gui/durationContext.ts +0 -3
  115. package/src/gui/efContext.ts +0 -6
  116. package/src/gui/fetchContext.ts +0 -5
  117. package/src/gui/focusContext.ts +0 -7
  118. package/src/gui/focusedElementContext.ts +0 -5
  119. package/src/gui/playingContext.ts +0 -5
  120. package/src/otel/BridgeSpanExporter.ts +0 -150
  121. package/src/otel/setupBrowserTracing.ts +0 -73
  122. package/src/otel/tracingHelpers.ts +0 -251
  123. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  124. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  125. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  126. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  127. package/src/transcoding/types/index.ts +0 -312
  128. package/src/transcoding/utils/MediaUtils.ts +0 -63
  129. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  130. package/src/transcoding/utils/constants.ts +0 -36
  131. package/src/utils/LRUCache.test.ts +0 -274
  132. package/src/utils/LRUCache.ts +0 -696
@@ -1,129 +0,0 @@
1
- import type {
2
- AudioSpan,
3
- MediaEngine,
4
- SegmentTimeRange,
5
- } from "../../../transcoding/types";
6
- import type { EFMedia } from "../../EFMedia";
7
-
8
- /**
9
- * Fetch audio segment data using MediaEngine
10
- * Pure function with explicit dependencies
11
- */
12
- const fetchAudioSegmentData = async (
13
- segmentIds: number[],
14
- mediaEngine: MediaEngine,
15
- signal: AbortSignal,
16
- ): Promise<Map<number, ArrayBuffer>> => {
17
- const audioRendition = mediaEngine.audioRendition;
18
- if (!audioRendition) {
19
- throw new Error("Audio rendition not available");
20
- }
21
-
22
- const segmentData = new Map<number, ArrayBuffer>();
23
-
24
- // Fetch all segments - MediaEngine handles deduplication internally
25
- const fetchPromises = segmentIds.map(async (segmentId) => {
26
- const arrayBuffer = await mediaEngine.fetchMediaSegment(
27
- segmentId,
28
- audioRendition,
29
- signal,
30
- );
31
- return [segmentId, arrayBuffer] as [number, ArrayBuffer];
32
- });
33
-
34
- const fetchedSegments = await Promise.all(fetchPromises);
35
- signal.throwIfAborted();
36
-
37
- for (const [segmentId, arrayBuffer] of fetchedSegments) {
38
- segmentData.set(segmentId, arrayBuffer);
39
- }
40
-
41
- return segmentData;
42
- };
43
-
44
- /**
45
- * Create audio span blob from init segment and media segments
46
- * Pure function for blob creation
47
- */
48
- const createAudioSpanBlob = (
49
- initSegment: ArrayBuffer,
50
- mediaSegments: ArrayBuffer[],
51
- ): Blob => {
52
- const chunks = [initSegment, ...mediaSegments];
53
- return new Blob(chunks, { type: "audio/mp4" });
54
- };
55
-
56
- /**
57
- * Fetch audio spanning a time range
58
- * Main function that orchestrates segment calculation, fetching, and blob creation
59
- */
60
- export const fetchAudioSpanningTime = async (
61
- host: EFMedia,
62
- fromMs: number,
63
- toMs: number,
64
- signal: AbortSignal,
65
- ): Promise<AudioSpan | undefined> => {
66
- // Validate inputs
67
- if (fromMs >= toMs || fromMs < 0) {
68
- throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
69
- }
70
-
71
- // Get dependencies from host
72
- const mediaEngine = await host.mediaEngineTask.taskComplete;
73
- const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
74
-
75
- // Return undefined if no audio rendition available
76
- if (!mediaEngine?.audioRendition) {
77
- return undefined;
78
- }
79
-
80
- if (!initSegment) {
81
- return undefined;
82
- }
83
-
84
- // Calculate segments needed using the media engine's method
85
- const segmentRanges = mediaEngine.calculateAudioSegmentRange(
86
- fromMs,
87
- toMs,
88
- mediaEngine.audioRendition,
89
- host.intrinsicDurationMs || 10000,
90
- );
91
-
92
- if (segmentRanges.length === 0) {
93
- throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);
94
- }
95
-
96
- // Fetch segment data
97
- const segmentIds = segmentRanges.map((r: SegmentTimeRange) => r.segmentId);
98
- const segmentData = await fetchAudioSegmentData(
99
- segmentIds,
100
- mediaEngine,
101
- signal,
102
- );
103
-
104
- // Create ordered array of segments
105
- const orderedSegments = segmentIds.map((id: number) => {
106
- const segment = segmentData.get(id);
107
- if (!segment) {
108
- throw new Error(`Missing segment data for segment ID ${id}`);
109
- }
110
- return segment;
111
- });
112
-
113
- // Create blob
114
- const blob = createAudioSpanBlob(initSegment, orderedSegments);
115
-
116
- // Calculate actual time boundaries
117
- const actualStartMs = Math.min(
118
- ...segmentRanges.map((r: SegmentTimeRange) => r.startMs),
119
- );
120
- const actualEndMs = Math.max(
121
- ...segmentRanges.map((r: SegmentTimeRange) => r.endMs),
122
- );
123
-
124
- return {
125
- startMs: actualStartMs,
126
- endMs: actualEndMs,
127
- blob,
128
- };
129
- };
@@ -1,342 +0,0 @@
1
- import type {
2
- AudioRendition,
3
- VideoRendition,
4
- } from "../../../transcoding/types";
5
-
6
- /**
7
- * State interface for media buffering - orchestration only, no data storage
8
- */
9
- export interface MediaBufferState {
10
- currentSeekTimeMs: number;
11
- requestedSegments: Set<number>; // Segments we've requested for buffering
12
- activeRequests: Set<number>; // Segments currently being fetched
13
- requestQueue: number[]; // Segments queued to be requested
14
- }
15
-
16
- /**
17
- * Configuration interface for media buffering - generic for both audio and video
18
- */
19
- export interface MediaBufferConfig {
20
- bufferDurationMs: number;
21
- maxParallelFetches: number;
22
- enableBuffering: boolean;
23
- enableContinuousBuffering?: boolean;
24
- bufferThresholdMs?: number; // Timeline-aware buffering threshold (default: 30000ms)
25
- }
26
-
27
- /**
28
- * Dependencies interface for media buffering - integrates with BaseMediaEngine
29
- */
30
- export interface MediaBufferDependencies<
31
- T extends AudioRendition | VideoRendition,
32
- > {
33
- computeSegmentId: (
34
- timeMs: number,
35
- rendition: T,
36
- ) => Promise<number | undefined>;
37
- prefetchSegment: (segmentId: number, rendition: T) => Promise<void>; // Just trigger prefetch, don't return data
38
- isSegmentCached: (segmentId: number, rendition: T) => boolean; // Check BaseMediaEngine cache
39
- getRendition: () => Promise<T | undefined>;
40
- logError: (message: string, error: any) => void;
41
- }
42
-
43
- /**
44
- * Compute segment range for a time window
45
- * Pure function - determines which segments are needed for a time range
46
- */
47
- export const computeSegmentRange = <T extends AudioRendition | VideoRendition>(
48
- startTimeMs: number,
49
- endTimeMs: number,
50
- rendition: T,
51
- computeSegmentId: (timeMs: number, rendition: T) => number | undefined,
52
- ): number[] => {
53
- const segments: number[] = [];
54
- const segmentDurationMs = (rendition as any).segmentDurationMs || 1000;
55
-
56
- // Calculate segment indices that overlap with [startTimeMs, endTimeMs]
57
- const startSegmentIndex = Math.floor(startTimeMs / segmentDurationMs);
58
- const endSegmentIndex = Math.floor(endTimeMs / segmentDurationMs);
59
-
60
- for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
61
- const segmentId = computeSegmentId(i * segmentDurationMs, rendition);
62
- if (segmentId !== undefined) {
63
- segments.push(segmentId);
64
- }
65
- }
66
-
67
- return segments.filter((id, index, arr) => arr.indexOf(id) === index); // Remove duplicates
68
- };
69
-
70
- /**
71
- * Async version of computeSegmentRange for when computeSegmentId is async
72
- */
73
- export const computeSegmentRangeAsync = async <
74
- T extends AudioRendition | VideoRendition,
75
- >(
76
- startTimeMs: number,
77
- endTimeMs: number,
78
- durationMs: number,
79
- rendition: T,
80
- computeSegmentId: (
81
- timeMs: number,
82
- rendition: T,
83
- ) => Promise<number | undefined>,
84
- ): Promise<number[]> => {
85
- const segments: number[] = [];
86
- const segmentDurationMs = (rendition as any).segmentDurationMs || 1000;
87
-
88
- // Calculate segment indices that overlap with [startTimeMs, endTimeMs]
89
- const startSegmentIndex = Math.floor(startTimeMs / segmentDurationMs);
90
- const endSegmentIndex = Math.floor(
91
- Math.min(endTimeMs, durationMs) / segmentDurationMs,
92
- );
93
-
94
- for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
95
- const timeMs = i * segmentDurationMs;
96
- if (timeMs < durationMs) {
97
- const segmentId = await computeSegmentId(timeMs, rendition);
98
- if (segmentId !== undefined) {
99
- segments.push(segmentId);
100
- }
101
- }
102
- }
103
-
104
- return segments.filter((id, index, arr) => arr.indexOf(id) === index); // Remove duplicates
105
- };
106
-
107
- /**
108
- * Compute buffer queue based on desired segments and what we've already requested
109
- * Pure function - determines what new segments should be prefetched
110
- */
111
- export const computeBufferQueue = (
112
- desiredSegments: number[],
113
- requestedSegments: Set<number>,
114
- ): number[] => {
115
- return desiredSegments.filter(
116
- (segmentId) => !requestedSegments.has(segmentId),
117
- );
118
- };
119
-
120
- /**
121
- * Handle seek time change and recompute buffer queue
122
- * Pure function - computes new queue when seek time changes
123
- */
124
- export const handleSeekTimeChange = <T extends AudioRendition | VideoRendition>(
125
- newSeekTimeMs: number,
126
- bufferDurationMs: number,
127
- rendition: T,
128
- currentState: MediaBufferState,
129
- computeSegmentId: (timeMs: number, rendition: T) => number | undefined,
130
- ): { newQueue: number[]; overlappingRequests: number[] } => {
131
- const endTimeMs = newSeekTimeMs + bufferDurationMs;
132
- const desiredSegments = computeSegmentRange(
133
- newSeekTimeMs,
134
- endTimeMs,
135
- rendition,
136
- computeSegmentId,
137
- );
138
-
139
- // Find segments that are already being requested
140
- const overlappingRequests = desiredSegments.filter((segmentId) =>
141
- currentState.requestedSegments.has(segmentId),
142
- );
143
-
144
- const newQueue = computeBufferQueue(
145
- desiredSegments,
146
- currentState.requestedSegments,
147
- );
148
-
149
- return { newQueue, overlappingRequests };
150
- };
151
-
152
- /**
153
- * Check if a segment has been requested for buffering
154
- * Pure function for checking buffer orchestration state
155
- */
156
- export const isSegmentRequested = (
157
- segmentId: number,
158
- bufferState: MediaBufferState | undefined,
159
- ): boolean => {
160
- return bufferState?.requestedSegments.has(segmentId) ?? false;
161
- };
162
-
163
- /**
164
- * Get requested segments from a list of segment IDs
165
- * Pure function that returns which segments have been requested for buffering
166
- */
167
- export const getRequestedSegments = (
168
- segmentIds: number[],
169
- bufferState: MediaBufferState | undefined,
170
- ): Set<number> => {
171
- if (!bufferState) {
172
- return new Set();
173
- }
174
- return new Set(
175
- segmentIds.filter((id) => bufferState.requestedSegments.has(id)),
176
- );
177
- };
178
-
179
- /**
180
- * Get unrequested segments from a list of segment IDs
181
- * Pure function that returns which segments haven't been requested yet
182
- */
183
- export const getUnrequestedSegments = (
184
- segmentIds: number[],
185
- bufferState: MediaBufferState | undefined,
186
- ): number[] => {
187
- if (!bufferState) {
188
- return segmentIds;
189
- }
190
- return segmentIds.filter((id) => !bufferState.requestedSegments.has(id));
191
- };
192
-
193
- /**
194
- * Calculate distance from element to playhead position
195
- * Returns 0 if element is currently active, otherwise returns distance in milliseconds
196
- */
197
- export const calculatePlayheadDistance = (
198
- element: { startTimeMs: number; endTimeMs: number },
199
- playheadMs: number,
200
- ): number => {
201
- // Element hasn't started yet
202
- if (playheadMs < element.startTimeMs) {
203
- return element.startTimeMs - playheadMs;
204
- }
205
- // Element already finished
206
- if (playheadMs > element.endTimeMs) {
207
- return playheadMs - element.endTimeMs;
208
- }
209
- // Element is currently active
210
- return 0;
211
- };
212
-
213
- /**
214
- * Core media buffering orchestration logic - prefetch only, no data storage
215
- * Integrates with BaseMediaEngine's existing caching and request deduplication
216
- */
217
- export const manageMediaBuffer = async <
218
- T extends AudioRendition | VideoRendition,
219
- >(
220
- seekTimeMs: number,
221
- config: MediaBufferConfig,
222
- currentState: MediaBufferState,
223
- durationMs: number,
224
- signal: AbortSignal,
225
- deps: MediaBufferDependencies<T>,
226
- timelineContext?: {
227
- elementStartMs: number;
228
- elementEndMs: number;
229
- playheadMs: number;
230
- },
231
- ): Promise<MediaBufferState> => {
232
- if (!config.enableBuffering) {
233
- return currentState;
234
- }
235
-
236
- // Timeline-aware buffering: skip if element is too far from playhead
237
- if (timelineContext && config.bufferThresholdMs !== undefined) {
238
- const distance = calculatePlayheadDistance(
239
- {
240
- startTimeMs: timelineContext.elementStartMs,
241
- endTimeMs: timelineContext.elementEndMs,
242
- },
243
- timelineContext.playheadMs,
244
- );
245
-
246
- if (distance > config.bufferThresholdMs) {
247
- // Element is too far from playhead, skip buffering
248
- return currentState;
249
- }
250
- }
251
-
252
- const rendition = await deps.getRendition();
253
- if (!rendition) {
254
- // Cannot buffer without a rendition
255
- return currentState;
256
- }
257
- const endTimeMs = seekTimeMs + config.bufferDurationMs;
258
-
259
- const desiredSegments = await computeSegmentRangeAsync(
260
- seekTimeMs,
261
- endTimeMs,
262
- durationMs,
263
- rendition,
264
- deps.computeSegmentId,
265
- );
266
- // Filter out segments already cached by BaseMediaEngine
267
- const uncachedSegments = desiredSegments.filter(
268
- (segmentId) => !deps.isSegmentCached(segmentId, rendition),
269
- );
270
-
271
- const newQueue = computeBufferQueue(
272
- uncachedSegments,
273
- currentState.requestedSegments,
274
- );
275
-
276
- // Shared state for concurrency control - prevents race conditions
277
- const newRequestedSegments = new Set(currentState.requestedSegments);
278
- const newActiveRequests = new Set(currentState.activeRequests);
279
- const remainingQueue = [...newQueue];
280
-
281
- // Thread-safe function to start next segment when slot becomes available
282
- const startNextSegment = (): void => {
283
- // Check if we have capacity and segments to fetch
284
- if (
285
- newActiveRequests.size >= config.maxParallelFetches ||
286
- remainingQueue.length === 0 ||
287
- signal.aborted
288
- ) {
289
- return;
290
- }
291
-
292
- const nextSegmentId = remainingQueue.shift();
293
- if (nextSegmentId === undefined) return;
294
-
295
- // Skip if already requested or now cached
296
- if (
297
- newRequestedSegments.has(nextSegmentId) ||
298
- deps.isSegmentCached(nextSegmentId, rendition)
299
- ) {
300
- startNextSegment(); // Try next segment immediately
301
- return;
302
- }
303
-
304
- newRequestedSegments.add(nextSegmentId);
305
- newActiveRequests.add(nextSegmentId);
306
-
307
- // Start the prefetch request
308
- deps
309
- .prefetchSegment(nextSegmentId, rendition)
310
- .then(() => {
311
- if (signal.aborted) return;
312
- newActiveRequests.delete(nextSegmentId);
313
- // Start next segment if continuous buffering is enabled
314
- if (config.enableContinuousBuffering ?? true) {
315
- startNextSegment();
316
- }
317
- })
318
- .catch((error) => {
319
- if (signal.aborted) return;
320
- newActiveRequests.delete(nextSegmentId);
321
- deps.logError(`Failed to prefetch segment ${nextSegmentId}`, error);
322
- // Continue even after error if continuous buffering is enabled
323
- if (config.enableContinuousBuffering ?? true) {
324
- startNextSegment();
325
- }
326
- });
327
- };
328
-
329
- // Start initial batch of requests up to maxParallelFetches limit
330
- const initialBatchSize = Math.min(config.maxParallelFetches, newQueue.length);
331
- for (let i = 0; i < initialBatchSize; i++) {
332
- startNextSegment();
333
- }
334
-
335
- const result = {
336
- currentSeekTimeMs: seekTimeMs,
337
- requestedSegments: newRequestedSegments,
338
- activeRequests: newActiveRequests,
339
- requestQueue: remainingQueue, // What's left in the queue
340
- };
341
- return result;
342
- };
@@ -1,77 +0,0 @@
1
- import type { Input } from "mediabunny";
2
- import { LRUCache } from "../../../utils/LRUCache.js";
3
-
4
- /**
5
- * Global cache for MediaBunny Input instances
6
- * Shared across all MediaEngine instances to prevent duplicate decoding
7
- * of the same segment data
8
- */
9
- class GlobalInputCache {
10
- private cache = new LRUCache<string, Input>(50); // 50 Input instances max
11
-
12
- /**
13
- * Generate standardized cache key for Input objects
14
- * Format: "input:{src}:{segmentId}:{renditionId}"
15
- */
16
- private generateKey(
17
- src: string,
18
- segmentId: number,
19
- renditionId?: string,
20
- ): string {
21
- return `input:${src}:${segmentId}:${renditionId || "default"}`;
22
- }
23
-
24
- /**
25
- * Get cached Input object
26
- */
27
- get(src: string, segmentId: number, renditionId?: string): Input | undefined {
28
- const key = this.generateKey(src, segmentId, renditionId);
29
- return this.cache.get(key);
30
- }
31
-
32
- /**
33
- * Cache Input object
34
- */
35
- set(
36
- src: string,
37
- segmentId: number,
38
- input: Input,
39
- renditionId?: string,
40
- ): void {
41
- const key = this.generateKey(src, segmentId, renditionId);
42
- this.cache.set(key, input);
43
- }
44
-
45
- /**
46
- * Check if Input is cached
47
- */
48
- has(src: string, segmentId: number, renditionId?: string): boolean {
49
- const key = this.generateKey(src, segmentId, renditionId);
50
- return this.cache.has(key);
51
- }
52
-
53
- /**
54
- * Clear all cached Input objects
55
- */
56
- clear(): void {
57
- this.cache.clear();
58
- }
59
-
60
- /**
61
- * Get cache statistics for debugging
62
- */
63
- getStats() {
64
- return {
65
- size: this.cache.size,
66
- cachedKeys: Array.from((this.cache as any).cache.keys()),
67
- };
68
- }
69
- }
70
-
71
- // Single global instance shared across all MediaEngine instances
72
- export const globalInputCache = new GlobalInputCache();
73
-
74
- // Export for debugging (works in both browser and server)
75
- (
76
- globalThis as typeof globalThis & { debugInputCache: typeof globalInputCache }
77
- ).debugInputCache = globalInputCache;
@@ -1,44 +0,0 @@
1
- import type { Task } from "@lit/task";
2
- import type {
3
- AudioRendition,
4
- MediaEngine,
5
- VideoRendition,
6
- } from "../../../transcoding/types";
7
- import type { BufferedSeekingInput } from "../BufferedSeekingInput";
8
-
9
- /**
10
- * Generic rendition type that can be either audio or video
11
- */
12
- export type MediaRendition = AudioRendition | VideoRendition;
13
-
14
- /**
15
- * Generic task type for init segment fetch
16
- */
17
- export type InitSegmentFetchTask = Task<
18
- readonly [MediaEngine | undefined],
19
- ArrayBuffer
20
- >;
21
-
22
- /**
23
- * Generic task type for segment ID calculation
24
- */
25
- export type SegmentIdTask = Task<
26
- readonly [MediaEngine | undefined, number],
27
- number | undefined
28
- >;
29
-
30
- /**
31
- * Generic task type for segment fetch
32
- */
33
- export type SegmentFetchTask = Task<
34
- readonly [MediaEngine | undefined, number | undefined],
35
- ArrayBuffer
36
- >;
37
-
38
- /**
39
- * Generic task type for input creation
40
- */
41
- export type InputTask = Task<
42
- readonly [ArrayBuffer, ArrayBuffer],
43
- BufferedSeekingInput | undefined
44
- >;
@@ -1,46 +0,0 @@
1
- /**
2
- * Centralized precision utilities for consistent timing calculations across the media pipeline.
3
- *
4
- * The key insight is that floating-point precision errors can cause inconsistencies between:
5
- * 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)
6
- * 2. Sample finding logic (in SampleBuffer.find)
7
- * 3. Timeline mapping (in BufferedSeekingInput.seek)
8
- *
9
- * All timing calculations must use the same rounding strategy to ensure consistency.
10
- */
11
-
12
- /**
13
- * Round time to millisecond precision to handle floating-point precision issues.
14
- * Uses Math.round for consistent behavior across the entire pipeline.
15
- *
16
- * This function should be used for ALL time-related calculations that need to be
17
- * compared between different parts of the system.
18
- */
19
- export const roundToMilliseconds = (timeMs: number): number => {
20
- // Round to 3 decimal places (microsecond precision)
21
- return Math.round(timeMs * 1000) / 1000;
22
- };
23
-
24
- /**
25
- * Convert media time (in seconds) to scaled time units using consistent rounding.
26
- * This is used in segment selection to convert from milliseconds to timescale units.
27
- */
28
- export const convertToScaledTime = (
29
- timeMs: number,
30
- timescale: number,
31
- ): number => {
32
- const scaledTime = (timeMs / 1000) * timescale;
33
- return Math.round(scaledTime);
34
- };
35
-
36
- /**
37
- * Convert scaled time units back to media time (in milliseconds) using consistent rounding.
38
- * This is the inverse of convertToScaledTime.
39
- */
40
- export const convertFromScaledTime = (
41
- scaledTime: number,
42
- timescale: number,
43
- ): number => {
44
- const timeMs = (scaledTime / timescale) * 1000;
45
- return roundToMilliseconds(timeMs);
46
- };