@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
|
@@ -36,11 +36,12 @@ export class EFCaptionsActiveWord extends EFTemporal(LitElement) {
|
|
|
36
36
|
css`
|
|
37
37
|
:host {
|
|
38
38
|
display: inline-block;
|
|
39
|
-
white-space:
|
|
40
|
-
|
|
39
|
+
white-space: normal;
|
|
40
|
+
line-height: 1;
|
|
41
41
|
}
|
|
42
42
|
:host([hidden]) {
|
|
43
|
-
|
|
43
|
+
opacity: 0;
|
|
44
|
+
pointer-events: none;
|
|
44
45
|
}
|
|
45
46
|
`,
|
|
46
47
|
];
|
|
@@ -57,7 +58,7 @@ export class EFCaptionsActiveWord extends EFTemporal(LitElement) {
|
|
|
57
58
|
const seedValue = seed / 233; // Normalize to 0-1 range
|
|
58
59
|
this.style.setProperty("--ef-word-seed", seedValue.toString());
|
|
59
60
|
|
|
60
|
-
return html
|
|
61
|
+
return html`${this.wordText}`;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
@property({ type: Number, attribute: false })
|
|
@@ -98,10 +99,9 @@ export class EFCaptionsSegment extends EFTemporal(LitElement) {
|
|
|
98
99
|
static styles = [
|
|
99
100
|
css`
|
|
100
101
|
:host {
|
|
101
|
-
display: block;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
display: none;
|
|
102
|
+
display: inline-block;
|
|
103
|
+
white-space: normal;
|
|
104
|
+
line-height: 1;
|
|
105
105
|
}
|
|
106
106
|
`,
|
|
107
107
|
];
|
|
@@ -152,9 +152,11 @@ export class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
|
|
|
152
152
|
:host {
|
|
153
153
|
display: inline-block;
|
|
154
154
|
white-space: pre;
|
|
155
|
+
line-height: 1;
|
|
155
156
|
}
|
|
156
157
|
:host([hidden]) {
|
|
157
|
-
|
|
158
|
+
opacity: 0;
|
|
159
|
+
pointer-events: none;
|
|
158
160
|
}
|
|
159
161
|
`,
|
|
160
162
|
];
|
|
@@ -165,7 +167,14 @@ export class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
|
|
|
165
167
|
return undefined;
|
|
166
168
|
}
|
|
167
169
|
this.hidden = false;
|
|
168
|
-
|
|
170
|
+
|
|
171
|
+
// Check if there's an active word by looking for sibling active word element
|
|
172
|
+
const activeWord = this.closest("ef-captions")?.querySelector(
|
|
173
|
+
"ef-captions-active-word",
|
|
174
|
+
);
|
|
175
|
+
const hasActiveWord = activeWord?.wordText && !activeWord.hidden;
|
|
176
|
+
|
|
177
|
+
return html`${this.segmentText}${hasActiveWord ? " " : ""}`;
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
@property({ type: Boolean, reflect: true })
|
|
@@ -205,9 +214,11 @@ export class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
|
|
|
205
214
|
:host {
|
|
206
215
|
display: inline-block;
|
|
207
216
|
white-space: pre;
|
|
217
|
+
line-height: 1;
|
|
208
218
|
}
|
|
209
219
|
:host([hidden]) {
|
|
210
|
-
|
|
220
|
+
opacity: 0;
|
|
221
|
+
pointer-events: none;
|
|
211
222
|
}
|
|
212
223
|
`,
|
|
213
224
|
];
|
|
@@ -218,7 +229,14 @@ export class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
|
|
|
218
229
|
return undefined;
|
|
219
230
|
}
|
|
220
231
|
this.hidden = false;
|
|
221
|
-
|
|
232
|
+
|
|
233
|
+
// Check if there's an active word by looking for sibling active word element
|
|
234
|
+
const activeWord = this.closest("ef-captions")?.querySelector(
|
|
235
|
+
"ef-captions-active-word",
|
|
236
|
+
);
|
|
237
|
+
const hasActiveWord = activeWord?.wordText && !activeWord.hidden;
|
|
238
|
+
|
|
239
|
+
return html`${hasActiveWord ? " " : ""}${this.segmentText}`;
|
|
222
240
|
}
|
|
223
241
|
|
|
224
242
|
@property({ type: Boolean, reflect: true })
|
|
@@ -260,31 +278,18 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
260
278
|
css`
|
|
261
279
|
:host {
|
|
262
280
|
display: inline-flex;
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
white-space: normal;
|
|
282
|
+
line-height: 1;
|
|
283
|
+
gap: 0;
|
|
265
284
|
}
|
|
266
285
|
::slotted(*) {
|
|
286
|
+
display: inline-block;
|
|
267
287
|
margin: 0;
|
|
268
288
|
padding: 0;
|
|
269
289
|
}
|
|
270
|
-
::slotted(ef-captions-active-word) {
|
|
271
|
-
min-width: 0.5ch; /* Maintain minimum width when empty */
|
|
272
|
-
min-height: 1em; /* Maintain height for baseline alignment */
|
|
273
|
-
}
|
|
274
|
-
::slotted(ef-captions-active-word[hidden]) {
|
|
275
|
-
opacity: 0; /* Hide when empty but maintain layout */
|
|
276
|
-
min-width: 0.5ch;
|
|
277
|
-
min-height: 1em;
|
|
278
|
-
}
|
|
279
290
|
`,
|
|
280
291
|
];
|
|
281
292
|
|
|
282
|
-
@property({ type: String, attribute: "display-mode", reflect: true })
|
|
283
|
-
displayMode: "word" | "segment" | "context" = "segment";
|
|
284
|
-
|
|
285
|
-
@property({ type: Number, attribute: "context-words", reflect: true })
|
|
286
|
-
contextWords = 3;
|
|
287
|
-
|
|
288
293
|
@property({ type: String, attribute: "target", reflect: true })
|
|
289
294
|
targetSelector = "";
|
|
290
295
|
|
|
@@ -499,9 +504,11 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
499
504
|
|
|
500
505
|
frameTask = new Task(this, {
|
|
501
506
|
autoRun: EF_INTERACTIVE,
|
|
502
|
-
args: () => [this.unifiedCaptionsDataTask.status],
|
|
507
|
+
args: () => [this.unifiedCaptionsDataTask.status, this.ownCurrentTimeMs],
|
|
503
508
|
task: async () => {
|
|
504
509
|
await this.unifiedCaptionsDataTask.taskComplete;
|
|
510
|
+
// Trigger updateTextContainers when data is ready or time changes
|
|
511
|
+
this.updateTextContainers();
|
|
505
512
|
},
|
|
506
513
|
});
|
|
507
514
|
|
|
@@ -519,6 +526,18 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
519
526
|
else if (this.hasCustomCaptionsData && this.rootTimegroup) {
|
|
520
527
|
new CrossUpdateController(this.rootTimegroup, this);
|
|
521
528
|
}
|
|
529
|
+
|
|
530
|
+
// Prevent display:none from being set on caption elements
|
|
531
|
+
// This maintains constant width in the parent flex container
|
|
532
|
+
const observer = new MutationObserver(() => {
|
|
533
|
+
if (this.style.display === "none") {
|
|
534
|
+
// Remove the display:none and use opacity instead
|
|
535
|
+
this.style.removeProperty("display");
|
|
536
|
+
this.style.opacity = "0";
|
|
537
|
+
this.style.pointerEvents = "none";
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
observer.observe(this, { attributes: true, attributeFilter: ["style"] });
|
|
522
541
|
}
|
|
523
542
|
|
|
524
543
|
protected updated(
|
|
@@ -557,10 +576,8 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
557
576
|
return;
|
|
558
577
|
}
|
|
559
578
|
|
|
560
|
-
//
|
|
561
|
-
const currentTimeMs = this.
|
|
562
|
-
? this.targetElement.currentSourceTimeMs
|
|
563
|
-
: this.ownCurrentTimeMs;
|
|
579
|
+
// Use ownCurrentTimeMs which is synchronized with the timegroup
|
|
580
|
+
const currentTimeMs = this.ownCurrentTimeMs;
|
|
564
581
|
const currentTimeSec = currentTimeMs / 1000;
|
|
565
582
|
|
|
566
583
|
// Find the current word from word_segments
|
|
@@ -611,6 +628,7 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
611
628
|
segmentContainer.segmentStartMs = 0;
|
|
612
629
|
segmentContainer.segmentEndMs = 0;
|
|
613
630
|
}
|
|
631
|
+
segmentContainer.requestUpdate();
|
|
614
632
|
}
|
|
615
633
|
|
|
616
634
|
// Process context for both word and segment cases
|
|
@@ -645,6 +663,7 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
645
663
|
container.segmentText = beforeWords;
|
|
646
664
|
container.segmentStartMs = currentWord.start * 1000;
|
|
647
665
|
container.segmentEndMs = currentWord.end * 1000;
|
|
666
|
+
container.requestUpdate();
|
|
648
667
|
}
|
|
649
668
|
|
|
650
669
|
// Update after containers - should be visible at the same time as active word
|
|
@@ -652,6 +671,7 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
652
671
|
container.segmentText = afterWords;
|
|
653
672
|
container.segmentStartMs = currentWord.start * 1000;
|
|
654
673
|
container.segmentEndMs = currentWord.end * 1000;
|
|
674
|
+
container.requestUpdate();
|
|
655
675
|
}
|
|
656
676
|
}
|
|
657
677
|
} else if (currentSegment) {
|
|
@@ -673,12 +693,14 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
673
693
|
container.segmentText = ""; // Nothing before yet
|
|
674
694
|
container.segmentStartMs = currentSegment.start * 1000;
|
|
675
695
|
container.segmentEndMs = currentSegment.end * 1000;
|
|
696
|
+
container.requestUpdate();
|
|
676
697
|
}
|
|
677
698
|
|
|
678
699
|
for (const container of this.afterActiveWordContainers) {
|
|
679
700
|
container.segmentText = allWords; // All words are upcoming
|
|
680
701
|
container.segmentStartMs = currentSegment.start * 1000;
|
|
681
702
|
container.segmentEndMs = currentSegment.end * 1000;
|
|
703
|
+
container.requestUpdate();
|
|
682
704
|
}
|
|
683
705
|
} else {
|
|
684
706
|
// After last word ends - show all completed words in "before" container
|
|
@@ -690,12 +712,14 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
690
712
|
container.segmentText = allCompletedWords;
|
|
691
713
|
container.segmentStartMs = currentSegment.start * 1000;
|
|
692
714
|
container.segmentEndMs = currentSegment.end * 1000;
|
|
715
|
+
container.requestUpdate();
|
|
693
716
|
}
|
|
694
717
|
|
|
695
718
|
for (const container of this.afterActiveWordContainers) {
|
|
696
719
|
container.segmentText = "";
|
|
697
720
|
container.segmentStartMs = currentSegment.start * 1000;
|
|
698
721
|
container.segmentEndMs = currentSegment.end * 1000;
|
|
722
|
+
container.requestUpdate();
|
|
699
723
|
}
|
|
700
724
|
}
|
|
701
725
|
} else {
|
|
@@ -704,12 +728,14 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
704
728
|
container.segmentText = "";
|
|
705
729
|
container.segmentStartMs = 0;
|
|
706
730
|
container.segmentEndMs = 0;
|
|
731
|
+
container.requestUpdate();
|
|
707
732
|
}
|
|
708
733
|
|
|
709
734
|
for (const container of this.afterActiveWordContainers) {
|
|
710
735
|
container.segmentText = "";
|
|
711
736
|
container.segmentStartMs = 0;
|
|
712
737
|
container.segmentEndMs = 0;
|
|
738
|
+
container.requestUpdate();
|
|
713
739
|
}
|
|
714
740
|
}
|
|
715
741
|
}
|
|
@@ -6,11 +6,10 @@ import { v4 } from "uuid";
|
|
|
6
6
|
describe("EFImage", () => {
|
|
7
7
|
describe("when rendering", () => {
|
|
8
8
|
beforeEach(() => {
|
|
9
|
-
// @ts-
|
|
9
|
+
// @ts-expect-error
|
|
10
10
|
window.FRAMEGEN_BRIDGE = true;
|
|
11
11
|
});
|
|
12
12
|
afterEach(() => {
|
|
13
|
-
// @ts-ignore
|
|
14
13
|
delete window.FRAMEGEN_BRIDGE;
|
|
15
14
|
});
|
|
16
15
|
test("assetPath uses http:// protocol", () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TrackFragmentIndex } from "@editframe/assets";
|
|
2
2
|
|
|
3
|
+
import { withSpan } from "../../otel/tracingHelpers.js";
|
|
3
4
|
import type {
|
|
4
5
|
AudioRendition,
|
|
5
6
|
InitSegmentPaths,
|
|
@@ -121,23 +122,36 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
121
122
|
rendition: { trackId: number | undefined; src: string },
|
|
122
123
|
signal: AbortSignal,
|
|
123
124
|
) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
return withSpan(
|
|
126
|
+
"assetEngine.fetchInitSegment",
|
|
127
|
+
{
|
|
128
|
+
trackId: rendition.trackId || -1,
|
|
129
|
+
src: rendition.src,
|
|
130
|
+
},
|
|
131
|
+
undefined,
|
|
132
|
+
async (span) => {
|
|
133
|
+
if (!rendition.trackId) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
"[fetchInitSegment] Track ID is required for asset metadata",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const url = this.buildInitSegmentUrl(rendition.trackId);
|
|
139
|
+
const initSegment = this.data[rendition.trackId]?.initSegment;
|
|
140
|
+
if (!initSegment) {
|
|
141
|
+
throw new Error("Init segment not found");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
span.setAttribute("offset", initSegment.offset);
|
|
145
|
+
span.setAttribute("size", initSegment.size);
|
|
146
|
+
|
|
147
|
+
// Use unified fetch method with Range headers
|
|
148
|
+
const headers = {
|
|
149
|
+
Range: `bytes=${initSegment.offset}-${initSegment.offset + initSegment.size - 1}`,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return this.fetchMediaWithHeaders(url, headers, signal);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
async fetchMediaSegment(
|
|
@@ -145,26 +159,40 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
145
159
|
rendition: { trackId: number | undefined; src: string },
|
|
146
160
|
signal?: AbortSignal,
|
|
147
161
|
) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
return withSpan(
|
|
163
|
+
"assetEngine.fetchMediaSegment",
|
|
164
|
+
{
|
|
165
|
+
segmentId,
|
|
166
|
+
trackId: rendition.trackId || -1,
|
|
167
|
+
src: rendition.src,
|
|
168
|
+
},
|
|
169
|
+
undefined,
|
|
170
|
+
async (span) => {
|
|
171
|
+
if (!rendition.trackId) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"[fetchMediaSegment] Track ID is required for asset metadata",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (segmentId === undefined) {
|
|
177
|
+
throw new Error("Segment ID is not available");
|
|
178
|
+
}
|
|
179
|
+
const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
|
|
180
|
+
const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
|
|
181
|
+
if (!mediaSegment) {
|
|
182
|
+
throw new Error("Media segment not found");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
span.setAttribute("offset", mediaSegment.offset);
|
|
186
|
+
span.setAttribute("size", mediaSegment.size);
|
|
187
|
+
|
|
188
|
+
// Use unified fetch method with Range headers
|
|
189
|
+
const headers = {
|
|
190
|
+
Range: `bytes=${mediaSegment.offset}-${mediaSegment.offset + mediaSegment.size - 1}`,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return this.fetchMediaWithHeaders(url, headers, signal);
|
|
194
|
+
},
|
|
195
|
+
);
|
|
168
196
|
}
|
|
169
197
|
|
|
170
198
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSpan } from "../../otel/tracingHelpers.js";
|
|
1
2
|
import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
|
|
2
3
|
import type {
|
|
3
4
|
AudioRendition,
|
|
@@ -61,65 +62,122 @@ export abstract class BaseMediaEngine {
|
|
|
61
62
|
signal?: AbortSignal;
|
|
62
63
|
},
|
|
63
64
|
): Promise<any> {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
return withSpan(
|
|
66
|
+
"mediaEngine.fetchWithCache",
|
|
67
|
+
{
|
|
68
|
+
url: url.length > 100 ? `${url.substring(0, 100)}...` : url,
|
|
69
|
+
responseType: options.responseType,
|
|
70
|
+
hasHeaders: !!options.headers,
|
|
71
|
+
},
|
|
72
|
+
undefined,
|
|
73
|
+
async (span) => {
|
|
74
|
+
const t0 = performance.now();
|
|
75
|
+
const { responseType, headers, signal } = options;
|
|
76
|
+
|
|
77
|
+
// Create cache key that includes URL and headers for proper isolation
|
|
78
|
+
// Note: We don't include signal in cache key as it would prevent proper deduplication
|
|
79
|
+
const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
|
|
80
|
+
|
|
81
|
+
// Check cache first
|
|
82
|
+
const t1 = performance.now();
|
|
83
|
+
const cached = mediaCache.get(cacheKey);
|
|
84
|
+
const t2 = performance.now();
|
|
85
|
+
span.setAttribute("cacheLookupMs", Math.round((t2 - t1) * 1000) / 1000);
|
|
86
|
+
|
|
87
|
+
if (cached) {
|
|
88
|
+
span.setAttribute("cacheHit", true);
|
|
89
|
+
// If we have a cached promise, we need to handle the caller's abort signal
|
|
90
|
+
// without affecting the underlying request that other instances might be using
|
|
91
|
+
if (signal) {
|
|
92
|
+
const t3 = performance.now();
|
|
93
|
+
const result = await this.handleAbortForCachedRequest(
|
|
94
|
+
cached,
|
|
95
|
+
signal,
|
|
96
|
+
);
|
|
97
|
+
const t4 = performance.now();
|
|
98
|
+
span.setAttribute(
|
|
99
|
+
"handleAbortMs",
|
|
100
|
+
Math.round((t4 - t3) * 100) / 100,
|
|
101
|
+
);
|
|
102
|
+
span.setAttribute(
|
|
103
|
+
"totalCacheHitMs",
|
|
104
|
+
Math.round((t4 - t0) * 100) / 100,
|
|
105
|
+
);
|
|
106
|
+
return result;
|
|
93
107
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
span.setAttribute(
|
|
109
|
+
"totalCacheHitMs",
|
|
110
|
+
Math.round((t2 - t0) * 100) / 100,
|
|
111
|
+
);
|
|
112
|
+
return cached;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
span.setAttribute("cacheHit", false);
|
|
116
|
+
|
|
117
|
+
// Use global deduplicator to prevent concurrent requests for the same resource
|
|
118
|
+
// Note: We do NOT pass the signal to the deduplicator - each caller manages their own abort
|
|
119
|
+
const promise = globalRequestDeduplicator.executeRequest(
|
|
120
|
+
cacheKey,
|
|
121
|
+
async () => {
|
|
122
|
+
const fetchStart = performance.now();
|
|
123
|
+
try {
|
|
124
|
+
// Make the fetch request WITHOUT the signal - let each caller handle their own abort
|
|
125
|
+
// This prevents one instance's abort from affecting other instances using the shared cache
|
|
126
|
+
const response = await this.host.fetch(url, { headers });
|
|
127
|
+
const fetchEnd = performance.now();
|
|
128
|
+
span.setAttribute("fetchMs", fetchEnd - fetchStart);
|
|
129
|
+
|
|
130
|
+
if (responseType === "json") {
|
|
131
|
+
return response.json();
|
|
132
|
+
}
|
|
133
|
+
const buffer = await response.arrayBuffer();
|
|
134
|
+
span.setAttribute("sizeBytes", buffer.byteLength);
|
|
135
|
+
return buffer;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// If the request was aborted, don't cache the error
|
|
138
|
+
if (
|
|
139
|
+
error instanceof DOMException &&
|
|
140
|
+
error.name === "AbortError"
|
|
141
|
+
) {
|
|
142
|
+
// Remove from cache so other requests can retry
|
|
143
|
+
mediaCache.delete(cacheKey);
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Cache the promise (not the result) to handle concurrent requests
|
|
151
|
+
mediaCache.set(cacheKey, promise);
|
|
152
|
+
|
|
153
|
+
// Handle the case where the promise might be aborted
|
|
154
|
+
promise.catch((error) => {
|
|
155
|
+
// If the request was aborted, remove it from cache to prevent corrupted data
|
|
97
156
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
98
|
-
// Remove from cache so other requests can retry
|
|
99
157
|
mediaCache.delete(cacheKey);
|
|
100
158
|
}
|
|
101
|
-
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// If the caller has a signal, handle abort logic without affecting the underlying request
|
|
162
|
+
if (signal) {
|
|
163
|
+
const result = await this.handleAbortForCachedRequest(
|
|
164
|
+
promise,
|
|
165
|
+
signal,
|
|
166
|
+
);
|
|
167
|
+
const tEnd = performance.now();
|
|
168
|
+
span.setAttribute(
|
|
169
|
+
"totalFetchMs",
|
|
170
|
+
Math.round((tEnd - t0) * 100) / 100,
|
|
171
|
+
);
|
|
172
|
+
return result;
|
|
102
173
|
}
|
|
174
|
+
|
|
175
|
+
const result = await promise;
|
|
176
|
+
const tEnd = performance.now();
|
|
177
|
+
span.setAttribute("totalFetchMs", Math.round((tEnd - t0) * 100) / 100);
|
|
178
|
+
return result;
|
|
103
179
|
},
|
|
104
180
|
);
|
|
105
|
-
|
|
106
|
-
// Cache the promise (not the result) to handle concurrent requests
|
|
107
|
-
mediaCache.set(cacheKey, promise);
|
|
108
|
-
|
|
109
|
-
// Handle the case where the promise might be aborted
|
|
110
|
-
promise.catch((error) => {
|
|
111
|
-
// If the request was aborted, remove it from cache to prevent corrupted data
|
|
112
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
113
|
-
mediaCache.delete(cacheKey);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// If the caller has a signal, handle abort logic without affecting the underlying request
|
|
118
|
-
if (signal) {
|
|
119
|
-
return this.handleAbortForCachedRequest(promise, signal);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return promise;
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
/**
|