@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,505 +0,0 @@
1
- import { withSpan } from "../../otel/tracingHelpers.js";
2
- import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
3
- import type {
4
- AudioRendition,
5
- SegmentTimeRange,
6
- ThumbnailResult,
7
- VideoRendition,
8
- } from "../../transcoding/types";
9
- import { SizeAwareLRUCache } from "../../utils/LRUCache.js";
10
- import type { EFMedia } from "../EFMedia.js";
11
- import type { MediaRendition } from "./shared/MediaTaskUtils.js";
12
-
13
- // Global instances shared across all media engines
14
- export const mediaCache = new SizeAwareLRUCache<string>(100 * 1024 * 1024); // 100MB cache limit
15
- export const globalRequestDeduplicator = new RequestDeduplicator();
16
-
17
- export abstract class BaseMediaEngine {
18
- protected host: EFMedia;
19
-
20
- constructor(host: EFMedia) {
21
- this.host = host;
22
- }
23
-
24
- abstract get videoRendition(): VideoRendition | undefined;
25
- abstract get audioRendition(): AudioRendition | undefined;
26
-
27
- /**
28
- * Get video rendition if available. Returns undefined for audio-only assets.
29
- * Callers should handle undefined gracefully.
30
- */
31
- getVideoRendition(): VideoRendition | undefined {
32
- return this.videoRendition;
33
- }
34
-
35
- /**
36
- * Get audio rendition if available. Returns undefined for video-only assets.
37
- * Callers should handle undefined gracefully.
38
- */
39
- getAudioRendition(): AudioRendition | undefined {
40
- return this.audioRendition;
41
- }
42
-
43
- /**
44
- * Generate cache key for segment requests
45
- */
46
- private getSegmentCacheKey(
47
- segmentId: number,
48
- rendition: { src: string; trackId: number | undefined; id?: string },
49
- ): string {
50
- return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;
51
- }
52
-
53
- /**
54
- * Unified fetch method with caching and global deduplication
55
- * All requests (media, manifest, init segments) go through this method
56
- */
57
- protected async fetchWithCache(
58
- url: string,
59
- options: {
60
- responseType: "arrayBuffer" | "json";
61
- headers?: Record<string, string>;
62
- signal?: AbortSignal;
63
- },
64
- ): Promise<any> {
65
- return withSpan(
66
- "mediaEngine.fetchWithCache",
67
- {
68
- url: url.length > 100 ? `${url.substring(0, 100)}...` : url,
69
- responseType: options.responseType,
70
- hasHeaders: !!options.headers,
71
- },
72
- undefined,
73
- async (span) => {
74
- const t0 = performance.now();
75
- const { responseType, headers, signal } = options;
76
-
77
- // Create cache key that includes URL and headers for proper isolation
78
- // Note: We don't include signal in cache key as it would prevent proper deduplication
79
- const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
80
-
81
- // Check cache first
82
- const t1 = performance.now();
83
- const cached = mediaCache.get(cacheKey);
84
- const t2 = performance.now();
85
- span.setAttribute("cacheLookupMs", Math.round((t2 - t1) * 1000) / 1000);
86
-
87
- if (cached) {
88
- span.setAttribute("cacheHit", true);
89
- // If we have a cached promise, we need to handle the caller's abort signal
90
- // without affecting the underlying request that other instances might be using
91
- if (signal) {
92
- const t3 = performance.now();
93
- const result = await this.handleAbortForCachedRequest(
94
- cached,
95
- signal,
96
- );
97
- const t4 = performance.now();
98
- span.setAttribute(
99
- "handleAbortMs",
100
- Math.round((t4 - t3) * 100) / 100,
101
- );
102
- span.setAttribute(
103
- "totalCacheHitMs",
104
- Math.round((t4 - t0) * 100) / 100,
105
- );
106
- return result;
107
- }
108
- span.setAttribute(
109
- "totalCacheHitMs",
110
- Math.round((t2 - t0) * 100) / 100,
111
- );
112
- return cached;
113
- }
114
-
115
- span.setAttribute("cacheHit", false);
116
-
117
- // Use global deduplicator to prevent concurrent requests for the same resource
118
- // Note: We do NOT pass the signal to the deduplicator - each caller manages their own abort
119
- const promise = globalRequestDeduplicator.executeRequest(
120
- cacheKey,
121
- async () => {
122
- const fetchStart = performance.now();
123
- try {
124
- // Make the fetch request WITHOUT the signal - let each caller handle their own abort
125
- // This prevents one instance's abort from affecting other instances using the shared cache
126
- const response = await this.host.fetch(url, { headers });
127
- const fetchEnd = performance.now();
128
- span.setAttribute("fetchMs", fetchEnd - fetchStart);
129
-
130
- if (responseType === "json") {
131
- return response.json();
132
- }
133
- const buffer = await response.arrayBuffer();
134
- span.setAttribute("sizeBytes", buffer.byteLength);
135
- return buffer;
136
- } catch (error) {
137
- // If the request was aborted, don't cache the error
138
- if (
139
- error instanceof DOMException &&
140
- error.name === "AbortError"
141
- ) {
142
- // Remove from cache so other requests can retry
143
- mediaCache.delete(cacheKey);
144
- }
145
- throw error;
146
- }
147
- },
148
- );
149
-
150
- // Cache the promise (not the result) to handle concurrent requests
151
- mediaCache.set(cacheKey, promise);
152
-
153
- // Handle the case where the promise might be aborted
154
- promise.catch((error) => {
155
- // If the request was aborted, remove it from cache to prevent corrupted data
156
- if (error instanceof DOMException && error.name === "AbortError") {
157
- mediaCache.delete(cacheKey);
158
- }
159
- });
160
-
161
- // If the caller has a signal, handle abort logic without affecting the underlying request
162
- if (signal) {
163
- const result = await this.handleAbortForCachedRequest(
164
- promise,
165
- signal,
166
- );
167
- const tEnd = performance.now();
168
- span.setAttribute(
169
- "totalFetchMs",
170
- Math.round((tEnd - t0) * 100) / 100,
171
- );
172
- return result;
173
- }
174
-
175
- const result = await promise;
176
- const tEnd = performance.now();
177
- span.setAttribute("totalFetchMs", Math.round((tEnd - t0) * 100) / 100);
178
- return result;
179
- },
180
- );
181
- }
182
-
183
- /**
184
- * Handles abort logic for a cached request without affecting the underlying fetch
185
- * This allows multiple instances to share the same cached request while each
186
- * manages their own abort behavior
187
- */
188
- private handleAbortForCachedRequest<T>(
189
- promise: Promise<T>,
190
- signal: AbortSignal,
191
- ): Promise<T> {
192
- // If signal is already aborted, reject immediately
193
- if (signal.aborted) {
194
- throw new DOMException("Aborted", "AbortError");
195
- }
196
-
197
- // Return a promise that respects the caller's abort signal
198
- // but doesn't affect the underlying cached request
199
- return Promise.race([
200
- promise,
201
- new Promise<never>((_, reject) => {
202
- signal.addEventListener("abort", () => {
203
- reject(new DOMException("Aborted", "AbortError"));
204
- });
205
- }),
206
- ]);
207
- }
208
-
209
- // Public wrapper methods that delegate to fetchWithCache
210
- async fetchMedia(url: string, signal?: AbortSignal): Promise<ArrayBuffer> {
211
- // Check abort signal immediately before any processing
212
- if (signal?.aborted) {
213
- throw new DOMException("Aborted", "AbortError");
214
- }
215
- return this.fetchWithCache(url, { responseType: "arrayBuffer", signal });
216
- }
217
-
218
- async fetchManifest(url: string, signal?: AbortSignal): Promise<any> {
219
- // Check abort signal immediately before any processing
220
- if (signal?.aborted) {
221
- throw new DOMException("Aborted", "AbortError");
222
- }
223
- return this.fetchWithCache(url, { responseType: "json", signal });
224
- }
225
-
226
- async fetchMediaWithHeaders(
227
- url: string,
228
- headers: Record<string, string>,
229
- signal?: AbortSignal,
230
- ): Promise<ArrayBuffer> {
231
- // Check abort signal immediately before any processing
232
- if (signal?.aborted) {
233
- throw new DOMException("Aborted", "AbortError");
234
- }
235
- return this.fetchWithCache(url, {
236
- responseType: "arrayBuffer",
237
- headers,
238
- signal,
239
- });
240
- }
241
-
242
- // Legacy methods for backward compatibility
243
- async fetchMediaCache(
244
- url: string,
245
- signal?: AbortSignal,
246
- ): Promise<ArrayBuffer> {
247
- return this.fetchMedia(url, signal);
248
- }
249
-
250
- async fetchManifestCache(url: string, signal?: AbortSignal): Promise<any> {
251
- return this.fetchManifest(url, signal);
252
- }
253
-
254
- async fetchMediaCacheWithHeaders(
255
- url: string,
256
- headers: Record<string, string>,
257
- signal?: AbortSignal,
258
- ): Promise<ArrayBuffer> {
259
- return this.fetchMediaWithHeaders(url, headers, signal);
260
- }
261
-
262
- /**
263
- * Abstract method for actual segment fetching - implemented by subclasses
264
- */
265
- abstract fetchMediaSegment(
266
- segmentId: number,
267
- rendition: { trackId: number | undefined; src: string },
268
- ): Promise<ArrayBuffer>;
269
-
270
- abstract fetchInitSegment(
271
- rendition: { trackId: number | undefined; src: string },
272
- signal: AbortSignal,
273
- ): Promise<ArrayBuffer>;
274
-
275
- abstract computeSegmentId(
276
- desiredSeekTimeMs: number,
277
- rendition: MediaRendition,
278
- ): number | undefined;
279
-
280
- /**
281
- * Fetch media segment with built-in deduplication
282
- * Now uses global deduplication for all requests
283
- */
284
- async fetchMediaSegmentWithDeduplication(
285
- segmentId: number,
286
- rendition: { trackId: number | undefined; src: string },
287
- _signal?: AbortSignal,
288
- ): Promise<ArrayBuffer> {
289
- const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
290
-
291
- return globalRequestDeduplicator.executeRequest(cacheKey, async () => {
292
- return this.fetchMediaSegment(segmentId, rendition);
293
- });
294
- }
295
-
296
- /**
297
- * Check if a segment is currently being fetched
298
- */
299
- isSegmentBeingFetched(
300
- segmentId: number,
301
- rendition: { src: string; trackId: number | undefined },
302
- ): boolean {
303
- const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
304
- return globalRequestDeduplicator.isPending(cacheKey);
305
- }
306
-
307
- /**
308
- * Get count of active segment requests (for debugging/monitoring)
309
- */
310
- getActiveSegmentRequestCount(): number {
311
- return globalRequestDeduplicator.getPendingCount();
312
- }
313
-
314
- /**
315
- * Cancel all active segment requests (for cleanup)
316
- */
317
- cancelAllSegmentRequests(): void {
318
- globalRequestDeduplicator.clear();
319
- }
320
-
321
- /**
322
- * Calculate audio segments needed for a time range
323
- * Each media engine implements this based on their segment structure
324
- */
325
- calculateAudioSegmentRange(
326
- fromMs: number,
327
- toMs: number,
328
- rendition: AudioRendition,
329
- durationMs: number,
330
- ): SegmentTimeRange[] {
331
- // Default implementation for uniform segments (used by JitMediaEngine)
332
- if (fromMs >= toMs) {
333
- return [];
334
- }
335
-
336
- const segments: SegmentTimeRange[] = [];
337
-
338
- // Use actual segment durations if available (more accurate)
339
- if (
340
- rendition.segmentDurationsMs &&
341
- rendition.segmentDurationsMs.length > 0
342
- ) {
343
- let cumulativeTime = 0;
344
-
345
- for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
346
- const segmentDuration = rendition.segmentDurationsMs[i];
347
- if (segmentDuration === undefined) {
348
- continue; // Skip undefined segment durations
349
- }
350
- const segmentStartMs = cumulativeTime;
351
- const segmentEndMs = Math.min(
352
- cumulativeTime + segmentDuration,
353
- durationMs,
354
- );
355
-
356
- // Don't include segments that start at or beyond the file duration
357
- if (segmentStartMs >= durationMs) {
358
- break;
359
- }
360
-
361
- // Only include segments that overlap with requested time range
362
- if (segmentStartMs < toMs && segmentEndMs > fromMs) {
363
- segments.push({
364
- segmentId: i + 1, // Convert to 1-based
365
- startMs: segmentStartMs,
366
- endMs: segmentEndMs,
367
- });
368
- }
369
-
370
- cumulativeTime += segmentDuration;
371
-
372
- // If we've reached or exceeded file duration, stop
373
- if (cumulativeTime >= durationMs) {
374
- break;
375
- }
376
- }
377
-
378
- return segments;
379
- }
380
-
381
- // Fall back to fixed duration calculation for backward compatibility
382
- const segmentDurationMs = rendition.segmentDurationMs || 1000;
383
- const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);
384
- const endSegmentIndex = Math.floor(toMs / segmentDurationMs);
385
-
386
- for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
387
- const segmentId = i + 1; // Convert to 1-based
388
- const segmentStartMs = i * segmentDurationMs;
389
- const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);
390
-
391
- // Don't include segments that start at or beyond the file duration
392
- if (segmentStartMs >= durationMs) {
393
- break;
394
- }
395
-
396
- // Only include segments that overlap with requested time range
397
- if (segmentStartMs < toMs && segmentEndMs > fromMs) {
398
- segments.push({
399
- segmentId,
400
- startMs: segmentStartMs,
401
- endMs: segmentEndMs,
402
- });
403
- }
404
- }
405
-
406
- return segments;
407
- }
408
-
409
- /**
410
- * Check if a segment is cached for a given rendition
411
- * This needs to check the URL-based cache since that's where segments are actually stored
412
- */
413
- isSegmentCached(
414
- segmentId: number,
415
- rendition: AudioRendition | VideoRendition,
416
- ): boolean {
417
- try {
418
- // Check if this is a JIT engine by looking for urlGenerator property
419
- const maybeJitEngine = this as any;
420
- if (
421
- maybeJitEngine.urlGenerator &&
422
- typeof maybeJitEngine.urlGenerator.generateSegmentUrl === "function"
423
- ) {
424
- // This is a JIT engine - generate the URL and check URL-based cache
425
- if (!rendition.id) {
426
- return false;
427
- }
428
-
429
- const segmentUrl = maybeJitEngine.urlGenerator.generateSegmentUrl(
430
- segmentId,
431
- rendition.id,
432
- maybeJitEngine,
433
- );
434
- const urlIsCached = mediaCache.has(segmentUrl);
435
-
436
- return urlIsCached;
437
- }
438
- // For other engine types, fall back to the old segment-based key approach
439
- const cacheKey = `${rendition.src}-${rendition.id || "default"}-${segmentId}-${rendition.trackId}`;
440
- const isCached = mediaCache.has(cacheKey);
441
- return isCached;
442
- } catch (error) {
443
- console.warn(
444
- `🎬 BaseMediaEngine: Error checking if segment ${segmentId} is cached:`,
445
- error,
446
- );
447
- return false;
448
- }
449
- }
450
-
451
- /**
452
- * Get cached segment IDs from a list for a given rendition
453
- */
454
- getCachedSegments(
455
- segmentIds: number[],
456
- rendition: AudioRendition | VideoRendition,
457
- ): Set<number> {
458
- return new Set(
459
- segmentIds.filter((id) => this.isSegmentCached(id, rendition)),
460
- );
461
- }
462
-
463
- /**
464
- * Extract thumbnail canvases at multiple timestamps efficiently
465
- * Default implementation provides helpful error information
466
- */
467
- async extractThumbnails(
468
- timestamps: number[],
469
- ): Promise<(ThumbnailResult | null)[]> {
470
- const engineName = this.constructor.name;
471
- console.warn(
472
- `${engineName}: extractThumbnails not properly implemented. ` +
473
- "This MediaEngine type does not support thumbnail generation. " +
474
- "Supported engines: JitMediaEngine. " +
475
- `Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? "" : "s"}.`,
476
- );
477
- return timestamps.map(() => null);
478
- }
479
-
480
- abstract convertToSegmentRelativeTimestamps(
481
- globalTimestamps: number[],
482
- segmentId: number,
483
- rendition: VideoRendition,
484
- ): number[];
485
-
486
- /**
487
- * Get buffer configuration for this media engine
488
- * Can be overridden by subclasses to provide custom buffer settings
489
- */
490
- getBufferConfig(): {
491
- videoBufferDurationMs: number;
492
- audioBufferDurationMs: number;
493
- maxVideoBufferFetches: number;
494
- maxAudioBufferFetches: number;
495
- bufferThresholdMs: number;
496
- } {
497
- return {
498
- videoBufferDurationMs: 10000, // 10 seconds
499
- audioBufferDurationMs: 10000, // 10 seconds
500
- maxVideoBufferFetches: 3,
501
- maxAudioBufferFetches: 3,
502
- bufferThresholdMs: 30000, // 30 seconds - timeline-aware buffering threshold
503
- };
504
- }
505
- }