@editframe/elements 0.20.3-beta.0 → 0.21.0-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 (128) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +51 -29
  4. package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
  5. package/dist/elements/ContextProxiesController.js +2 -22
  6. package/dist/elements/EFAudio.js +4 -8
  7. package/dist/elements/EFCaptions.js +59 -84
  8. package/dist/elements/EFImage.js +5 -6
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
  10. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +4 -4
  11. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -32
  12. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
  13. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -67
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
  15. package/dist/elements/EFMedia/JitMediaEngine.js +22 -23
  16. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
  17. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -3
  18. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +2 -2
  19. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +9 -7
  20. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  21. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
  22. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +2 -2
  23. package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
  24. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +6 -3
  25. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
  26. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -17
  27. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
  28. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -13
  29. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  30. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
  31. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  32. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
  33. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  34. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
  35. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +2 -16
  36. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  37. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  38. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  39. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +3 -8
  40. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
  41. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -7
  42. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  43. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
  44. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -3
  45. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -71
  46. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +8 -12
  47. package/dist/elements/EFMedia.d.ts +2 -1
  48. package/dist/elements/EFMedia.js +26 -23
  49. package/dist/elements/EFSourceMixin.js +5 -7
  50. package/dist/elements/EFSurface.js +6 -9
  51. package/dist/elements/EFTemporal.js +19 -37
  52. package/dist/elements/EFThumbnailStrip.js +16 -59
  53. package/dist/elements/EFTimegroup.js +96 -91
  54. package/dist/elements/EFVideo.d.ts +6 -2
  55. package/dist/elements/EFVideo.js +142 -107
  56. package/dist/elements/EFWaveform.js +18 -27
  57. package/dist/elements/SampleBuffer.js +2 -5
  58. package/dist/elements/TargetController.js +3 -3
  59. package/dist/elements/durationConverter.js +4 -4
  60. package/dist/elements/updateAnimations.js +14 -35
  61. package/dist/gui/ContextMixin.js +23 -52
  62. package/dist/gui/EFConfiguration.js +7 -7
  63. package/dist/gui/EFControls.js +5 -5
  64. package/dist/gui/EFFilmstrip.js +77 -98
  65. package/dist/gui/EFFitScale.js +5 -6
  66. package/dist/gui/EFFocusOverlay.js +4 -4
  67. package/dist/gui/EFPreview.js +4 -4
  68. package/dist/gui/EFScrubber.js +9 -9
  69. package/dist/gui/EFTimeDisplay.js +5 -5
  70. package/dist/gui/EFToggleLoop.js +4 -4
  71. package/dist/gui/EFTogglePlay.js +5 -5
  72. package/dist/gui/EFWorkbench.js +5 -5
  73. package/dist/gui/TWMixin2.js +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  76. package/dist/otel/BridgeSpanExporter.js +87 -0
  77. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  78. package/dist/otel/setupBrowserTracing.js +30 -0
  79. package/dist/otel/tracingHelpers.d.ts +34 -0
  80. package/dist/otel/tracingHelpers.js +113 -0
  81. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  82. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  83. package/dist/transcoding/types/index.d.ts +6 -4
  84. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  85. package/dist/utils/LRUCache.js +6 -53
  86. package/package.json +10 -2
  87. package/src/elements/EFCaptions.browsertest.ts +2 -0
  88. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
  89. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
  90. package/src/elements/EFMedia/AssetMediaEngine.ts +81 -43
  91. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
  92. package/src/elements/EFMedia/BaseMediaEngine.ts +120 -60
  93. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  94. package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
  95. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
  96. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
  97. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
  98. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +18 -8
  99. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
  100. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
  101. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
  102. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
  103. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
  104. package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
  105. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
  106. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
  107. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
  108. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
  109. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  110. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
  111. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
  112. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +17 -15
  113. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  114. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
  115. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
  116. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -125
  117. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
  118. package/src/elements/EFMedia.ts +18 -2
  119. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
  120. package/src/elements/EFTimegroup.browsertest.ts +10 -8
  121. package/src/elements/EFTimegroup.ts +165 -77
  122. package/src/elements/EFVideo.browsertest.ts +19 -27
  123. package/src/elements/EFVideo.ts +203 -101
  124. package/src/otel/BridgeSpanExporter.ts +150 -0
  125. package/src/otel/setupBrowserTracing.ts +68 -0
  126. package/src/otel/tracingHelpers.ts +251 -0
  127. package/src/transcoding/types/index.ts +6 -4
  128. package/types.json +1 -1
