@editframe/elements 0.26.3-beta.0 → 0.30.0-beta.13

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 (191) hide show
  1. package/dist/elements/EFSourceMixin.js +1 -1
  2. package/dist/elements/EFSourceMixin.js.map +1 -1
  3. package/dist/elements/EFSurface.d.ts +4 -4
  4. package/dist/elements/EFText.d.ts +52 -0
  5. package/dist/elements/EFText.js +319 -0
  6. package/dist/elements/EFText.js.map +1 -0
  7. package/dist/elements/EFTextSegment.d.ts +30 -0
  8. package/dist/elements/EFTextSegment.js +94 -0
  9. package/dist/elements/EFTextSegment.js.map +1 -0
  10. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  11. package/dist/elements/EFWaveform.d.ts +4 -4
  12. package/dist/elements/FetchMixin.js +22 -7
  13. package/dist/elements/FetchMixin.js.map +1 -1
  14. package/dist/elements/easingUtils.js +62 -0
  15. package/dist/elements/easingUtils.js.map +1 -0
  16. package/dist/elements/updateAnimations.js +57 -10
  17. package/dist/elements/updateAnimations.js.map +1 -1
  18. package/dist/gui/ContextMixin.js +11 -2
  19. package/dist/gui/ContextMixin.js.map +1 -1
  20. package/dist/gui/EFConfiguration.d.ts +4 -4
  21. package/dist/gui/EFControls.d.ts +2 -2
  22. package/dist/gui/EFDial.d.ts +4 -4
  23. package/dist/gui/EFDial.js +4 -2
  24. package/dist/gui/EFDial.js.map +1 -1
  25. package/dist/gui/EFFilmstrip.d.ts +32 -6
  26. package/dist/gui/EFFilmstrip.js +314 -50
  27. package/dist/gui/EFFilmstrip.js.map +1 -1
  28. package/dist/gui/EFFitScale.js +39 -15
  29. package/dist/gui/EFFitScale.js.map +1 -1
  30. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  31. package/dist/gui/EFPause.d.ts +4 -4
  32. package/dist/gui/EFPlay.d.ts +4 -4
  33. package/dist/gui/EFPreview.d.ts +4 -4
  34. package/dist/gui/EFPreview.js +2 -2
  35. package/dist/gui/EFPreview.js.map +1 -1
  36. package/dist/gui/EFResizableBox.d.ts +4 -4
  37. package/dist/gui/EFResizableBox.js +6 -3
  38. package/dist/gui/EFResizableBox.js.map +1 -1
  39. package/dist/gui/EFScrubber.d.ts +8 -5
  40. package/dist/gui/EFScrubber.js +64 -12
  41. package/dist/gui/EFScrubber.js.map +1 -1
  42. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  43. package/dist/gui/EFToggleLoop.d.ts +4 -4
  44. package/dist/gui/EFTogglePlay.d.ts +4 -4
  45. package/dist/gui/EFWorkbench.d.ts +4 -4
  46. package/dist/gui/EFWorkbench.js +16 -3
  47. package/dist/gui/EFWorkbench.js.map +1 -1
  48. package/dist/gui/TWMixin.js +1 -1
  49. package/dist/gui/TWMixin.js.map +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/style.css +7 -120
  54. package/package.json +3 -3
  55. package/scripts/build-css.js +5 -9
  56. package/test/constants.ts +8 -0
  57. package/test/recordReplayProxyPlugin.js +76 -10
  58. package/test/setup.ts +32 -0
  59. package/test/useMSW.ts +3 -0
  60. package/test/useTranscodeMSW.ts +191 -0
  61. package/tsdown.config.ts +7 -5
  62. package/types.json +1 -1
  63. package/src/elements/ContextProxiesController.ts +0 -124
  64. package/src/elements/CrossUpdateController.ts +0 -22
  65. package/src/elements/EFAudio.browsertest.ts +0 -706
  66. package/src/elements/EFAudio.ts +0 -56
  67. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  68. package/src/elements/EFCaptions.ts +0 -823
  69. package/src/elements/EFImage.browsertest.ts +0 -120
  70. package/src/elements/EFImage.ts +0 -113
  71. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  72. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  73. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  74. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  75. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  76. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  77. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  78. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  79. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  80. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  81. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  82. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  83. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  84. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  85. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  86. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  87. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  88. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  89. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  90. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  91. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  92. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  93. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  94. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  95. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  96. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  97. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  98. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  99. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  100. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  101. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  102. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  103. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  104. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  105. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  106. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  107. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  108. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  109. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  110. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  111. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  112. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  113. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  114. package/src/elements/EFMedia.browsertest.ts +0 -872
  115. package/src/elements/EFMedia.ts +0 -341
  116. package/src/elements/EFSourceMixin.ts +0 -60
  117. package/src/elements/EFSurface.browsertest.ts +0 -151
  118. package/src/elements/EFSurface.ts +0 -142
  119. package/src/elements/EFTemporal.browsertest.ts +0 -215
  120. package/src/elements/EFTemporal.ts +0 -800
  121. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  122. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  123. package/src/elements/EFThumbnailStrip.ts +0 -906
  124. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  125. package/src/elements/EFTimegroup.ts +0 -882
  126. package/src/elements/EFVideo.browsertest.ts +0 -1482
  127. package/src/elements/EFVideo.ts +0 -564
  128. package/src/elements/EFWaveform.ts +0 -547
  129. package/src/elements/FetchContext.browsertest.ts +0 -401
  130. package/src/elements/FetchMixin.ts +0 -38
  131. package/src/elements/SampleBuffer.ts +0 -94
  132. package/src/elements/TargetController.browsertest.ts +0 -230
  133. package/src/elements/TargetController.ts +0 -224
  134. package/src/elements/TimegroupController.ts +0 -26
  135. package/src/elements/durationConverter.ts +0 -35
  136. package/src/elements/parseTimeToMs.ts +0 -9
  137. package/src/elements/printTaskStatus.ts +0 -16
  138. package/src/elements/renderTemporalAudio.ts +0 -108
  139. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  140. package/src/elements/updateAnimations.ts +0 -217
  141. package/src/elements/util.ts +0 -24
  142. package/src/gui/ContextMixin.browsertest.ts +0 -860
  143. package/src/gui/ContextMixin.ts +0 -562
  144. package/src/gui/Controllable.browsertest.ts +0 -258
  145. package/src/gui/Controllable.ts +0 -41
  146. package/src/gui/EFConfiguration.ts +0 -40
  147. package/src/gui/EFControls.browsertest.ts +0 -389
  148. package/src/gui/EFControls.ts +0 -195
  149. package/src/gui/EFDial.browsertest.ts +0 -84
  150. package/src/gui/EFDial.ts +0 -172
  151. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  152. package/src/gui/EFFilmstrip.ts +0 -1349
  153. package/src/gui/EFFitScale.ts +0 -152
  154. package/src/gui/EFFocusOverlay.ts +0 -79
  155. package/src/gui/EFPause.browsertest.ts +0 -202
  156. package/src/gui/EFPause.ts +0 -73
  157. package/src/gui/EFPlay.browsertest.ts +0 -202
  158. package/src/gui/EFPlay.ts +0 -73
  159. package/src/gui/EFPreview.ts +0 -74
  160. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  161. package/src/gui/EFResizableBox.ts +0 -898
  162. package/src/gui/EFScrubber.ts +0 -151
  163. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  164. package/src/gui/EFTimeDisplay.ts +0 -55
  165. package/src/gui/EFToggleLoop.ts +0 -35
  166. package/src/gui/EFTogglePlay.ts +0 -70
  167. package/src/gui/EFWorkbench.ts +0 -115
  168. package/src/gui/PlaybackController.ts +0 -527
  169. package/src/gui/TWMixin.css +0 -6
  170. package/src/gui/TWMixin.ts +0 -61
  171. package/src/gui/TargetOrContextMixin.ts +0 -185
  172. package/src/gui/currentTimeContext.ts +0 -5
  173. package/src/gui/durationContext.ts +0 -3
  174. package/src/gui/efContext.ts +0 -6
  175. package/src/gui/fetchContext.ts +0 -5
  176. package/src/gui/focusContext.ts +0 -7
  177. package/src/gui/focusedElementContext.ts +0 -5
  178. package/src/gui/playingContext.ts +0 -5
  179. package/src/otel/BridgeSpanExporter.ts +0 -150
  180. package/src/otel/setupBrowserTracing.ts +0 -73
  181. package/src/otel/tracingHelpers.ts +0 -251
  182. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  183. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  184. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  185. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  186. package/src/transcoding/types/index.ts +0 -312
  187. package/src/transcoding/utils/MediaUtils.ts +0 -63
  188. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  189. package/src/transcoding/utils/constants.ts +0 -36
  190. package/src/utils/LRUCache.test.ts +0 -274
  191. package/src/utils/LRUCache.ts +0 -696
@@ -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
- }