@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.
- package/dist/elements/EFTimegroup.js +7 -2
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/package.json +2 -2
- package/scripts/build-css.js +3 -3
- package/tsdown.config.ts +1 -1
- package/types.json +1 -1
- package/src/elements/ContextProxiesController.ts +0 -124
- package/src/elements/CrossUpdateController.ts +0 -22
- package/src/elements/EFAudio.browsertest.ts +0 -706
- package/src/elements/EFAudio.ts +0 -56
- package/src/elements/EFCaptions.browsertest.ts +0 -1960
- package/src/elements/EFCaptions.ts +0 -823
- package/src/elements/EFImage.browsertest.ts +0 -120
- package/src/elements/EFImage.ts +0 -113
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
- package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
- package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
- package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
- package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
- package/src/elements/EFMedia.browsertest.ts +0 -872
- package/src/elements/EFMedia.ts +0 -341
- package/src/elements/EFSourceMixin.ts +0 -60
- package/src/elements/EFSurface.browsertest.ts +0 -151
- package/src/elements/EFSurface.ts +0 -142
- package/src/elements/EFTemporal.browsertest.ts +0 -215
- package/src/elements/EFTemporal.ts +0 -800
- package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
- package/src/elements/EFThumbnailStrip.ts +0 -906
- package/src/elements/EFTimegroup.browsertest.ts +0 -870
- package/src/elements/EFTimegroup.ts +0 -878
- package/src/elements/EFVideo.browsertest.ts +0 -1482
- package/src/elements/EFVideo.ts +0 -564
- package/src/elements/EFWaveform.ts +0 -547
- package/src/elements/FetchContext.browsertest.ts +0 -401
- package/src/elements/FetchMixin.ts +0 -38
- package/src/elements/SampleBuffer.ts +0 -94
- package/src/elements/TargetController.browsertest.ts +0 -230
- package/src/elements/TargetController.ts +0 -224
- package/src/elements/TimegroupController.ts +0 -26
- package/src/elements/durationConverter.ts +0 -35
- package/src/elements/parseTimeToMs.ts +0 -9
- package/src/elements/printTaskStatus.ts +0 -16
- package/src/elements/renderTemporalAudio.ts +0 -108
- package/src/elements/updateAnimations.browsertest.ts +0 -1884
- package/src/elements/updateAnimations.ts +0 -217
- package/src/elements/util.ts +0 -24
- package/src/gui/ContextMixin.browsertest.ts +0 -860
- package/src/gui/ContextMixin.ts +0 -562
- package/src/gui/Controllable.browsertest.ts +0 -258
- package/src/gui/Controllable.ts +0 -41
- package/src/gui/EFConfiguration.ts +0 -40
- package/src/gui/EFControls.browsertest.ts +0 -389
- package/src/gui/EFControls.ts +0 -195
- package/src/gui/EFDial.browsertest.ts +0 -84
- package/src/gui/EFDial.ts +0 -172
- package/src/gui/EFFilmstrip.browsertest.ts +0 -712
- package/src/gui/EFFilmstrip.ts +0 -1349
- package/src/gui/EFFitScale.ts +0 -152
- package/src/gui/EFFocusOverlay.ts +0 -79
- package/src/gui/EFPause.browsertest.ts +0 -202
- package/src/gui/EFPause.ts +0 -73
- package/src/gui/EFPlay.browsertest.ts +0 -202
- package/src/gui/EFPlay.ts +0 -73
- package/src/gui/EFPreview.ts +0 -74
- package/src/gui/EFResizableBox.browsertest.ts +0 -79
- package/src/gui/EFResizableBox.ts +0 -898
- package/src/gui/EFScrubber.ts +0 -151
- package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
- package/src/gui/EFTimeDisplay.ts +0 -55
- package/src/gui/EFToggleLoop.ts +0 -35
- package/src/gui/EFTogglePlay.ts +0 -70
- package/src/gui/EFWorkbench.ts +0 -115
- package/src/gui/PlaybackController.ts +0 -527
- package/src/gui/TWMixin.css +0 -6
- package/src/gui/TWMixin.ts +0 -61
- package/src/gui/TargetOrContextMixin.ts +0 -185
- package/src/gui/currentTimeContext.ts +0 -5
- package/src/gui/durationContext.ts +0 -3
- package/src/gui/efContext.ts +0 -6
- package/src/gui/fetchContext.ts +0 -5
- package/src/gui/focusContext.ts +0 -7
- package/src/gui/focusedElementContext.ts +0 -5
- package/src/gui/playingContext.ts +0 -5
- package/src/otel/BridgeSpanExporter.ts +0 -150
- package/src/otel/setupBrowserTracing.ts +0 -73
- package/src/otel/tracingHelpers.ts +0 -251
- package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
- package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
- package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
- package/src/transcoding/types/index.ts +0 -312
- package/src/transcoding/utils/MediaUtils.ts +0 -63
- package/src/transcoding/utils/UrlGenerator.ts +0 -68
- package/src/transcoding/utils/constants.ts +0 -36
- package/src/utils/LRUCache.test.ts +0 -274
- package/src/utils/LRUCache.ts +0 -696
package/src/elements/EFVideo.ts
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
import { Task } from "@lit/task";
|
|
2
|
-
import { context, trace } from "@opentelemetry/api";
|
|
3
|
-
import debug from "debug";
|
|
4
|
-
import { css, html, type PropertyValueMap } from "lit";
|
|
5
|
-
import { customElement, property, state } from "lit/decorators.js";
|
|
6
|
-
import { createRef, ref } from "lit/directives/ref.js";
|
|
7
|
-
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
8
|
-
import { TWMixin } from "../gui/TWMixin.js";
|
|
9
|
-
import { withSpan, withSpanSync } from "../otel/tracingHelpers.js";
|
|
10
|
-
import { makeScrubVideoBufferTask } from "./EFMedia/videoTasks/makeScrubVideoBufferTask.ts";
|
|
11
|
-
import { makeScrubVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts";
|
|
12
|
-
import { makeScrubVideoInputTask } from "./EFMedia/videoTasks/makeScrubVideoInputTask.ts";
|
|
13
|
-
import { makeScrubVideoSeekTask } from "./EFMedia/videoTasks/makeScrubVideoSeekTask.ts";
|
|
14
|
-
import { makeScrubVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts";
|
|
15
|
-
import { makeScrubVideoSegmentIdTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts";
|
|
16
|
-
import { makeUnifiedVideoSeekTask } from "./EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts";
|
|
17
|
-
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
|
|
18
|
-
import { EFMedia } from "./EFMedia.js";
|
|
19
|
-
import { updateAnimations } from "./updateAnimations.js";
|
|
20
|
-
|
|
21
|
-
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
22
|
-
declare global {
|
|
23
|
-
var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const log = debug("ef:elements:EFVideo");
|
|
27
|
-
|
|
28
|
-
interface LoadingState {
|
|
29
|
-
isLoading: boolean;
|
|
30
|
-
operation: "scrub-segment" | "video-segment" | "seeking" | "decoding" | null;
|
|
31
|
-
message: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@customElement("ef-video")
|
|
35
|
-
export class EFVideo extends TWMixin(EFMedia) {
|
|
36
|
-
static styles = [
|
|
37
|
-
css`
|
|
38
|
-
:host {
|
|
39
|
-
display: block;
|
|
40
|
-
position: relative;
|
|
41
|
-
}
|
|
42
|
-
canvas {
|
|
43
|
-
overflow: hidden;
|
|
44
|
-
position: static;
|
|
45
|
-
width: 100%;
|
|
46
|
-
height: 100%;
|
|
47
|
-
margin: 0;
|
|
48
|
-
padding: 0;
|
|
49
|
-
overflow: hidden;
|
|
50
|
-
border: none;
|
|
51
|
-
outline: none;
|
|
52
|
-
box-shadow: none;
|
|
53
|
-
}
|
|
54
|
-
.loading-overlay {
|
|
55
|
-
position: absolute;
|
|
56
|
-
top: 0;
|
|
57
|
-
left: 0;
|
|
58
|
-
right: 0;
|
|
59
|
-
bottom: 0;
|
|
60
|
-
background: rgba(0, 0, 0, 0.6);
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
justify-content: center;
|
|
64
|
-
z-index: 10;
|
|
65
|
-
backdrop-filter: blur(2px);
|
|
66
|
-
}
|
|
67
|
-
.loading-content {
|
|
68
|
-
background: rgba(0, 0, 0, 0.8);
|
|
69
|
-
border-radius: 8px;
|
|
70
|
-
padding: 16px 24px;
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
gap: 12px;
|
|
74
|
-
color: white;
|
|
75
|
-
font-size: 14px;
|
|
76
|
-
font-weight: 500;
|
|
77
|
-
}
|
|
78
|
-
.loading-spinner {
|
|
79
|
-
width: 20px;
|
|
80
|
-
height: 20px;
|
|
81
|
-
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
82
|
-
border-left: 2px solid #fff;
|
|
83
|
-
border-radius: 50%;
|
|
84
|
-
animation: spin 1s linear infinite;
|
|
85
|
-
}
|
|
86
|
-
@keyframes spin {
|
|
87
|
-
0% { transform: rotate(0deg); }
|
|
88
|
-
100% { transform: rotate(360deg); }
|
|
89
|
-
}
|
|
90
|
-
.loading-message {
|
|
91
|
-
font-size: 12px;
|
|
92
|
-
opacity: 0.8;
|
|
93
|
-
}
|
|
94
|
-
`,
|
|
95
|
-
];
|
|
96
|
-
canvasRef = createRef<HTMLCanvasElement>();
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Duration in milliseconds for video buffering ahead of current time
|
|
100
|
-
* @domAttribute "video-buffer-duration"
|
|
101
|
-
*/
|
|
102
|
-
@property({ type: Number, attribute: "video-buffer-duration" })
|
|
103
|
-
videoBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Maximum number of concurrent video segment fetches for buffering
|
|
107
|
-
* @domAttribute "max-video-buffer-fetches"
|
|
108
|
-
*/
|
|
109
|
-
@property({ type: Number, attribute: "max-video-buffer-fetches" })
|
|
110
|
-
maxVideoBufferFetches = 2;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Enable/disable video buffering system
|
|
114
|
-
* @domAttribute "enable-video-buffering"
|
|
115
|
-
*/
|
|
116
|
-
@property({ type: Boolean, attribute: "enable-video-buffering" })
|
|
117
|
-
enableVideoBuffering = true;
|
|
118
|
-
|
|
119
|
-
// Unified video system - single smart seek task that routes to scrub or main
|
|
120
|
-
unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);
|
|
121
|
-
videoBufferTask = makeVideoBufferTask(this); // Keep for main video buffering
|
|
122
|
-
|
|
123
|
-
// Scrub video preloading system
|
|
124
|
-
scrubVideoBufferTask = makeScrubVideoBufferTask(this);
|
|
125
|
-
scrubVideoInputTask = makeScrubVideoInputTask(this);
|
|
126
|
-
scrubVideoSeekTask = makeScrubVideoSeekTask(this);
|
|
127
|
-
scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);
|
|
128
|
-
scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);
|
|
129
|
-
scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Delayed loading state manager for user feedback
|
|
133
|
-
*/
|
|
134
|
-
private delayedLoadingState: DelayedLoadingState;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Loading state for user feedback
|
|
138
|
-
*/
|
|
139
|
-
@state()
|
|
140
|
-
loadingState = {
|
|
141
|
-
isLoading: false,
|
|
142
|
-
operation: null as LoadingState["operation"],
|
|
143
|
-
message: "",
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
constructor() {
|
|
147
|
-
super();
|
|
148
|
-
|
|
149
|
-
// Initialize delayed loading state with callback to update UI
|
|
150
|
-
this.delayedLoadingState = new DelayedLoadingState(
|
|
151
|
-
250,
|
|
152
|
-
(isLoading, message) => {
|
|
153
|
-
this.setLoadingState(isLoading, null, message);
|
|
154
|
-
},
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
protected updated(
|
|
159
|
-
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
160
|
-
): void {
|
|
161
|
-
super.updated(changedProperties);
|
|
162
|
-
|
|
163
|
-
// No need to clear canvas - displayFrame() overwrites it completely
|
|
164
|
-
// and clearing creates blank frame gaps during transitions
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
render() {
|
|
168
|
-
return html`
|
|
169
|
-
<canvas ${ref(this.canvasRef)}></canvas>
|
|
170
|
-
${
|
|
171
|
-
this.loadingState.isLoading
|
|
172
|
-
? html`
|
|
173
|
-
<div class="loading-overlay">
|
|
174
|
-
<div class="loading-content">
|
|
175
|
-
<div class="loading-spinner"></div>
|
|
176
|
-
<div>
|
|
177
|
-
<div>Loading Video...</div>
|
|
178
|
-
<div class="loading-message">${this.loadingState.message}</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
`
|
|
183
|
-
: ""
|
|
184
|
-
}
|
|
185
|
-
`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
get canvasElement() {
|
|
189
|
-
const referencedCanvas = this.canvasRef.value;
|
|
190
|
-
if (referencedCanvas) {
|
|
191
|
-
return referencedCanvas;
|
|
192
|
-
}
|
|
193
|
-
const shadowCanvas = this.shadowRoot?.querySelector("canvas");
|
|
194
|
-
if (shadowCanvas) {
|
|
195
|
-
return shadowCanvas;
|
|
196
|
-
}
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
frameTask = new Task(this, {
|
|
201
|
-
autoRun: false,
|
|
202
|
-
args: () => [this.desiredSeekTimeMs] as const,
|
|
203
|
-
onError: (error) => {
|
|
204
|
-
console.error("frameTask error", error);
|
|
205
|
-
},
|
|
206
|
-
onComplete: () => {},
|
|
207
|
-
task: async ([_desiredSeekTimeMs], { signal }) => {
|
|
208
|
-
const t0 = performance.now();
|
|
209
|
-
|
|
210
|
-
await withSpan(
|
|
211
|
-
"video.frameTask",
|
|
212
|
-
{
|
|
213
|
-
elementId: this.id || "unknown",
|
|
214
|
-
desiredSeekTimeMs: _desiredSeekTimeMs,
|
|
215
|
-
src: this.src || "none",
|
|
216
|
-
},
|
|
217
|
-
undefined,
|
|
218
|
-
async (span) => {
|
|
219
|
-
const t1 = performance.now();
|
|
220
|
-
span.setAttribute("preworkMs", t1 - t0);
|
|
221
|
-
|
|
222
|
-
this.unifiedVideoSeekTask.run();
|
|
223
|
-
const t2 = performance.now();
|
|
224
|
-
span.setAttribute("seekRunMs", t2 - t1);
|
|
225
|
-
|
|
226
|
-
await this.unifiedVideoSeekTask.taskComplete;
|
|
227
|
-
const t3 = performance.now();
|
|
228
|
-
span.setAttribute("seekAwaitMs", t3 - t2);
|
|
229
|
-
if (signal.aborted) {
|
|
230
|
-
span.setAttribute("aborted", true);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
this.paint(this.desiredSeekTimeMs, span);
|
|
235
|
-
|
|
236
|
-
if (!this.parentTimegroup) {
|
|
237
|
-
updateAnimations(this);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const t4 = performance.now();
|
|
241
|
-
this.paint(_desiredSeekTimeMs, span);
|
|
242
|
-
const t5 = performance.now();
|
|
243
|
-
span.setAttribute("paintMs", t5 - t4);
|
|
244
|
-
span.setAttribute("totalFrameMs", t5 - t0);
|
|
245
|
-
},
|
|
246
|
-
);
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Start a delayed loading operation for testing
|
|
252
|
-
*/
|
|
253
|
-
startDelayedLoading(
|
|
254
|
-
operationId: string,
|
|
255
|
-
message: string,
|
|
256
|
-
options: { background?: boolean } = {},
|
|
257
|
-
): void {
|
|
258
|
-
this.delayedLoadingState.startLoading(operationId, message, options);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Clear a delayed loading operation for testing
|
|
263
|
-
*/
|
|
264
|
-
clearDelayedLoading(operationId: string): void {
|
|
265
|
-
this.delayedLoadingState.clearLoading(operationId);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Set loading state for user feedback
|
|
270
|
-
*/
|
|
271
|
-
private setLoadingState(
|
|
272
|
-
isLoading: boolean,
|
|
273
|
-
operation: LoadingState["operation"] = null,
|
|
274
|
-
message = "",
|
|
275
|
-
): void {
|
|
276
|
-
this.loadingState = {
|
|
277
|
-
isLoading,
|
|
278
|
-
operation,
|
|
279
|
-
message,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Paint the current video frame to canvas
|
|
285
|
-
* Called by frameTask after seek is complete
|
|
286
|
-
*/
|
|
287
|
-
paint(seekToMs: number, parentSpan?: any): void {
|
|
288
|
-
const parentContext = parentSpan
|
|
289
|
-
? trace.setSpan(context.active(), parentSpan)
|
|
290
|
-
: undefined;
|
|
291
|
-
|
|
292
|
-
withSpanSync(
|
|
293
|
-
"video.paint",
|
|
294
|
-
{
|
|
295
|
-
elementId: this.id || "unknown",
|
|
296
|
-
seekToMs,
|
|
297
|
-
src: this.src || "none",
|
|
298
|
-
},
|
|
299
|
-
parentContext,
|
|
300
|
-
(span) => {
|
|
301
|
-
const t0 = performance.now();
|
|
302
|
-
|
|
303
|
-
// Check if we're in production rendering mode vs preview mode
|
|
304
|
-
const isProductionRendering = this.isInProductionRenderingMode();
|
|
305
|
-
const t1 = performance.now();
|
|
306
|
-
span.setAttribute("isProductionRendering", isProductionRendering);
|
|
307
|
-
span.setAttribute("modeCheckMs", t1 - t0);
|
|
308
|
-
|
|
309
|
-
// Unified video system: smart routing to scrub or main, with background upgrades
|
|
310
|
-
// Note: frameTask guarantees unifiedVideoSeekTask is complete before calling paint
|
|
311
|
-
try {
|
|
312
|
-
const t2 = performance.now();
|
|
313
|
-
const videoSample = this.unifiedVideoSeekTask.value;
|
|
314
|
-
span.setAttribute("hasVideoSample", !!videoSample);
|
|
315
|
-
span.setAttribute("valueAccessMs", t2 - t1);
|
|
316
|
-
|
|
317
|
-
if (videoSample) {
|
|
318
|
-
const t3 = performance.now();
|
|
319
|
-
const videoFrame = videoSample.toVideoFrame();
|
|
320
|
-
const t4 = performance.now();
|
|
321
|
-
span.setAttribute("toVideoFrameMs", t4 - t3);
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const t5 = performance.now();
|
|
325
|
-
this.displayFrame(videoFrame, seekToMs, span);
|
|
326
|
-
const t6 = performance.now();
|
|
327
|
-
span.setAttribute("displayFrameMs", t6 - t5);
|
|
328
|
-
} finally {
|
|
329
|
-
videoFrame.close();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.warn("Unified video pipeline error:", error);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// EF_FRAMEGEN-aware rendering mode detection
|
|
337
|
-
if (!isProductionRendering) {
|
|
338
|
-
// Preview mode: skip rendering during initialization to prevent artifacts
|
|
339
|
-
if (
|
|
340
|
-
!this.rootTimegroup ||
|
|
341
|
-
(this.rootTimegroup.currentTimeMs === 0 &&
|
|
342
|
-
this.desiredSeekTimeMs === 0)
|
|
343
|
-
) {
|
|
344
|
-
span.setAttribute("skipped", "preview-initialization");
|
|
345
|
-
return; // Skip initialization frame in preview mode
|
|
346
|
-
}
|
|
347
|
-
// Preview mode: proceed with rendering
|
|
348
|
-
} else {
|
|
349
|
-
// Production rendering mode: only render when EF_FRAMEGEN has explicitly started frame rendering
|
|
350
|
-
// This prevents initialization frames before the actual render sequence begins
|
|
351
|
-
if (!this.rootTimegroup) {
|
|
352
|
-
span.setAttribute("skipped", "no-root-timegroup");
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (!this.isFrameRenderingActive()) {
|
|
357
|
-
span.setAttribute("skipped", "frame-rendering-not-active");
|
|
358
|
-
return; // Wait for EF_FRAMEGEN to start frame sequence
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Production mode: EF_FRAMEGEN has started frame sequence, proceed with rendering
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const tEnd = performance.now();
|
|
365
|
-
span.setAttribute("totalPaintMs", tEnd - t0);
|
|
366
|
-
},
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Clear the canvas when element becomes inactive
|
|
372
|
-
*/
|
|
373
|
-
clearCanvas(): void {
|
|
374
|
-
if (!this.canvasElement) return;
|
|
375
|
-
|
|
376
|
-
const ctx = this.canvasElement.getContext("2d");
|
|
377
|
-
if (ctx) {
|
|
378
|
-
ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Display a video frame on the canvas
|
|
384
|
-
*/
|
|
385
|
-
displayFrame(frame: VideoFrame, seekToMs: number, parentSpan?: any): void {
|
|
386
|
-
const parentContext = parentSpan
|
|
387
|
-
? trace.setSpan(context.active(), parentSpan)
|
|
388
|
-
: undefined;
|
|
389
|
-
|
|
390
|
-
withSpanSync(
|
|
391
|
-
"video.displayFrame",
|
|
392
|
-
{
|
|
393
|
-
elementId: this.id || "unknown",
|
|
394
|
-
seekToMs,
|
|
395
|
-
format: frame.format || "unknown",
|
|
396
|
-
width: frame.codedWidth,
|
|
397
|
-
height: frame.codedHeight,
|
|
398
|
-
},
|
|
399
|
-
parentContext,
|
|
400
|
-
(span) => {
|
|
401
|
-
const t0 = performance.now();
|
|
402
|
-
|
|
403
|
-
log("trace: displayFrame start", {
|
|
404
|
-
seekToMs,
|
|
405
|
-
frameFormat: frame.format,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
if (!this.canvasElement) {
|
|
409
|
-
log("trace: displayFrame aborted - no canvas element");
|
|
410
|
-
throw new Error(
|
|
411
|
-
`Frame display failed: Canvas element is not available at time ${seekToMs}ms. The video component may not be properly initialized.`,
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
const t1 = performance.now();
|
|
415
|
-
span.setAttribute("getCanvasMs", Math.round((t1 - t0) * 100) / 100);
|
|
416
|
-
|
|
417
|
-
const ctx = this.canvasElement.getContext("2d");
|
|
418
|
-
const t2 = performance.now();
|
|
419
|
-
span.setAttribute("getCtxMs", Math.round((t2 - t1) * 100) / 100);
|
|
420
|
-
|
|
421
|
-
if (!ctx) {
|
|
422
|
-
log("trace: displayFrame aborted - no canvas context");
|
|
423
|
-
throw new Error(
|
|
424
|
-
`Frame display failed: Unable to get 2D canvas context at time ${seekToMs}ms. This may indicate a browser compatibility issue or canvas corruption.`,
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
let resized = false;
|
|
429
|
-
if (frame?.codedWidth && frame?.codedHeight) {
|
|
430
|
-
if (
|
|
431
|
-
this.canvasElement.width !== frame.codedWidth ||
|
|
432
|
-
this.canvasElement.height !== frame.codedHeight
|
|
433
|
-
) {
|
|
434
|
-
log("trace: updating canvas dimensions", {
|
|
435
|
-
width: frame.codedWidth,
|
|
436
|
-
height: frame.codedHeight,
|
|
437
|
-
});
|
|
438
|
-
this.canvasElement.width = frame.codedWidth;
|
|
439
|
-
this.canvasElement.height = frame.codedHeight;
|
|
440
|
-
resized = true;
|
|
441
|
-
const t3 = performance.now();
|
|
442
|
-
span.setAttribute("resizeMs", Math.round((t3 - t2) * 100) / 100);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
span.setAttribute("canvasResized", resized);
|
|
446
|
-
|
|
447
|
-
if (frame.format === null) {
|
|
448
|
-
log("trace: displayFrame aborted - null frame format");
|
|
449
|
-
throw new Error(
|
|
450
|
-
`Frame display failed: Video frame has null format at time ${seekToMs}ms. This indicates corrupted or incompatible video data.`,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const tDrawStart = performance.now();
|
|
455
|
-
ctx.drawImage(
|
|
456
|
-
frame,
|
|
457
|
-
0,
|
|
458
|
-
0,
|
|
459
|
-
this.canvasElement.width,
|
|
460
|
-
this.canvasElement.height,
|
|
461
|
-
);
|
|
462
|
-
const tDrawEnd = performance.now();
|
|
463
|
-
span.setAttribute(
|
|
464
|
-
"drawImageMs",
|
|
465
|
-
Math.round((tDrawEnd - tDrawStart) * 100) / 100,
|
|
466
|
-
);
|
|
467
|
-
span.setAttribute(
|
|
468
|
-
"totalDisplayMs",
|
|
469
|
-
Math.round((tDrawEnd - t0) * 100) / 100,
|
|
470
|
-
);
|
|
471
|
-
span.setAttribute("canvasWidth", this.canvasElement.width);
|
|
472
|
-
span.setAttribute("canvasHeight", this.canvasElement.height);
|
|
473
|
-
|
|
474
|
-
log("trace: frame drawn to canvas", { seekToMs });
|
|
475
|
-
},
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode
|
|
481
|
-
*/
|
|
482
|
-
private isInProductionRenderingMode(): boolean {
|
|
483
|
-
// Check if EF_RENDERING function exists and returns true (production rendering)
|
|
484
|
-
if (typeof window.EF_RENDERING === "function") {
|
|
485
|
-
return window.EF_RENDERING();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Check if workbench is in rendering mode
|
|
489
|
-
const workbench = document.querySelector("ef-workbench") as any;
|
|
490
|
-
if (workbench?.rendering) {
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Check if EF_FRAMEGEN exists and has render options (indicates active rendering)
|
|
495
|
-
if (window.EF_FRAMEGEN?.renderOptions) {
|
|
496
|
-
return true;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Default to preview mode
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)
|
|
505
|
-
*/
|
|
506
|
-
private isFrameRenderingActive(): boolean {
|
|
507
|
-
if (!window.EF_FRAMEGEN?.renderOptions) {
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// In production mode, only render when EF_FRAMEGEN has actually begun frame sequence
|
|
512
|
-
// Check if we're past the initialization phase by looking for explicit frame control
|
|
513
|
-
const renderOptions = window.EF_FRAMEGEN.renderOptions;
|
|
514
|
-
const renderStartTime = renderOptions.encoderOptions.fromMs;
|
|
515
|
-
const currentTime = this.rootTimegroup?.currentTimeMs || 0;
|
|
516
|
-
|
|
517
|
-
// We're in active frame rendering if:
|
|
518
|
-
// 1. currentTime >= renderStartTime (includes the starting frame)
|
|
519
|
-
return currentTime >= renderStartTime;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Legacy getter for fragment index task
|
|
524
|
-
* Still used by EFCaptions - maps to unified video seek task
|
|
525
|
-
*/
|
|
526
|
-
get fragmentIndexTask() {
|
|
527
|
-
return this.unifiedVideoSeekTask;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Helper method for tests: wait for the current frame to be ready
|
|
532
|
-
* This encapsulates the complexity of ensuring the video has updated
|
|
533
|
-
* and its frameTask has completed.
|
|
534
|
-
*
|
|
535
|
-
* @returns Promise that resolves when the frame is ready
|
|
536
|
-
*/
|
|
537
|
-
async waitForFrameReady(): Promise<void> {
|
|
538
|
-
await this.updateComplete;
|
|
539
|
-
await this.frameTask.run();
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Clean up resources when component is disconnected
|
|
544
|
-
*/
|
|
545
|
-
disconnectedCallback(): void {
|
|
546
|
-
super.disconnectedCallback();
|
|
547
|
-
|
|
548
|
-
// Clean up delayed loading state
|
|
549
|
-
this.delayedLoadingState.clearAllLoading();
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
didBecomeRoot() {
|
|
553
|
-
super.didBecomeRoot();
|
|
554
|
-
}
|
|
555
|
-
didBecomeChild() {
|
|
556
|
-
super.didBecomeChild();
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
declare global {
|
|
561
|
-
interface HTMLElementTagNameMap {
|
|
562
|
-
"ef-video": EFVideo;
|
|
563
|
-
}
|
|
564
|
-
}
|