@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,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
- };