@editframe/elements 0.21.0-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/EF_FRAMEGEN.js +2 -3
- 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 +2 -2
- package/dist/elements/EFCaptions.d.ts +1 -3
- package/dist/elements/EFCaptions.js +59 -51
- package/dist/elements/EFImage.js +2 -2
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js +1 -3
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +2 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -2
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -9
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -3
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +4 -7
- package/dist/elements/EFMedia.d.ts +19 -0
- package/dist/elements/EFMedia.js +19 -2
- package/dist/elements/EFSourceMixin.js +1 -1
- package/dist/elements/EFSurface.js +1 -1
- package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
- package/dist/elements/EFTemporal.d.ts +10 -0
- package/dist/elements/EFTemporal.js +82 -5
- package/dist/elements/EFThumbnailStrip.js +9 -16
- package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
- package/dist/elements/EFTimegroup.d.ts +35 -14
- package/dist/elements/EFTimegroup.js +72 -120
- package/dist/elements/EFVideo.d.ts +10 -0
- package/dist/elements/EFVideo.js +15 -2
- package/dist/elements/EFWaveform.js +10 -18
- package/dist/elements/SampleBuffer.js +1 -2
- package/dist/elements/TargetController.js +2 -2
- package/dist/elements/renderTemporalAudio.d.ts +10 -0
- package/dist/elements/renderTemporalAudio.js +35 -0
- package/dist/elements/updateAnimations.js +7 -10
- package/dist/gui/ContextMixin.d.ts +5 -5
- package/dist/gui/ContextMixin.js +151 -117
- 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 +1 -1
- package/dist/gui/EFControls.browsertest.d.ts +11 -0
- package/dist/gui/EFControls.d.ts +18 -4
- package/dist/gui/EFControls.js +67 -25
- 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 +140 -34
- package/dist/gui/EFFitScale.js +2 -4
- package/dist/gui/EFFocusOverlay.js +1 -1
- 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 +15 -6
- 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 +7 -7
- package/dist/gui/EFTimeDisplay.d.ts +7 -1
- package/dist/gui/EFTimeDisplay.js +5 -5
- package/dist/gui/EFToggleLoop.d.ts +9 -3
- package/dist/gui/EFToggleLoop.js +6 -4
- package/dist/gui/EFTogglePlay.d.ts +12 -4
- package/dist/gui/EFTogglePlay.js +24 -19
- package/dist/gui/EFWorkbench.js +1 -1
- 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/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 +4 -0
- package/dist/index.js +5 -1
- package/dist/otel/setupBrowserTracing.d.ts +1 -1
- package/dist/otel/setupBrowserTracing.js +6 -4
- package/dist/otel/tracingHelpers.js +1 -2
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/src/elements/ContextProxiesController.ts +10 -10
- package/src/elements/EFAudio.ts +1 -0
- package/src/elements/EFCaptions.browsertest.ts +128 -58
- package/src/elements/EFCaptions.ts +60 -34
- package/src/elements/EFImage.browsertest.ts +1 -2
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
- package/src/elements/EFMedia.browsertest.ts +8 -15
- package/src/elements/EFMedia.ts +38 -7
- 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 +6 -7
- package/src/elements/EFTimegroup.ts +162 -244
- package/src/elements/EFVideo.browsertest.ts +143 -47
- package/src/elements/EFVideo.ts +26 -0
- 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/setupBrowserTracing.ts +17 -12
- package/test/cache-integration-verification.browsertest.ts +1 -1
- package/types.json +1 -1
- package/dist/elements/ContextProxiesController.js +0 -49
- /package/dist/_virtual/{_@oxc-project_runtime@0.93.0 → _@oxc-project_runtime@0.94.0}/helpers/decorate.js +0 -0
|
@@ -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", () => {
|
|
@@ -55,10 +55,13 @@ const test = baseTest.extend<{
|
|
|
55
55
|
const apiHost = `${window.location.protocol}//${window.location.host}`;
|
|
56
56
|
configuration.setAttribute("api-host", apiHost);
|
|
57
57
|
configuration.apiHost = apiHost;
|
|
58
|
+
configuration.signingURL = ""; // Disable URL signing for tests
|
|
58
59
|
const host = document.createElement("ef-video");
|
|
59
60
|
configuration.appendChild(host);
|
|
60
61
|
host.src = "http://web:3000/head-moov-480p.mp4";
|
|
62
|
+
document.body.appendChild(configuration);
|
|
61
63
|
await use(host);
|
|
64
|
+
configuration.remove();
|
|
62
65
|
},
|
|
63
66
|
urlGenerator: async ({}, use: any) => {
|
|
64
67
|
// UrlGenerator points to integrated proxy server (same host/port as test runner)
|
package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts
CHANGED
|
@@ -145,7 +145,7 @@ describe("Audio Seek Task - Chunk Boundary Regression Test", () => {
|
|
|
145
145
|
|
|
146
146
|
// Now trigger the localStorage restoration that happens in waitForMediaDurations().then()
|
|
147
147
|
// This will load currentTime = 4.0 from localStorage, jumping from 0ms to 4000ms
|
|
148
|
-
const loadedTime = timegroup.
|
|
148
|
+
const loadedTime = timegroup.loadTimeFromLocalStorage();
|
|
149
149
|
if (loadedTime !== undefined) {
|
|
150
150
|
timegroup.currentTime = loadedTime;
|
|
151
151
|
}
|
|
@@ -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,9 @@
|
|
|
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";
|
|
4
7
|
import { withSpan } from "../otel/tracingHelpers.js";
|
|
5
8
|
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
6
9
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
@@ -17,6 +20,7 @@ import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.ts";
|
|
|
17
20
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
18
21
|
import { EFTemporal } from "./EFTemporal.js";
|
|
19
22
|
import { FetchMixin } from "./FetchMixin.js";
|
|
23
|
+
import { renderTemporalAudio } from "./renderTemporalAudio.js";
|
|
20
24
|
import { EFTargetable } from "./TargetController.ts";
|
|
21
25
|
|
|
22
26
|
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
@@ -47,6 +51,11 @@ export class EFMedia extends EFTargetable(
|
|
|
47
51
|
assetType: "isobmff_files",
|
|
48
52
|
}),
|
|
49
53
|
) {
|
|
54
|
+
@provide({ context: efContext })
|
|
55
|
+
get efContext(): ControllableInterface | null {
|
|
56
|
+
return this.rootTimegroup ?? this;
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
// Sample buffer size configuration
|
|
51
60
|
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
52
61
|
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
@@ -249,13 +258,6 @@ export class EFMedia extends EFTargetable(
|
|
|
249
258
|
}
|
|
250
259
|
}
|
|
251
260
|
}
|
|
252
|
-
|
|
253
|
-
// if (
|
|
254
|
-
// changedProperties.has("currentTime") ||
|
|
255
|
-
// changedProperties.has("ownCurrentTimeMs")
|
|
256
|
-
// ) {
|
|
257
|
-
// updateAnimations(this);
|
|
258
|
-
// }
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
get hasOwnDuration() {
|
|
@@ -307,4 +309,33 @@ export class EFMedia extends EFTargetable(
|
|
|
307
309
|
},
|
|
308
310
|
);
|
|
309
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);
|
|
340
|
+
}
|
|
310
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()
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { LitElement } from "lit";
|
|
2
2
|
import { customElement } from "lit/decorators/custom-element.js";
|
|
3
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { describe, expect, test, vi } from "vitest";
|
|
4
4
|
import { EFTemporal } from "./EFTemporal.js";
|
|
5
|
+
import "./EFTimegroup.js";
|
|
6
|
+
import { state } from "lit/decorators.js";
|
|
5
7
|
|
|
6
8
|
@customElement("ten-seconds")
|
|
7
9
|
class TenSeconds extends EFTemporal(LitElement) {
|
|
@@ -156,3 +158,58 @@ describe("EFVideo sourcein attribute", () => {
|
|
|
156
158
|
expect(element2.tagName).toBe("EF-VIDEO");
|
|
157
159
|
});
|
|
158
160
|
});
|
|
161
|
+
|
|
162
|
+
@customElement("test-root-lifecycle")
|
|
163
|
+
class TestLifecycleChild extends EFTemporal(LitElement) {
|
|
164
|
+
@state()
|
|
165
|
+
role: "root" | "child" | null = null;
|
|
166
|
+
|
|
167
|
+
didBecomeRoot() {
|
|
168
|
+
this.role = "root";
|
|
169
|
+
}
|
|
170
|
+
didBecomeChild() {
|
|
171
|
+
this.role = "child";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
declare global {
|
|
176
|
+
interface HTMLElementTagNameMap {
|
|
177
|
+
"test-root-lifecycle": TestLifecycleChild;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
describe("Temporal Lifecycle", () => {
|
|
182
|
+
test("a standalone temporal element becomes a root", async () => {
|
|
183
|
+
const root = document.createElement("test-root-lifecycle");
|
|
184
|
+
document.body.append(root);
|
|
185
|
+
expect(root.role).toBe("root");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("temporal element in a timegroup becomes a child", async () => {
|
|
189
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
190
|
+
vi.spyOn(timegroup as any, "didBecomeRoot");
|
|
191
|
+
vi.spyOn(timegroup as any, "didBecomeChild");
|
|
192
|
+
const child = document.createElement("test-root-lifecycle");
|
|
193
|
+
vi.spyOn(child as any, "didBecomeRoot");
|
|
194
|
+
vi.spyOn(child as any, "didBecomeChild");
|
|
195
|
+
timegroup.append(child);
|
|
196
|
+
document.body.append(timegroup);
|
|
197
|
+
expect((timegroup as any).didBecomeRoot).toHaveBeenCalledOnce();
|
|
198
|
+
expect((timegroup as any).didBecomeChild).not.toHaveBeenCalled();
|
|
199
|
+
expect((child as any).didBecomeChild).toHaveBeenCalledOnce();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("timegroup nested in a timegroup becomes a child", async () => {
|
|
203
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
204
|
+
const child = document.createElement("ef-timegroup");
|
|
205
|
+
vi.spyOn(timegroup as any, "didBecomeRoot");
|
|
206
|
+
vi.spyOn(timegroup as any, "didBecomeChild");
|
|
207
|
+
vi.spyOn(child as any, "didBecomeRoot");
|
|
208
|
+
vi.spyOn(child as any, "didBecomeChild");
|
|
209
|
+
timegroup.append(child);
|
|
210
|
+
document.body.append(timegroup);
|
|
211
|
+
expect((timegroup as any).didBecomeRoot).toHaveBeenCalledOnce();
|
|
212
|
+
expect((timegroup as any).didBecomeChild).not.toHaveBeenCalled();
|
|
213
|
+
expect((child as any).didBecomeChild).toHaveBeenCalledOnce();
|
|
214
|
+
});
|
|
215
|
+
});
|