@editframe/elements 0.20.4-beta.0 → 0.23.6-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 +49 -11
- package/dist/_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js +7 -0
- package/dist/attachContextRoot.d.ts +1 -0
- package/dist/attachContextRoot.js +9 -0
- package/dist/elements/ContextProxiesController.d.ts +1 -2
- package/dist/elements/EFAudio.js +5 -9
- package/dist/elements/EFCaptions.d.ts +1 -3
- package/dist/elements/EFCaptions.js +112 -129
- package/dist/elements/EFImage.js +6 -7
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -5
- package/dist/elements/EFMedia/AssetMediaEngine.js +36 -33
- package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -78
- package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +7 -13
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +9 -25
- package/dist/elements/EFMedia/shared/BufferUtils.js +2 -17
- package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
- 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 +1 -7
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -18
- package/dist/elements/EFMedia.d.ts +19 -0
- package/dist/elements/EFMedia.js +44 -25
- package/dist/elements/EFSourceMixin.js +5 -7
- package/dist/elements/EFSurface.js +6 -9
- package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
- package/dist/elements/EFTemporal.d.ts +10 -0
- package/dist/elements/EFTemporal.js +100 -41
- package/dist/elements/EFThumbnailStrip.js +23 -73
- package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
- package/dist/elements/EFTimegroup.d.ts +35 -14
- package/dist/elements/EFTimegroup.js +138 -181
- package/dist/elements/EFVideo.d.ts +16 -2
- package/dist/elements/EFVideo.js +156 -108
- package/dist/elements/EFWaveform.js +23 -40
- package/dist/elements/SampleBuffer.js +3 -7
- package/dist/elements/TargetController.js +5 -5
- package/dist/elements/durationConverter.js +4 -4
- package/dist/elements/renderTemporalAudio.d.ts +10 -0
- package/dist/elements/renderTemporalAudio.js +35 -0
- package/dist/elements/updateAnimations.js +19 -43
- package/dist/gui/ContextMixin.d.ts +5 -5
- package/dist/gui/ContextMixin.js +167 -162
- package/dist/gui/Controllable.browsertest.d.ts +0 -0
- package/dist/gui/Controllable.d.ts +15 -0
- package/dist/gui/Controllable.js +9 -0
- package/dist/gui/EFConfiguration.js +7 -7
- package/dist/gui/EFControls.browsertest.d.ts +11 -0
- package/dist/gui/EFControls.d.ts +18 -4
- package/dist/gui/EFControls.js +70 -28
- package/dist/gui/EFDial.browsertest.d.ts +0 -0
- package/dist/gui/EFDial.d.ts +18 -0
- package/dist/gui/EFDial.js +141 -0
- package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
- package/dist/gui/EFFilmstrip.d.ts +12 -2
- package/dist/gui/EFFilmstrip.js +214 -129
- package/dist/gui/EFFitScale.js +5 -8
- package/dist/gui/EFFocusOverlay.js +4 -4
- package/dist/gui/EFPause.browsertest.d.ts +0 -0
- package/dist/gui/EFPause.d.ts +23 -0
- package/dist/gui/EFPause.js +59 -0
- package/dist/gui/EFPlay.browsertest.d.ts +0 -0
- package/dist/gui/EFPlay.d.ts +23 -0
- package/dist/gui/EFPlay.js +59 -0
- package/dist/gui/EFPreview.d.ts +4 -0
- package/dist/gui/EFPreview.js +18 -9
- package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
- package/dist/gui/EFResizableBox.d.ts +34 -0
- package/dist/gui/EFResizableBox.js +547 -0
- package/dist/gui/EFScrubber.d.ts +9 -3
- package/dist/gui/EFScrubber.js +13 -13
- package/dist/gui/EFTimeDisplay.d.ts +7 -1
- package/dist/gui/EFTimeDisplay.js +8 -8
- package/dist/gui/EFToggleLoop.d.ts +9 -3
- package/dist/gui/EFToggleLoop.js +7 -5
- package/dist/gui/EFTogglePlay.d.ts +12 -4
- package/dist/gui/EFTogglePlay.js +26 -21
- package/dist/gui/EFWorkbench.js +5 -5
- package/dist/gui/PlaybackController.d.ts +67 -0
- package/dist/gui/PlaybackController.js +310 -0
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin2.js +1 -1
- package/dist/gui/TargetOrContextMixin.d.ts +10 -0
- package/dist/gui/TargetOrContextMixin.js +98 -0
- package/dist/gui/efContext.d.ts +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -1
- 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 +32 -0
- package/dist/otel/tracingHelpers.d.ts +34 -0
- package/dist/otel/tracingHelpers.js +112 -0
- package/dist/style.css +1 -1
- package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
- package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
- package/dist/transcoding/utils/UrlGenerator.js +2 -19
- package/dist/utils/LRUCache.js +6 -53
- package/package.json +13 -5
- package/src/elements/ContextProxiesController.ts +10 -10
- package/src/elements/EFAudio.ts +1 -0
- package/src/elements/EFCaptions.browsertest.ts +128 -56
- package/src/elements/EFCaptions.ts +60 -34
- package/src/elements/EFImage.browsertest.ts +1 -2
- package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
- package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
- package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
- package/src/elements/EFMedia.browsertest.ts +8 -15
- package/src/elements/EFMedia.ts +54 -8
- package/src/elements/EFSurface.browsertest.ts +2 -6
- package/src/elements/EFSurface.ts +1 -0
- package/src/elements/EFTemporal.browsertest.ts +58 -1
- package/src/elements/EFTemporal.ts +140 -4
- package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
- package/src/elements/EFThumbnailStrip.ts +1 -0
- package/src/elements/EFTimegroup.browsertest.ts +16 -15
- package/src/elements/EFTimegroup.ts +281 -275
- package/src/elements/EFVideo.browsertest.ts +162 -74
- package/src/elements/EFVideo.ts +229 -101
- package/src/elements/FetchContext.browsertest.ts +7 -2
- package/src/elements/TargetController.browsertest.ts +1 -0
- package/src/elements/TargetController.ts +1 -0
- package/src/elements/renderTemporalAudio.ts +108 -0
- package/src/elements/updateAnimations.browsertest.ts +181 -6
- package/src/elements/updateAnimations.ts +6 -6
- package/src/gui/ContextMixin.browsertest.ts +274 -27
- package/src/gui/ContextMixin.ts +230 -175
- package/src/gui/Controllable.browsertest.ts +258 -0
- package/src/gui/Controllable.ts +41 -0
- package/src/gui/EFControls.browsertest.ts +294 -80
- package/src/gui/EFControls.ts +139 -28
- package/src/gui/EFDial.browsertest.ts +84 -0
- package/src/gui/EFDial.ts +172 -0
- package/src/gui/EFFilmstrip.browsertest.ts +712 -0
- package/src/gui/EFFilmstrip.ts +213 -23
- package/src/gui/EFPause.browsertest.ts +202 -0
- package/src/gui/EFPause.ts +73 -0
- package/src/gui/EFPlay.browsertest.ts +202 -0
- package/src/gui/EFPlay.ts +73 -0
- package/src/gui/EFPreview.ts +20 -5
- package/src/gui/EFResizableBox.browsertest.ts +79 -0
- package/src/gui/EFResizableBox.ts +898 -0
- package/src/gui/EFScrubber.ts +7 -5
- package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
- package/src/gui/EFTimeDisplay.ts +3 -1
- package/src/gui/EFToggleLoop.ts +6 -5
- package/src/gui/EFTogglePlay.ts +30 -23
- package/src/gui/PlaybackController.ts +522 -0
- package/src/gui/TWMixin.css +3 -0
- package/src/gui/TargetOrContextMixin.ts +185 -0
- package/src/gui/efContext.ts +2 -2
- package/src/otel/BridgeSpanExporter.ts +150 -0
- package/src/otel/setupBrowserTracing.ts +73 -0
- package/src/otel/tracingHelpers.ts +251 -0
- package/test/cache-integration-verification.browsertest.ts +1 -1
- package/types.json +1 -1
- package/dist/elements/ContextProxiesController.js +0 -69
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
2
|
import type { VideoSample } from "mediabunny";
|
|
3
|
+
import { withSpan } from "../../../otel/tracingHelpers.js";
|
|
3
4
|
import type { VideoRendition } from "../../../transcoding/types";
|
|
5
|
+
import { EFMedia } from "../../EFMedia.js";
|
|
4
6
|
import type { EFVideo } from "../../EFVideo";
|
|
7
|
+
import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
|
|
5
8
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
|
|
9
|
+
import { MainVideoInputCache } from "./MainVideoInputCache";
|
|
6
10
|
import { ScrubInputCache } from "./ScrubInputCache";
|
|
7
11
|
|
|
8
12
|
type UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;
|
|
@@ -10,6 +14,9 @@ type UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;
|
|
|
10
14
|
// Shared cache for scrub inputs
|
|
11
15
|
const scrubInputCache = new ScrubInputCache();
|
|
12
16
|
|
|
17
|
+
// Shared cache for main video inputs
|
|
18
|
+
const mainVideoInputCache = new MainVideoInputCache();
|
|
19
|
+
|
|
13
20
|
export const makeUnifiedVideoSeekTask = (
|
|
14
21
|
host: EFVideo,
|
|
15
22
|
): UnifiedVideoSeekTask => {
|
|
@@ -22,7 +29,7 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
22
29
|
onComplete: (_value) => {},
|
|
23
30
|
task: async ([desiredSeekTimeMs], { signal }) => {
|
|
24
31
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
25
|
-
if (!mediaEngine) return undefined;
|
|
32
|
+
if (!mediaEngine || signal.aborted) return undefined;
|
|
26
33
|
|
|
27
34
|
// FIRST: Check if main quality content is already cached
|
|
28
35
|
const mainRendition = mediaEngine.videoRendition;
|
|
@@ -35,13 +42,18 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
35
42
|
mainSegmentId !== undefined &&
|
|
36
43
|
mediaEngine.isSegmentCached(mainSegmentId, mainRendition)
|
|
37
44
|
) {
|
|
38
|
-
|
|
39
|
-
return await getMainVideoSample(
|
|
45
|
+
const result = await getMainVideoSample(
|
|
40
46
|
host,
|
|
41
47
|
mediaEngine,
|
|
42
48
|
desiredSeekTimeMs,
|
|
43
49
|
signal,
|
|
44
50
|
);
|
|
51
|
+
|
|
52
|
+
if (signal.aborted) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
45
57
|
}
|
|
46
58
|
}
|
|
47
59
|
|
|
@@ -51,7 +63,12 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
51
63
|
desiredSeekTimeMs,
|
|
52
64
|
signal,
|
|
53
65
|
);
|
|
66
|
+
|
|
54
67
|
if (scrubSample || signal.aborted) {
|
|
68
|
+
if (signal.aborted) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
// If scrub succeeded, start background main quality upgrade (non-blocking)
|
|
56
73
|
if (scrubSample) {
|
|
57
74
|
startMainQualityUpgrade(
|
|
@@ -68,12 +85,18 @@ export const makeUnifiedVideoSeekTask = (
|
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
// THIRD: Neither are cached, fetch main video path as final fallback
|
|
71
|
-
|
|
88
|
+
const result = await getMainVideoSample(
|
|
72
89
|
host,
|
|
73
90
|
mediaEngine,
|
|
74
91
|
desiredSeekTimeMs,
|
|
75
92
|
signal,
|
|
76
93
|
);
|
|
94
|
+
|
|
95
|
+
if (signal.aborted) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
77
100
|
},
|
|
78
101
|
});
|
|
79
102
|
};
|
|
@@ -86,82 +109,124 @@ async function tryGetScrubSample(
|
|
|
86
109
|
desiredSeekTimeMs: number,
|
|
87
110
|
signal: AbortSignal,
|
|
88
111
|
): Promise<VideoSample | undefined> {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
|
102
133
|
|
|
103
|
-
|
|
134
|
+
if (!scrubRendition) {
|
|
135
|
+
span.setAttribute("result", "no-scrub-rendition");
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
104
138
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
139
|
+
const scrubRenditionWithSrc = {
|
|
140
|
+
...scrubRendition,
|
|
141
|
+
src: mediaEngine.src,
|
|
142
|
+
};
|
|
109
143
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Get cached scrub input and seek within it
|
|
124
|
-
const scrubInput = await scrubInputCache.getOrCreateInput(
|
|
125
|
-
segmentId,
|
|
126
|
-
async () => {
|
|
127
|
-
const [initSegment, mediaSegment] = await Promise.all([
|
|
128
|
-
mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),
|
|
129
|
-
mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc),
|
|
130
|
-
]);
|
|
131
|
-
|
|
132
|
-
if (!initSegment || !mediaSegment || signal.aborted) return undefined;
|
|
133
|
-
|
|
134
|
-
const { BufferedSeekingInput } = await import(
|
|
135
|
-
"../BufferedSeekingInput.js"
|
|
144
|
+
// Check if scrub segment is cached
|
|
145
|
+
const segmentId = mediaEngine.computeSegmentId(
|
|
146
|
+
desiredSeekTimeMs,
|
|
147
|
+
scrubRenditionWithSrc,
|
|
148
|
+
);
|
|
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,
|
|
136
157
|
);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
);
|
|
145
189
|
},
|
|
146
190
|
);
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
191
|
|
|
150
|
-
|
|
192
|
+
if (!scrubInput) {
|
|
193
|
+
span.setAttribute("result", "no-scrub-input");
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
151
196
|
|
|
152
|
-
|
|
153
|
-
|
|
197
|
+
if (signal.aborted) {
|
|
198
|
+
span.setAttribute("result", "aborted-after-scrub-input");
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
154
201
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
202
|
+
const videoTrack = await scrubInput.getFirstVideoTrack();
|
|
203
|
+
if (!videoTrack) {
|
|
204
|
+
span.setAttribute("result", "no-video-track");
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
159
207
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
);
|
|
165
230
|
}
|
|
166
231
|
|
|
167
232
|
/**
|
|
@@ -173,60 +238,101 @@ async function getMainVideoSample(
|
|
|
173
238
|
desiredSeekTimeMs: number,
|
|
174
239
|
signal: AbortSignal,
|
|
175
240
|
): Promise<VideoSample | undefined> {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (!videoRendition) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
"Video rendition unavailable after checking videoRendition exists",
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const segmentId = mediaEngine.computeSegmentId(
|
|
241
|
+
return withSpan(
|
|
242
|
+
"video.getMainVideoSample",
|
|
243
|
+
{
|
|
186
244
|
desiredSeekTimeMs,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
}
|
|
199
257
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
}
|
|
203
266
|
|
|
204
|
-
|
|
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
|
+
);
|
|
205
298
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
211
|
-
startTimeOffsetMs,
|
|
212
|
-
},
|
|
213
|
-
);
|
|
299
|
+
if (!mainInput) {
|
|
300
|
+
span.setAttribute("result", "no-segments");
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
214
303
|
|
|
215
|
-
|
|
216
|
-
|
|
304
|
+
if (signal.aborted) {
|
|
305
|
+
span.setAttribute("result", "aborted-after-input");
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
217
308
|
|
|
218
|
-
|
|
309
|
+
const videoTrack = await mainInput.getFirstVideoTrack();
|
|
310
|
+
if (!videoTrack) {
|
|
311
|
+
span.setAttribute("result", "no-video-track");
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
219
314
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
315
|
+
if (signal.aborted) {
|
|
316
|
+
span.setAttribute("result", "aborted-after-track");
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
224
319
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
);
|
|
230
336
|
}
|
|
231
337
|
|
|
232
338
|
/**
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { css } from "lit";
|
|
2
2
|
import { customElement } from "lit/decorators.js";
|
|
3
|
-
import type { VideoSample } from "mediabunny";
|
|
4
3
|
import { afterEach, beforeEach, describe, vi } from "vitest";
|
|
5
4
|
import { test as baseTest } from "../../test/useMSW.js";
|
|
6
5
|
|
|
@@ -106,9 +105,8 @@ describe("JIT Media Engine", () => {
|
|
|
106
105
|
jitVideo,
|
|
107
106
|
expect,
|
|
108
107
|
}) => {
|
|
109
|
-
timegroup.
|
|
110
|
-
|
|
111
|
-
const sample = await jitVideo.unifiedVideoSeekTask.taskComplete;
|
|
108
|
+
await timegroup.seek(2200);
|
|
109
|
+
const sample = jitVideo.unifiedVideoSeekTask.value;
|
|
112
110
|
expect(sample?.timestamp).toBeCloseTo(2.2, 1);
|
|
113
111
|
});
|
|
114
112
|
});
|
|
@@ -148,9 +146,8 @@ describe("JIT Media Engine", () => {
|
|
|
148
146
|
expect,
|
|
149
147
|
}) => {
|
|
150
148
|
await timegroup.waitForMediaDurations();
|
|
151
|
-
timegroup.
|
|
152
|
-
|
|
153
|
-
const frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
|
|
149
|
+
await timegroup.seek(3000);
|
|
150
|
+
const frame = jitVideo.unifiedVideoSeekTask.value;
|
|
154
151
|
expect(frame?.timestamp).toBeCloseTo(3, 1);
|
|
155
152
|
});
|
|
156
153
|
|
|
@@ -160,9 +157,8 @@ describe("JIT Media Engine", () => {
|
|
|
160
157
|
expect,
|
|
161
158
|
}) => {
|
|
162
159
|
await timegroup.waitForMediaDurations();
|
|
163
|
-
timegroup.
|
|
164
|
-
|
|
165
|
-
const frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
|
|
160
|
+
await timegroup.seek(5000);
|
|
161
|
+
const frame = jitVideo.unifiedVideoSeekTask.value;
|
|
166
162
|
expect(frame?.timestamp).toBeCloseTo(5, 1);
|
|
167
163
|
});
|
|
168
164
|
|
|
@@ -172,17 +168,14 @@ describe("JIT Media Engine", () => {
|
|
|
172
168
|
expect,
|
|
173
169
|
}) => {
|
|
174
170
|
await timegroup.waitForMediaDurations();
|
|
175
|
-
timegroup.currentTimeMs = 0;
|
|
176
171
|
|
|
177
172
|
// Test seeking in larger increments to avoid CI timeouts
|
|
178
173
|
// while still validating incremental seeking works
|
|
179
174
|
const testPoints = [0, 500, 1000, 1500, 2000, 2500, 3000];
|
|
180
|
-
let frame: VideoSample | undefined;
|
|
181
175
|
|
|
182
176
|
for (const timeMs of testPoints) {
|
|
183
|
-
timegroup.
|
|
184
|
-
|
|
185
|
-
frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
|
|
177
|
+
await timegroup.seek(timeMs);
|
|
178
|
+
const frame = jitVideo.unifiedVideoSeekTask.value;
|
|
186
179
|
expect(frame).toBeDefined();
|
|
187
180
|
expect(frame?.timestamp).toBeCloseTo(timeMs / 1000, 1);
|
|
188
181
|
}
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { provide } from "@lit/context";
|
|
1
2
|
import { css, LitElement, type PropertyValueMap } from "lit";
|
|
2
3
|
import { property, state } from "lit/decorators.js";
|
|
3
4
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
5
|
+
import type { ControllableInterface } from "../gui/Controllable.js";
|
|
6
|
+
import { efContext } from "../gui/efContext.js";
|
|
7
|
+
import { withSpan } from "../otel/tracingHelpers.js";
|
|
4
8
|
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
5
9
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
6
10
|
import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
|
|
@@ -16,6 +20,7 @@ import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.ts";
|
|
|
16
20
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
17
21
|
import { EFTemporal } from "./EFTemporal.js";
|
|
18
22
|
import { FetchMixin } from "./FetchMixin.js";
|
|
23
|
+
import { renderTemporalAudio } from "./renderTemporalAudio.js";
|
|
19
24
|
import { EFTargetable } from "./TargetController.ts";
|
|
20
25
|
|
|
21
26
|
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
@@ -46,6 +51,11 @@ export class EFMedia extends EFTargetable(
|
|
|
46
51
|
assetType: "isobmff_files",
|
|
47
52
|
}),
|
|
48
53
|
) {
|
|
54
|
+
@provide({ context: efContext })
|
|
55
|
+
get efContext(): ControllableInterface | null {
|
|
56
|
+
return this.rootTimegroup ?? this;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
// Sample buffer size configuration
|
|
50
60
|
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
51
61
|
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
@@ -248,13 +258,6 @@ export class EFMedia extends EFTargetable(
|
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
|
-
|
|
252
|
-
// if (
|
|
253
|
-
// changedProperties.has("currentTime") ||
|
|
254
|
-
// changedProperties.has("ownCurrentTimeMs")
|
|
255
|
-
// ) {
|
|
256
|
-
// updateAnimations(this);
|
|
257
|
-
// }
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
get hasOwnDuration() {
|
|
@@ -290,6 +293,49 @@ export class EFMedia extends EFTargetable(
|
|
|
290
293
|
toMs: number,
|
|
291
294
|
signal: AbortSignal = new AbortController().signal,
|
|
292
295
|
): Promise<AudioSpan | undefined> {
|
|
293
|
-
return
|
|
296
|
+
return withSpan(
|
|
297
|
+
"media.fetchAudioSpanningTime",
|
|
298
|
+
{
|
|
299
|
+
elementId: this.id || "unknown",
|
|
300
|
+
tagName: this.tagName.toLowerCase(),
|
|
301
|
+
fromMs,
|
|
302
|
+
toMs,
|
|
303
|
+
durationMs: toMs - fromMs,
|
|
304
|
+
src: this.src || "none",
|
|
305
|
+
},
|
|
306
|
+
undefined,
|
|
307
|
+
async () => {
|
|
308
|
+
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Wait for media engine to load and determine duration
|
|
315
|
+
* Ensures media is ready for playback
|
|
316
|
+
*/
|
|
317
|
+
async waitForMediaDurations(): Promise<void> {
|
|
318
|
+
if (this.mediaEngineTask.value) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
await this.mediaEngineTask.run();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Returns media elements for playback audio rendering
|
|
326
|
+
* For standalone media, returns [this]; for timegroups, returns all descendants
|
|
327
|
+
* Used by PlaybackController for audio-driven playback
|
|
328
|
+
*/
|
|
329
|
+
getMediaElements(): EFMedia[] {
|
|
330
|
+
return [this];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Render audio buffer for playback
|
|
335
|
+
* Called by PlaybackController during live playback
|
|
336
|
+
* Delegates to shared renderTemporalAudio utility for consistent behavior
|
|
337
|
+
*/
|
|
338
|
+
async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {
|
|
339
|
+
return renderTemporalAudio(this, fromMs, toMs);
|
|
294
340
|
}
|
|
295
341
|
}
|
|
@@ -26,7 +26,7 @@ const surfaceTest = baseTest.extend<{
|
|
|
26
26
|
const container = document.createElement("div");
|
|
27
27
|
render(
|
|
28
28
|
html`
|
|
29
|
-
<ef-configuration api-host="http://localhost:63315">
|
|
29
|
+
<ef-configuration api-host="http://localhost:63315" signing-url="">
|
|
30
30
|
<ef-preview>
|
|
31
31
|
<ef-timegroup id="tg" mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
32
32
|
<ef-video id="vid" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
@@ -38,8 +38,6 @@ const surfaceTest = baseTest.extend<{
|
|
|
38
38
|
container,
|
|
39
39
|
);
|
|
40
40
|
document.body.appendChild(container);
|
|
41
|
-
const configuration = container.querySelector("ef-configuration") as any;
|
|
42
|
-
configuration.signingURL = "";
|
|
43
41
|
const tg = container.querySelector("#tg") as EFTimegroup;
|
|
44
42
|
await tg.updateComplete;
|
|
45
43
|
await use(tg);
|
|
@@ -103,7 +101,7 @@ describe("EFSurface", () => {
|
|
|
103
101
|
const container = document.createElement("div");
|
|
104
102
|
render(
|
|
105
103
|
html`
|
|
106
|
-
<ef-configuration api-host="http://localhost:63315">
|
|
104
|
+
<ef-configuration api-host="http://localhost:63315" signing-url="">
|
|
107
105
|
<ef-preview>
|
|
108
106
|
<ef-timegroup mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
109
107
|
<ef-video id="v" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
@@ -116,8 +114,6 @@ describe("EFSurface", () => {
|
|
|
116
114
|
container,
|
|
117
115
|
);
|
|
118
116
|
document.body.appendChild(container);
|
|
119
|
-
const configuration = container.querySelector("ef-configuration") as any;
|
|
120
|
-
configuration.signingURL = "";
|
|
121
117
|
const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
|
|
122
118
|
const video = container.querySelector("ef-video") as EFVideo;
|
|
123
119
|
const s1 = container.querySelector("#s1") as unknown as EFSurface;
|
|
@@ -25,6 +25,7 @@ export class EFSurface extends LitElement {
|
|
|
25
25
|
canvasRef = createRef<HTMLCanvasElement>();
|
|
26
26
|
|
|
27
27
|
// @ts-expect-error controller is intentionally not referenced directly
|
|
28
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for side effects
|
|
28
29
|
#targetController: TargetController = new TargetController(this);
|
|
29
30
|
|
|
30
31
|
@state()
|