@@ -16,7 +16,7 @@ const fetchAudioSegmentData = async (
16
16
  ): Promise<Map<number, ArrayBuffer>> => {
17
17
  const audioRendition = mediaEngine.audioRendition;
18
18
  if (!audioRendition) {
19
- throw new Error("No audio track available in source");
19
+ throw new Error("Audio rendition not available");
20
20
  }
21
21
 
22
22
  const segmentData = new Map<number, ArrayBuffer>();
@@ -62,7 +62,7 @@ export const fetchAudioSpanningTime = async (
62
62
  fromMs: number,
63
63
  toMs: number,
64
64
  signal: AbortSignal,
65
- ): Promise<AudioSpan> => {
65
+ ): Promise<AudioSpan | undefined> => {
66
66
  // Validate inputs
67
67
  if (fromMs >= toMs || fromMs < 0) {
68
68
  throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
@@ -72,12 +72,13 @@ export const fetchAudioSpanningTime = async (
72
72
  const mediaEngine = await host.mediaEngineTask.taskComplete;
73
73
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
74
74
 
75
+ // Return undefined if no audio rendition available
75
76
  if (!mediaEngine?.audioRendition) {
76
- throw new Error("No audio track available in source");
77
+ return undefined;
77
78
  }
78
79
 
79
80
  if (!initSegment) {
80
- throw new Error("Audio init segment is not available");
81
+ return undefined;
81
82
  }
82
83
 
83
84
  // Calculate segments needed using the media engine's method
@@ -35,7 +35,7 @@ export interface MediaBufferDependencies<
35
35
  ) => Promise<number | undefined>;
36
36
  prefetchSegment: (segmentId: number, rendition: T) => Promise<void>; // Just trigger prefetch, don't return data
37
37
  isSegmentCached: (segmentId: number, rendition: T) => boolean; // Check BaseMediaEngine cache
38
- getRendition: () => Promise<T>;
38
+ getRendition: () => Promise<T | undefined>;
39
39
  logError: (message: string, error: any) => void;
40
40
  }
41
41
 
@@ -208,6 +208,10 @@ export const manageMediaBuffer = async <
208
208
  }
209
209
 
210
210
  const rendition = await deps.getRendition();
211
+ if (!rendition) {
212
+ // Cannot buffer without a rendition
213
+ return currentState;
214
+ }
211
215
  const endTimeMs = seekTimeMs + config.bufferDurationMs;
212
216
 
213
217
  const desiredSegments = await computeSegmentRangeAsync(
@@ -217,7 +221,6 @@ export const manageMediaBuffer = async <
217
221
  rendition,
218
222
  deps.computeSegmentId,
219
223
  );
220
-
221
224
  // Filter out segments already cached by BaseMediaEngine
222
225
  const uncachedSegments = desiredSegments.filter(
223
226
  (segmentId) => !deps.isSegmentCached(segmentId, rendition),
@@ -287,10 +290,11 @@ export const manageMediaBuffer = async <
287
290
  startNextSegment();
288
291
  }
289
292
 
290
- return {
293
+ const result = {
291
294
  currentSeekTimeMs: seekTimeMs,
292
295
  requestedSegments: newRequestedSegments,
293
296
  activeRequests: newActiveRequests,
294
297
  requestQueue: remainingQueue, // What's left in the queue
295
298
  };
299
+ return result;
296
300
  };
@@ -40,5 +40,5 @@ export type SegmentFetchTask = Task<
40
40
  */
41
41
  export type InputTask = Task<
42
42
  readonly [ArrayBuffer, ArrayBuffer],
43
- BufferedSeekingInput
43
+ BufferedSeekingInput | undefined
44
44
  >;
@@ -4,12 +4,7 @@ import type {
4
4
  MediaEngine,
5
5
  VideoRendition,
6
6
  } from "../../../transcoding/types";
7
- import {
8
- calculateSegmentRange,
9
- computeSegmentId,
10
- getAudioRendition,
11
- getVideoRendition,
12
- } from "./RenditionHelpers";
7
+ import { calculateSegmentRange, computeSegmentId } from "./RenditionHelpers";
13
8
 
14
9
  const test = baseTest.extend<{
15
10
  mockMediaEngine: MediaEngine;
@@ -36,6 +31,8 @@ const test = baseTest.extend<{
36
31
  src: "https://example.com/media.mp4",
37
32
  videoRendition: mockVideoRendition,
38
33
  audioRendition: mockAudioRendition,
34
+ getVideoRendition: () => mockVideoRendition,
35
+ getAudioRendition: () => mockAudioRendition,
39
36
  fetchMediaSegment: vi.fn(),
40
37
  } as unknown as MediaEngine;
41
38
  await use(mockMediaEngine);
@@ -60,30 +57,38 @@ const test = baseTest.extend<{
60
57
  },
61
58
 
62
59
  mockMediaEngineWithoutAudio: async ({}, use) => {
60
+ const videoRendition = {
61
+ trackId: 1,
62
+ src: "video-track.mp4",
63
+ segmentDurationMs: 1000,
64
+ } as VideoRendition;
65
+
63
66
  const mockMediaEngine = {
64
67
  durationMs: 10000,
65
68
  src: "https://example.com/media.mp4",
66
- videoRendition: {
67
- trackId: 1,
68
- src: "video-track.mp4",
69
- segmentDurationMs: 1000,
70
- } as VideoRendition,
71
- audioRendition: null,
69
+ videoRendition,
70
+ audioRendition: undefined,
71
+ getVideoRendition: () => videoRendition,
72
+ getAudioRendition: () => undefined,
72
73
  fetchMediaSegment: vi.fn(),
73
74
  } as unknown as MediaEngine;
74
75
  await use(mockMediaEngine);
75
76
  },
76
77
 
77
78
  mockMediaEngineWithoutVideo: async ({}, use) => {
79
+ const audioRendition = {
80
+ trackId: 2,
81
+ src: "audio-track.mp4",
82
+ segmentDurationMs: 1000,
83
+ } as AudioRendition;
84
+
78
85
  const mockMediaEngine = {
79
86
  durationMs: 10000,
80
87
  src: "https://example.com/media.mp4",
81
- videoRendition: null,
82
- audioRendition: {
83
- trackId: 2,
84
- src: "audio-track.mp4",
85
- segmentDurationMs: 1000,
86
- } as AudioRendition,
88
+ videoRendition: undefined,
89
+ audioRendition,
90
+ getVideoRendition: () => undefined,
91
+ getAudioRendition: () => audioRendition,
87
92
  fetchMediaSegment: vi.fn(),
88
93
  } as unknown as MediaEngine;
89
94
  await use(mockMediaEngine);
@@ -91,45 +96,39 @@ const test = baseTest.extend<{
91
96
  });
92
97
 
93
98
  describe("RenditionHelpers", () => {
94
- describe("getAudioRendition", () => {
95
- test("returns audio rendition when available", ({
96
- mockMediaEngine,
99
+ describe("MediaEngine Rendition Access", () => {
100
+ test("mediaEngine.getAudioRendition() returns undefined for video-only assets", ({
101
+ mockMediaEngineWithoutAudio,
97
102
  expect,
98
103
  }) => {
99
- const result = getAudioRendition(mockMediaEngine);
100
- expect(result).toBe(mockMediaEngine.audioRendition);
101
- expect(result.trackId).toBe(2);
102
- expect(result.src).toBe("audio-track.mp4");
104
+ const result = mockMediaEngineWithoutAudio.getAudioRendition();
105
+ expect(result).toBeUndefined();
103
106
  });
104
107
 
105
- test("throws error when audio rendition is not available", ({
106
- mockMediaEngineWithoutAudio,
108
+ test("mediaEngine.getVideoRendition() returns undefined for audio-only assets", ({
109
+ mockMediaEngineWithoutVideo,
107
110
  expect,
108
111
  }) => {
109
- expect(() => getAudioRendition(mockMediaEngineWithoutAudio)).toThrow(
110
- "No audio track available in source",
111
- );
112
+ const result = mockMediaEngineWithoutVideo.getVideoRendition();
113
+ expect(result).toBeUndefined();
112
114
  });
113
- });
114
115
 
115
- describe("getVideoRendition", () => {
116
- test("returns video rendition when available", ({
116
+ test("mediaEngine.getAudioRendition() returns rendition when available", ({
117
117
  mockMediaEngine,
118
118
  expect,
119
119
  }) => {
120
- const result = getVideoRendition(mockMediaEngine);
121
- expect(result).toBe(mockMediaEngine.videoRendition);
122
- expect(result.trackId).toBe(1);
123
- expect(result.src).toBe("video-track.mp4");
120
+ const result = mockMediaEngine.getAudioRendition();
121
+ expect(result).toBeDefined();
122
+ expect(result?.trackId).toBe(2);
124
123
  });
125
124
 
126
- test("throws error when video rendition is not available", ({
127
- mockMediaEngineWithoutVideo,
125
+ test("mediaEngine.getVideoRendition() returns rendition when available", ({
126
+ mockMediaEngine,
128
127
  expect,
129
128
  }) => {
130
- expect(() => getVideoRendition(mockMediaEngineWithoutVideo)).toThrow(
131
- "No video track available in source",
132
- );
129
+ const result = mockMediaEngine.getVideoRendition();
130
+ expect(result).toBeDefined();
131
+ expect(result?.trackId).toBe(1);
133
132
  });
134
133
  });
135
134
 
@@ -1,31 +1,8 @@
1
1
  import type {
2
2
  AudioRendition,
3
- MediaEngine,
4
3
  VideoRendition,
5
4
  } from "../../../transcoding/types";
6
5
 
7
- /**
8
- * Get audio rendition from media engine, throwing if not available
9
- */
10
- export const getAudioRendition = (mediaEngine: MediaEngine): AudioRendition => {
11
- const audioRendition = mediaEngine.audioRendition;
12
- if (!audioRendition) {
13
- throw new Error("No audio track available in source");
14
- }
15
- return audioRendition;
16
- };
17
-
18
- /**
19
- * Get video rendition from media engine, throwing if not available
20
- */
21
- export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
- const videoRendition = mediaEngine.videoRendition;
23
- if (!videoRendition) {
24
- throw new Error("No video track available in source");
25
- }
26
- return videoRendition;
27
- };
28
-
29
6
  /**
30
7
  * Calculate which segment contains a given timestamp
31
8
  * Returns 1-based segment ID, or undefined if segmentDurationMs is not available
@@ -1,6 +1,6 @@
1
1
  import { Task } from "@lit/task";
2
2
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE";
3
- import type { MediaEngine, VideoRendition } from "../../../transcoding/types";
3
+ import type { MediaEngine } from "../../../transcoding/types";
4
4
  import type { EFMedia } from "../../EFMedia";
5
5
  import { AssetIdMediaEngine } from "../AssetIdMediaEngine";
6
6
  import { AssetMediaEngine } from "../AssetMediaEngine";
@@ -18,14 +18,6 @@ export const getLatestMediaEngine = async (
18
18
  return mediaEngine;
19
19
  };
20
20
 
21
- export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
- const videoRendition = mediaEngine.videoRendition;
23
- if (!videoRendition) {
24
- throw new Error("No video track available in source");
25
- }
26
- return videoRendition;
27
- };
28
-
29
21
  /**
30
22
  * Core logic for creating a MediaEngine with explicit dependencies.
31
23
  * Pure function that requires all dependencies to be provided.
@@ -0,0 +1,76 @@
1
+ import type { BufferedSeekingInput } from "../BufferedSeekingInput";
2
+
3
+ /**
4
+ * Cache for main video BufferedSeekingInput instances
5
+ * Main video segments are typically 2s long, so we can reuse the same input
6
+ * for multiple frames within that segment (e.g., 60 frames at 30fps)
7
+ */
8
+ export class MainVideoInputCache {
9
+ private cache = new Map<string, BufferedSeekingInput>();
10
+ private maxCacheSize = 10; // Keep last 10 main inputs (covers 20 seconds at 2s/segment)
11
+
12
+ /**
13
+ * Create a cache key that uniquely identifies a segment
14
+ */
15
+ private getCacheKey(
16
+ src: string,
17
+ segmentId: number,
18
+ renditionId: string | undefined,
19
+ ): string {
20
+ return `${src}:${renditionId || "default"}:${segmentId}`;
21
+ }
22
+
23
+ /**
24
+ * Get or create BufferedSeekingInput for a main video segment
25
+ */
26
+ async getOrCreateInput(
27
+ src: string,
28
+ segmentId: number,
29
+ renditionId: string | undefined,
30
+ createInputFn: () => Promise<BufferedSeekingInput | undefined>,
31
+ ): Promise<BufferedSeekingInput | undefined> {
32
+ const cacheKey = this.getCacheKey(src, segmentId, renditionId);
33
+
34
+ // Check if we already have this segment cached
35
+ const cached = this.cache.get(cacheKey);
36
+ if (cached) {
37
+ return cached;
38
+ }
39
+
40
+ // Create new input
41
+ const input = await createInputFn();
42
+ if (!input) {
43
+ return undefined;
44
+ }
45
+
46
+ // Add to cache and maintain size limit
47
+ this.cache.set(cacheKey, input);
48
+
49
+ // Evict oldest entries if cache is too large (LRU-like behavior)
50
+ if (this.cache.size > this.maxCacheSize) {
51
+ const oldestKey = this.cache.keys().next().value;
52
+ if (oldestKey !== undefined) {
53
+ this.cache.delete(oldestKey);
54
+ }
55
+ }
56
+
57
+ return input;
58
+ }
59
+
60
+ /**
61
+ * Clear the entire cache (called when video changes)
62
+ */
63
+ clear() {
64
+ this.cache.clear();
65
+ }
66
+
67
+ /**
68
+ * Get cache statistics
69
+ */
70
+ getStats() {
71
+ return {
72
+ size: this.cache.size,
73
+ cacheKeys: Array.from(this.cache.keys()),
74
+ };
75
+ }
76
+ }
@@ -1,4 +1,6 @@
1
1
  import { Task } from "@lit/task";
2
+
3
+ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
4
  import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
5
  import type { VideoRendition } from "../../../transcoding/types";
4
6
  import type { EFVideo } from "../../EFVideo";
@@ -19,8 +21,7 @@ export const makeScrubVideoBufferTask = (host: EFVideo) => {
19
21
  };
20
22
 
21
23
  return new Task(host, {
22
- // Make lazy - only run when element becomes timeline-active
23
- autoRun: false,
24
+ autoRun: EF_INTERACTIVE,
24
25
  args: () => [host.mediaEngineTask.value] as const,
25
26
  onError: (error) => {
26
27
  console.error("scrubVideoBufferTask error", error);
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -14,10 +13,6 @@ export const makeScrubVideoInitSegmentFetchTask = (
14
13
  },
15
14
  onComplete: (_value) => {},
16
15
  task: async ([_mediaEngine], { signal }) => {
17
- if (EF_RENDERING()) {
18
- return new ArrayBuffer(0);
19
- }
20
-
21
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
22
17
 
23
18
  // Get scrub rendition using the proper interface method
@@ -1,5 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
2
+
3
3
  import { EFMedia } from "../../EFMedia";
4
4
  import type { EFVideo } from "../../EFVideo";
5
5
  import { BufferedSeekingInput } from "../BufferedSeekingInput";
@@ -8,7 +8,7 @@ import type { InputTask } from "../shared/MediaTaskUtils";
8
8
  export const makeScrubVideoInputTask = (host: EFVideo): InputTask => {
9
9
  return new Task<
10
10
  readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],
11
- BufferedSeekingInput
11
+ BufferedSeekingInput | undefined
12
12
  >(host, {
13
13
  args: () =>
14
14
  [
@@ -19,31 +19,33 @@ export const makeScrubVideoInputTask = (host: EFVideo): InputTask => {
19
19
  console.error("scrubVideoInputTask error", error);
20
20
  },
21
21
  onComplete: (_value) => {},
22
- task: async () => {
23
- if (EF_RENDERING()) {
24
- console.info("Scrub not available in rendering mode");
25
- }
26
-
22
+ task: async (_, { signal }) => {
27
23
  const initSegment =
28
24
  await host.scrubVideoInitSegmentFetchTask.taskComplete;
25
+ if (signal.aborted) return undefined;
26
+
29
27
  const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
28
+ if (signal.aborted) return undefined;
29
+
30
30
  if (!initSegment || !segment) {
31
31
  throw new Error("Scrub init segment or segment is not available");
32
32
  }
33
33
 
34
34
  // Get startTimeOffsetMs from the scrub rendition if available
35
35
  const mediaEngine = await host.mediaEngineTask.taskComplete;
36
+ if (signal.aborted) return undefined;
37
+
36
38
  const scrubRendition = mediaEngine.getScrubVideoRendition();
37
39
  const startTimeOffsetMs = scrubRendition?.startTimeOffsetMs;
38
40
 
39
- const input = new BufferedSeekingInput(
40
- await new Blob([initSegment, segment]).arrayBuffer(),
41
- {
42
- videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
43
- audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
44
- startTimeOffsetMs,
45
- },
46
- );
41
+ const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
42
+ if (signal.aborted) return undefined;
43
+
44
+ const input = new BufferedSeekingInput(arrayBuffer, {
45
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
46
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
47
+ startTimeOffsetMs,
48
+ });
47
49
  return input;
48
50
  },
49
51
  });
@@ -94,13 +94,19 @@ export const makeScrubVideoSeekTask = (host: EFVideo): ScrubVideoSeekTask => {
94
94
  return undefined;
95
95
  }
96
96
 
97
+ if (signal.aborted) {
98
+ return undefined;
99
+ }
100
+
97
101
  // Get video track and seek to precise time within the 30s scrub segment
98
102
  const videoTrack = await scrubInput.getFirstVideoTrack();
99
103
  if (!videoTrack) {
100
104
  return undefined;
101
105
  }
102
106
 
103
- signal.throwIfAborted();
107
+ if (signal.aborted) {
108
+ return undefined;
109
+ }
104
110
 
105
111
  const sample = (await scrubInput.seek(
106
112
  videoTrack.id,
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -18,10 +17,6 @@ export const makeScrubVideoSegmentFetchTask = (
18
17
  },
19
18
  onComplete: (_value) => {},
20
19
  task: async (_, { signal }) => {
21
- if (EF_RENDERING()) {
22
- return new ArrayBuffer(0);
23
- }
24
-
25
20
  const mediaEngine = await getLatestMediaEngine(host, signal);
26
21
  const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
27
22
  if (segmentId === undefined) {
@@ -1,5 +1,4 @@
1
1
  import { Task } from "@lit/task";
2
- import { EF_RENDERING } from "../../../EF_RENDERING";
3
2
  import type { MediaEngine } from "../../../transcoding/types";
4
3
  import type { EFVideo } from "../../EFVideo";
5
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -14,10 +13,6 @@ export const makeScrubVideoSegmentIdTask = (
14
13
  },
15
14
  onComplete: (_value) => {},
16
15
  task: async ([, targetSeekTimeMs], { signal }) => {
17
- if (EF_RENDERING()) {
18
- return undefined;
19
- }
20
-
21
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
22
17
  signal.throwIfAborted(); // Abort if a new seek started
23
18