@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.
- package/dist/DelayedLoadingState.js +0 -27
- package/dist/EF_FRAMEGEN.d.ts +5 -3
- package/dist/EF_FRAMEGEN.js +51 -29
- package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
- package/dist/elements/ContextProxiesController.js +2 -22
- package/dist/elements/EFAudio.js +4 -8
- package/dist/elements/EFCaptions.js +59 -84
- package/dist/elements/EFImage.js +5 -6
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +4 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +41 -32
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
- package/dist/elements/EFMedia/BaseMediaEngine.js +57 -67
- package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
- package/dist/elements/EFMedia/JitMediaEngine.js +22 -23
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +2 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +9 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +2 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +6 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -17
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +2 -13
- package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +2 -16
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +3 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -7
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -3
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -71
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +8 -12
- package/dist/elements/EFMedia.d.ts +2 -1
- package/dist/elements/EFMedia.js +26 -23
- package/dist/elements/EFSourceMixin.js +5 -7
- package/dist/elements/EFSurface.js +6 -9
- package/dist/elements/EFTemporal.js +19 -37
- package/dist/elements/EFThumbnailStrip.js +16 -59
- package/dist/elements/EFTimegroup.js +96 -91
- package/dist/elements/EFVideo.d.ts +6 -2
- package/dist/elements/EFVideo.js +142 -107
- package/dist/elements/EFWaveform.js +18 -27
- package/dist/elements/SampleBuffer.js +2 -5
- package/dist/elements/TargetController.js +3 -3
- package/dist/elements/durationConverter.js +4 -4
- package/dist/elements/updateAnimations.js +14 -35
- package/dist/gui/ContextMixin.js +23 -52
- package/dist/gui/EFConfiguration.js +7 -7
- package/dist/gui/EFControls.js +5 -5
- package/dist/gui/EFFilmstrip.js +77 -98
- package/dist/gui/EFFitScale.js +5 -6
- package/dist/gui/EFFocusOverlay.js +4 -4
- package/dist/gui/EFPreview.js +4 -4
- package/dist/gui/EFScrubber.js +9 -9
- package/dist/gui/EFTimeDisplay.js +5 -5
- package/dist/gui/EFToggleLoop.js +4 -4
- package/dist/gui/EFTogglePlay.js +5 -5
- package/dist/gui/EFWorkbench.js +5 -5
- package/dist/gui/TWMixin2.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/otel/BridgeSpanExporter.d.ts +13 -0
- package/dist/otel/BridgeSpanExporter.js +87 -0
- package/dist/otel/setupBrowserTracing.d.ts +12 -0
- package/dist/otel/setupBrowserTracing.js +30 -0
- package/dist/otel/tracingHelpers.d.ts +34 -0
- package/dist/otel/tracingHelpers.js +113 -0
- package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
- package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
- package/dist/transcoding/types/index.d.ts +6 -4
- package/dist/transcoding/utils/UrlGenerator.js +2 -19
- package/dist/utils/LRUCache.js +6 -53
- package/package.json +10 -2
- package/src/elements/EFCaptions.browsertest.ts +2 -0
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
- package/src/elements/EFMedia/AssetMediaEngine.ts +81 -43
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +120 -60
- package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
- package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +18 -8
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
- package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +17 -15
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -125
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
- package/src/elements/EFMedia.ts +18 -2
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
- package/src/elements/EFTimegroup.browsertest.ts +10 -8
- package/src/elements/EFTimegroup.ts +165 -77
- package/src/elements/EFVideo.browsertest.ts +19 -27
- package/src/elements/EFVideo.ts +203 -101
- package/src/otel/BridgeSpanExporter.ts +150 -0
- package/src/otel/setupBrowserTracing.ts +68 -0
- package/src/otel/tracingHelpers.ts +251 -0
- package/src/transcoding/types/index.ts +6 -4
- package/types.json +1 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
2
|
import type { VideoSample } from "mediabunny";
|
|
3
|
-
import {
|
|
3
|
+
import { withSpan } from "../../../otel/tracingHelpers.js";
|
|
4
4
|
import type { VideoRendition } from "../../../transcoding/types";
|
|
5
|
+
import { EFMedia } from "../../EFMedia.js";
|
|
5
6
|
import type { EFVideo } from "../../EFVideo";
|
|
7
|
+
import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
|
|
6
8
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
|
|
9
|
+
import { MainVideoInputCache } from "./MainVideoInputCache";
|
|
7
10
|
import { ScrubInputCache } from "./ScrubInputCache";
|
|
8
11
|
|
|
9
12
|
type UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;
|
|
@@ -11,6 +14,9 @@ type UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;
|
|
|
11
14
|
// Shared cache for scrub inputs
|
|
12
15
|
const scrubInputCache = new ScrubInputCache();
|
|
13
16
|
|
|
17
|
+
// Shared cache for main video inputs
|
|
18
|
+
const mainVideoInputCache = new MainVideoInputCache();
|
|
19
|
+
|
|
14
20
|
export const makeUnifiedVideoSeekTask = (
|
|
15
21
|
host: EFVideo,
|
|
16
22
|
): UnifiedVideoSeekTask => {
|
|
@@ -23,17 +29,7 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
23
29
|
onComplete: (_value) => {},
|
|
24
30
|
task: async ([desiredSeekTimeMs], { signal }) => {
|
|
25
31
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
26
|
-
if (!mediaEngine) return undefined;
|
|
27
|
-
|
|
28
|
-
// In rendering mode, skip all the scrub logic and go straight to main
|
|
29
|
-
if (EF_RENDERING()) {
|
|
30
|
-
return await getMainVideoSample(
|
|
31
|
-
host,
|
|
32
|
-
mediaEngine,
|
|
33
|
-
desiredSeekTimeMs,
|
|
34
|
-
signal,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
32
|
+
if (!mediaEngine || signal.aborted) return undefined;
|
|
37
33
|
|
|
38
34
|
// FIRST: Check if main quality content is already cached
|
|
39
35
|
const mainRendition = mediaEngine.videoRendition;
|
|
@@ -46,13 +42,18 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
46
42
|
mainSegmentId !== undefined &&
|
|
47
43
|
mediaEngine.isSegmentCached(mainSegmentId, mainRendition)
|
|
48
44
|
) {
|
|
49
|
-
|
|
50
|
-
return await getMainVideoSample(
|
|
45
|
+
const result = await getMainVideoSample(
|
|
51
46
|
host,
|
|
52
47
|
mediaEngine,
|
|
53
48
|
desiredSeekTimeMs,
|
|
54
49
|
signal,
|
|
55
50
|
);
|
|
51
|
+
|
|
52
|
+
if (signal.aborted) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -62,7 +63,12 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
62
63
|
desiredSeekTimeMs,
|
|
63
64
|
signal,
|
|
64
65
|
);
|
|
66
|
+
|
|
65
67
|
if (scrubSample || signal.aborted) {
|
|
68
|
+
if (signal.aborted) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
// If scrub succeeded, start background main quality upgrade (non-blocking)
|
|
67
73
|
if (scrubSample) {
|
|
68
74
|
startMainQualityUpgrade(
|
|
@@ -79,12 +85,18 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
// THIRD: Neither are cached, fetch main video path as final fallback
|
|
82
|
-
|
|
88
|
+
const result = await getMainVideoSample(
|
|
83
89
|
host,
|
|
84
90
|
mediaEngine,
|
|
85
91
|
desiredSeekTimeMs,
|
|
86
92
|
signal,
|
|
87
93
|
);
|
|
94
|
+
|
|
95
|
+
if (signal.aborted) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
88
100
|
},
|
|
89
101
|
});
|
|
90
102
|
};
|
|
@@ -97,82 +109,124 @@ async function tryGetScrubSample(
|
|
|
97
109
|
desiredSeekTimeMs: number,
|
|
98
110
|
signal: AbortSignal,
|
|
99
111
|
): Promise<VideoSample | undefined> {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
return withSpan(
|
|
113
|
+
"video.tryGetScrubSample",
|
|
114
|
+
{
|
|
115
|
+
desiredSeekTimeMs,
|
|
116
|
+
src: mediaEngine.src || "unknown",
|
|
117
|
+
},
|
|
118
|
+
undefined,
|
|
119
|
+
async (span) => {
|
|
120
|
+
try {
|
|
121
|
+
// Get scrub rendition
|
|
122
|
+
let scrubRendition: VideoRendition | undefined;
|
|
123
|
+
|
|
124
|
+
// Check if media engine has a getScrubVideoRendition method (AssetMediaEngine, etc.)
|
|
125
|
+
if (typeof mediaEngine.getScrubVideoRendition === "function") {
|
|
126
|
+
scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
127
|
+
} else if ("data" in mediaEngine && mediaEngine.data?.videoRenditions) {
|
|
128
|
+
// Fallback to data structure for other engines
|
|
129
|
+
scrubRendition = mediaEngine.data.videoRenditions.find(
|
|
130
|
+
(r: any) => r.id === "scrub",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
113
133
|
|
|
114
|
-
|
|
134
|
+
if (!scrubRendition) {
|
|
135
|
+
span.setAttribute("result", "no-scrub-rendition");
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
const scrubRenditionWithSrc = {
|
|
140
|
+
...scrubRendition,
|
|
141
|
+
src: mediaEngine.src,
|
|
142
|
+
};
|
|
120
143
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
);
|
|
126
|
-
if (segmentId === undefined) return undefined;
|
|
127
|
-
|
|
128
|
-
const isCached = mediaEngine.isSegmentCached(
|
|
129
|
-
segmentId,
|
|
130
|
-
scrubRenditionWithSrc,
|
|
131
|
-
);
|
|
132
|
-
if (!isCached) return undefined; // Not cached - let main video handle it
|
|
133
|
-
|
|
134
|
-
// Get cached scrub input and seek within it
|
|
135
|
-
const scrubInput = await scrubInputCache.getOrCreateInput(
|
|
136
|
-
segmentId,
|
|
137
|
-
async () => {
|
|
138
|
-
const [initSegment, mediaSegment] = await Promise.all([
|
|
139
|
-
mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),
|
|
140
|
-
mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc),
|
|
141
|
-
]);
|
|
142
|
-
|
|
143
|
-
if (!initSegment || !mediaSegment || signal.aborted) return undefined;
|
|
144
|
-
|
|
145
|
-
const { BufferedSeekingInput } = await import(
|
|
146
|
-
"../BufferedSeekingInput.js"
|
|
144
|
+
// Check if scrub segment is cached
|
|
145
|
+
const segmentId = mediaEngine.computeSegmentId(
|
|
146
|
+
desiredSeekTimeMs,
|
|
147
|
+
scrubRenditionWithSrc,
|
|
147
148
|
);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
if (segmentId === undefined) {
|
|
150
|
+
span.setAttribute("result", "no-segment-id");
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const isCached = mediaEngine.isSegmentCached(
|
|
155
|
+
segmentId,
|
|
156
|
+
scrubRenditionWithSrc,
|
|
157
|
+
);
|
|
158
|
+
span.setAttribute("isCached", isCached);
|
|
159
|
+
if (!isCached) {
|
|
160
|
+
span.setAttribute("result", "not-cached");
|
|
161
|
+
return undefined; // Not cached - let main video handle it
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Get cached scrub input and seek within it
|
|
165
|
+
const scrubInput = await scrubInputCache.getOrCreateInput(
|
|
166
|
+
segmentId,
|
|
167
|
+
async () => {
|
|
168
|
+
const [initSegment, mediaSegment] = await Promise.all([
|
|
169
|
+
mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),
|
|
170
|
+
mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc),
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
if (!initSegment || !mediaSegment || signal.aborted)
|
|
174
|
+
return undefined;
|
|
175
|
+
|
|
176
|
+
const { BufferedSeekingInput } = await import(
|
|
177
|
+
"../BufferedSeekingInput.js"
|
|
178
|
+
);
|
|
179
|
+
const { EFMedia } = await import("../../EFMedia.js");
|
|
180
|
+
|
|
181
|
+
return new BufferedSeekingInput(
|
|
182
|
+
await new Blob([initSegment, mediaSegment]).arrayBuffer(),
|
|
183
|
+
{
|
|
184
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
185
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
186
|
+
startTimeOffsetMs: scrubRendition.startTimeOffsetMs,
|
|
187
|
+
},
|
|
188
|
+
);
|
|
156
189
|
},
|
|
157
190
|
);
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
191
|
|
|
161
|
-
|
|
192
|
+
if (!scrubInput) {
|
|
193
|
+
span.setAttribute("result", "no-scrub-input");
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
162
196
|
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
if (signal.aborted) {
|
|
198
|
+
span.setAttribute("result", "aborted-after-scrub-input");
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
165
201
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
const videoTrack = await scrubInput.getFirstVideoTrack();
|
|
203
|
+
if (!videoTrack) {
|
|
204
|
+
span.setAttribute("result", "no-video-track");
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
170
207
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
208
|
+
if (signal.aborted) {
|
|
209
|
+
span.setAttribute("result", "aborted-after-scrub-track");
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const sample = (await scrubInput.seek(
|
|
214
|
+
videoTrack.id,
|
|
215
|
+
desiredSeekTimeMs,
|
|
216
|
+
)) as unknown as VideoSample | undefined;
|
|
217
|
+
|
|
218
|
+
span.setAttribute("result", sample ? "success" : "no-sample");
|
|
219
|
+
return sample;
|
|
220
|
+
} catch (_error) {
|
|
221
|
+
if (signal.aborted) {
|
|
222
|
+
span.setAttribute("result", "aborted");
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
span.setAttribute("result", "error");
|
|
226
|
+
return undefined; // Scrub failed - let main video handle it
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
);
|
|
176
230
|
}
|
|
177
231
|
|
|
178
232
|
/**
|
|
@@ -184,58 +238,101 @@ async function getMainVideoSample(
|
|
|
184
238
|
desiredSeekTimeMs: number,
|
|
185
239
|
signal: AbortSignal,
|
|
186
240
|
): Promise<VideoSample | undefined> {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
241
|
+
return withSpan(
|
|
242
|
+
"video.getMainVideoSample",
|
|
243
|
+
{
|
|
190
244
|
desiredSeekTimeMs,
|
|
191
|
-
mediaEngine.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
]);
|
|
204
|
-
|
|
205
|
-
if (!initSegment || !mediaSegment) return undefined;
|
|
206
|
-
signal.throwIfAborted();
|
|
245
|
+
src: mediaEngine.src || "unknown",
|
|
246
|
+
},
|
|
247
|
+
undefined,
|
|
248
|
+
async (span) => {
|
|
249
|
+
try {
|
|
250
|
+
// Use existing main video task chain
|
|
251
|
+
const videoRendition = mediaEngine.getVideoRendition();
|
|
252
|
+
if (!videoRendition) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Video rendition unavailable after checking videoRendition exists",
|
|
255
|
+
);
|
|
256
|
+
}
|
|
207
257
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
const segmentId = mediaEngine.computeSegmentId(
|
|
259
|
+
desiredSeekTimeMs,
|
|
260
|
+
videoRendition,
|
|
261
|
+
);
|
|
262
|
+
if (segmentId === undefined) {
|
|
263
|
+
span.setAttribute("result", "no-segment-id");
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
211
266
|
|
|
212
|
-
|
|
213
|
-
|
|
267
|
+
span.setAttribute("segmentId", segmentId);
|
|
268
|
+
|
|
269
|
+
// Get cached main video input or create new one
|
|
270
|
+
const mainInput = await mainVideoInputCache.getOrCreateInput(
|
|
271
|
+
mediaEngine.src,
|
|
272
|
+
segmentId,
|
|
273
|
+
videoRendition.id,
|
|
274
|
+
async () => {
|
|
275
|
+
// Fetch main video segment (will be cached at mediaEngine level)
|
|
276
|
+
const [initSegment, mediaSegment] = await Promise.all([
|
|
277
|
+
mediaEngine.fetchInitSegment(videoRendition, signal),
|
|
278
|
+
mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal),
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
if (!initSegment || !mediaSegment) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
signal.throwIfAborted();
|
|
285
|
+
|
|
286
|
+
const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
|
|
287
|
+
|
|
288
|
+
return new BufferedSeekingInput(
|
|
289
|
+
await new Blob([initSegment, mediaSegment]).arrayBuffer(),
|
|
290
|
+
{
|
|
291
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
292
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
293
|
+
startTimeOffsetMs,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
},
|
|
297
|
+
);
|
|
214
298
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
220
|
-
startTimeOffsetMs,
|
|
221
|
-
},
|
|
222
|
-
);
|
|
299
|
+
if (!mainInput) {
|
|
300
|
+
span.setAttribute("result", "no-segments");
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
223
303
|
|
|
224
|
-
|
|
225
|
-
|
|
304
|
+
if (signal.aborted) {
|
|
305
|
+
span.setAttribute("result", "aborted-after-input");
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
226
308
|
|
|
227
|
-
|
|
309
|
+
const videoTrack = await mainInput.getFirstVideoTrack();
|
|
310
|
+
if (!videoTrack) {
|
|
311
|
+
span.setAttribute("result", "no-video-track");
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
228
314
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
315
|
+
if (signal.aborted) {
|
|
316
|
+
span.setAttribute("result", "aborted-after-track");
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
233
319
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
320
|
+
const sample = (await mainInput.seek(
|
|
321
|
+
videoTrack.id,
|
|
322
|
+
desiredSeekTimeMs,
|
|
323
|
+
)) as unknown as VideoSample | undefined;
|
|
324
|
+
|
|
325
|
+
span.setAttribute("result", sample ? "success" : "no-sample");
|
|
326
|
+
return sample;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if (signal.aborted) {
|
|
329
|
+
span.setAttribute("result", "aborted");
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
);
|
|
239
336
|
}
|
|
240
337
|
|
|
241
338
|
/**
|
|
@@ -9,10 +9,7 @@ import {
|
|
|
9
9
|
type MediaBufferState,
|
|
10
10
|
manageMediaBuffer,
|
|
11
11
|
} from "../shared/BufferUtils";
|
|
12
|
-
import {
|
|
13
|
-
getLatestMediaEngine,
|
|
14
|
-
getVideoRendition,
|
|
15
|
-
} from "../tasks/makeMediaEngineTask";
|
|
12
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
|
|
16
13
|
|
|
17
14
|
/**
|
|
18
15
|
* Configuration for video buffering - extends the generic interface
|
|
@@ -90,7 +87,7 @@ export const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {
|
|
|
90
87
|
getRendition: async () => {
|
|
91
88
|
// Get real video rendition from media engine
|
|
92
89
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
93
|
-
return getVideoRendition(
|
|
90
|
+
return mediaEngine.getVideoRendition();
|
|
94
91
|
},
|
|
95
92
|
logError: console.error,
|
|
96
93
|
},
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { css, LitElement, type PropertyValueMap } from "lit";
|
|
2
2
|
import { property, state } from "lit/decorators.js";
|
|
3
3
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
4
|
+
import { withSpan } from "../otel/tracingHelpers.js";
|
|
4
5
|
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
5
6
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
6
7
|
import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
|
|
@@ -283,12 +284,27 @@ export class EFMedia extends EFTargetable(
|
|
|
283
284
|
/**
|
|
284
285
|
* Main integration method for EFTimegroup audio playback
|
|
285
286
|
* Now powered by clean, testable utility functions
|
|
287
|
+
* Returns undefined if no audio rendition is available
|
|
286
288
|
*/
|
|
287
289
|
async fetchAudioSpanningTime(
|
|
288
290
|
fromMs: number,
|
|
289
291
|
toMs: number,
|
|
290
292
|
signal: AbortSignal = new AbortController().signal,
|
|
291
|
-
): Promise<AudioSpan> {
|
|
292
|
-
return
|
|
293
|
+
): Promise<AudioSpan | undefined> {
|
|
294
|
+
return withSpan(
|
|
295
|
+
"media.fetchAudioSpanningTime",
|
|
296
|
+
{
|
|
297
|
+
elementId: this.id || "unknown",
|
|
298
|
+
tagName: this.tagName.toLowerCase(),
|
|
299
|
+
fromMs,
|
|
300
|
+
toMs,
|
|
301
|
+
durationMs: toMs - fromMs,
|
|
302
|
+
src: this.src || "none",
|
|
303
|
+
},
|
|
304
|
+
undefined,
|
|
305
|
+
async () => {
|
|
306
|
+
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
307
|
+
},
|
|
308
|
+
);
|
|
293
309
|
}
|
|
294
310
|
}
|
|
@@ -132,7 +132,8 @@ describe("MediaEngine Thumbnail Extraction", () => {
|
|
|
132
132
|
// Get segment duration to ensure timestamps are in same segment
|
|
133
133
|
const videoRendition =
|
|
134
134
|
mediaEngine.getScrubVideoRendition() || mediaEngine.getVideoRendition();
|
|
135
|
-
|
|
135
|
+
expect(videoRendition).toBeDefined();
|
|
136
|
+
const segmentDurationMs = videoRendition!.segmentDurationMs || 2000;
|
|
136
137
|
|
|
137
138
|
// Pick timestamps within the first segment - avoid edge cases near boundaries
|
|
138
139
|
const timestamps = [
|
|
@@ -616,23 +616,25 @@ describe("Dynamic content updates", () => {
|
|
|
616
616
|
const frameTaskB = timegroup.querySelector("test-frame-task-b")!;
|
|
617
617
|
const frameTaskC = timegroup.querySelector("test-frame-task-c")!;
|
|
618
618
|
|
|
619
|
-
//
|
|
619
|
+
// Following the initial update, frame tasks may run during initialization
|
|
620
620
|
await timegroup.updateComplete;
|
|
621
621
|
|
|
622
|
-
|
|
622
|
+
// frameTaskB should never run (not visible at time 0ms in sequence)
|
|
623
623
|
assert.equal(frameTaskB.frameTaskCount, 0);
|
|
624
|
-
assert.equal(frameTaskC.frameTaskCount, 1);
|
|
625
624
|
|
|
626
625
|
// Then we run them manually.
|
|
627
626
|
await timegroup.frameTask.run();
|
|
628
627
|
|
|
629
628
|
// At timeline time 0ms:
|
|
630
|
-
// - frameTaskA (0-1000ms) should run
|
|
631
|
-
// - frameTaskB (1000-2000ms) should NOT run
|
|
632
|
-
// - frameTaskC (0-1000ms) should run (inherits root positioning)
|
|
633
|
-
|
|
629
|
+
// - frameTaskA (0-1000ms) should have run (visible)
|
|
630
|
+
// - frameTaskB (1000-2000ms) should NOT run (not visible at time 0)
|
|
631
|
+
// - frameTaskC (0-1000ms) should have run (inherits root positioning, visible)
|
|
632
|
+
|
|
633
|
+
// Verify visible tasks have run at least once
|
|
634
|
+
assert.ok(frameTaskA.frameTaskCount > 0, "frameTaskA should have run");
|
|
635
|
+
assert.ok(frameTaskC.frameTaskCount > 0, "frameTaskC should have run");
|
|
636
|
+
// Verify non-visible task has never run
|
|
634
637
|
assert.equal(frameTaskB.frameTaskCount, 0); // Not visible at time 0
|
|
635
|
-
assert.equal(frameTaskC.frameTaskCount, 2); // Nested in B but inherits root positioning
|
|
636
638
|
});
|
|
637
639
|
});
|
|
638
640
|
|