@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
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { consume, createContext, provide } from "@lit/context";
|
|
1
|
+
import { ContextProvider, consume, createContext, provide } from "@lit/context";
|
|
2
2
|
import type { LitElement } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
isEFTemporal,
|
|
7
|
+
type TemporalMixinInterface,
|
|
8
|
+
} from "../elements/EFTemporal.js";
|
|
6
9
|
import { globalURLTokenDeduplicator } from "../transcoding/cache/URLTokenDeduplicator.js";
|
|
7
10
|
import { currentTimeContext } from "./currentTimeContext.js";
|
|
8
11
|
import { durationContext } from "./durationContext.js";
|
|
@@ -16,9 +19,8 @@ import { type FocusContext, focusContext } from "./focusContext.js";
|
|
|
16
19
|
import { focusedElementContext } from "./focusedElementContext.js";
|
|
17
20
|
import { loopContext, playingContext } from "./playingContext.js";
|
|
18
21
|
|
|
19
|
-
export const
|
|
20
|
-
"target-
|
|
21
|
-
);
|
|
22
|
+
export const targetTemporalContext =
|
|
23
|
+
createContext<TemporalMixinInterface | null>(Symbol("target-temporal"));
|
|
22
24
|
|
|
23
25
|
export declare class ContextMixinInterface extends LitElement {
|
|
24
26
|
signingURL?: string;
|
|
@@ -28,8 +30,8 @@ export declare class ContextMixinInterface extends LitElement {
|
|
|
28
30
|
loop: boolean;
|
|
29
31
|
currentTimeMs: number;
|
|
30
32
|
focusedElement?: HTMLElement;
|
|
31
|
-
|
|
32
|
-
play(): void
|
|
33
|
+
targetTemporal: TemporalMixinInterface | null;
|
|
34
|
+
play(): Promise<void>;
|
|
33
35
|
pause(): void;
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -58,6 +60,13 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
58
60
|
@state()
|
|
59
61
|
focusedElement?: HTMLElement;
|
|
60
62
|
|
|
63
|
+
#playingProvider!: ContextProvider<typeof playingContext>;
|
|
64
|
+
#loopProvider!: ContextProvider<typeof loopContext>;
|
|
65
|
+
#currentTimeMsProvider!: ContextProvider<typeof currentTimeContext>;
|
|
66
|
+
#targetTemporalProvider!: ContextProvider<typeof targetTemporalContext>;
|
|
67
|
+
|
|
68
|
+
#loop = false;
|
|
69
|
+
|
|
61
70
|
#apiHost?: string;
|
|
62
71
|
@property({ type: String, attribute: "api-host" })
|
|
63
72
|
get apiHost() {
|
|
@@ -71,11 +80,97 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
71
80
|
@provide({ context: efContext })
|
|
72
81
|
efContext = this;
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
#targetTemporal: TemporalMixinInterface | null = null;
|
|
84
|
+
|
|
75
85
|
@state()
|
|
76
|
-
|
|
86
|
+
get targetTemporal(): TemporalMixinInterface | null {
|
|
87
|
+
return this.#targetTemporal;
|
|
88
|
+
}
|
|
89
|
+
#controllerSubscribed = false;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Find the first root temporal element (recursively searches through children)
|
|
93
|
+
* Supports ef-timegroup, ef-video, ef-audio, and any other temporal elements
|
|
94
|
+
* even when they're wrapped in non-temporal elements like divs
|
|
95
|
+
*/
|
|
96
|
+
private findRootTemporal(): TemporalMixinInterface | null {
|
|
97
|
+
const findRecursive = (
|
|
98
|
+
element: Element,
|
|
99
|
+
): TemporalMixinInterface | null => {
|
|
100
|
+
if (isEFTemporal(element)) {
|
|
101
|
+
return element as TemporalMixinInterface & HTMLElement;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const child of element.children) {
|
|
105
|
+
const found = findRecursive(child);
|
|
106
|
+
if (found) return found;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const child of this.children) {
|
|
113
|
+
const found = findRecursive(child);
|
|
114
|
+
if (found) return found;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set targetTemporal(value: TemporalMixinInterface | null) {
|
|
121
|
+
if (this.#targetTemporal === value) return;
|
|
122
|
+
|
|
123
|
+
// Unsubscribe from old controller updates
|
|
124
|
+
if (this.#targetTemporal?.playbackController) {
|
|
125
|
+
this.#targetTemporal.playbackController.removeListener(
|
|
126
|
+
this.#onControllerUpdate,
|
|
127
|
+
);
|
|
128
|
+
this.#controllerSubscribed = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.#targetTemporal = value;
|
|
132
|
+
this.#targetTemporalProvider?.setValue(value);
|
|
133
|
+
|
|
134
|
+
// Sync all provided contexts
|
|
135
|
+
this.requestUpdate("targetTemporal");
|
|
136
|
+
this.requestUpdate("playing");
|
|
137
|
+
this.requestUpdate("loop");
|
|
138
|
+
this.requestUpdate("currentTimeMs");
|
|
139
|
+
|
|
140
|
+
// If the new targetTemporal has a playbackController, apply stored loop value immediately
|
|
141
|
+
if (value?.playbackController && this.#loop) {
|
|
142
|
+
value.playbackController.setLoop(this.#loop);
|
|
143
|
+
}
|
|
77
144
|
|
|
78
|
-
|
|
145
|
+
// If the new targetTemporal doesn't have a playbackController yet,
|
|
146
|
+
// wait for it to complete its updates (it might be initializing)
|
|
147
|
+
if (value && !value.playbackController) {
|
|
148
|
+
// Wait for the temporal element to initialize
|
|
149
|
+
(value as any).updateComplete?.then(() => {
|
|
150
|
+
if (value === this.#targetTemporal && !this.#controllerSubscribed) {
|
|
151
|
+
this.requestUpdate();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#onControllerUpdate = (
|
|
158
|
+
event: import("./PlaybackController.js").PlaybackControllerUpdateEvent,
|
|
159
|
+
) => {
|
|
160
|
+
switch (event.property) {
|
|
161
|
+
case "playing":
|
|
162
|
+
this.#playingProvider.setValue(event.value as boolean);
|
|
163
|
+
break;
|
|
164
|
+
case "loop":
|
|
165
|
+
this.#loopProvider.setValue(event.value as boolean);
|
|
166
|
+
break;
|
|
167
|
+
case "currentTimeMs":
|
|
168
|
+
this.#currentTimeMsProvider.setValue(event.value as number);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Add reactive properties that depend on the targetTemporal
|
|
79
174
|
@provide({ context: durationContext })
|
|
80
175
|
@property({ type: Number })
|
|
81
176
|
durationMs = 0;
|
|
@@ -236,39 +331,58 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
236
331
|
this.#signingURL = value;
|
|
237
332
|
}
|
|
238
333
|
|
|
239
|
-
@provide({ context: playingContext })
|
|
240
334
|
@property({ type: Boolean, reflect: true })
|
|
241
|
-
playing
|
|
335
|
+
get playing(): boolean {
|
|
336
|
+
return this.targetTemporal?.playbackController?.playing ?? false;
|
|
337
|
+
}
|
|
338
|
+
set playing(value: boolean) {
|
|
339
|
+
if (this.targetTemporal?.playbackController) {
|
|
340
|
+
this.targetTemporal.playbackController.setPlaying(value);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
242
343
|
|
|
243
|
-
@
|
|
244
|
-
|
|
245
|
-
|
|
344
|
+
@property({ type: Boolean, reflect: true, attribute: "loop" })
|
|
345
|
+
get loop(): boolean {
|
|
346
|
+
return this.targetTemporal?.playbackController?.loop ?? this.#loop;
|
|
347
|
+
}
|
|
348
|
+
set loop(value: boolean) {
|
|
349
|
+
const oldValue = this.#loop;
|
|
350
|
+
this.#loop = value;
|
|
351
|
+
if (this.targetTemporal?.playbackController) {
|
|
352
|
+
this.targetTemporal.playbackController.setLoop(value);
|
|
353
|
+
}
|
|
354
|
+
this.requestUpdate("loop", oldValue);
|
|
355
|
+
}
|
|
246
356
|
|
|
247
357
|
@property({ type: Boolean })
|
|
248
358
|
rendering = false;
|
|
249
359
|
|
|
250
|
-
@provide({ context: currentTimeContext })
|
|
251
360
|
@property({ type: Number })
|
|
252
|
-
currentTimeMs
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
361
|
+
get currentTimeMs(): number {
|
|
362
|
+
return (
|
|
363
|
+
this.targetTemporal?.playbackController?.currentTimeMs ?? Number.NaN
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
set currentTimeMs(value: number) {
|
|
367
|
+
if (this.targetTemporal?.playbackController) {
|
|
368
|
+
this.targetTemporal.playbackController.setCurrentTimeMs(value);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
256
371
|
|
|
257
372
|
#timegroupObserver = new MutationObserver((mutations) => {
|
|
258
373
|
let shouldUpdate = false;
|
|
259
374
|
|
|
260
375
|
for (const mutation of mutations) {
|
|
261
376
|
if (mutation.type === "childList") {
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
this.
|
|
377
|
+
const newTemporal = this.findRootTemporal();
|
|
378
|
+
if (newTemporal !== this.targetTemporal) {
|
|
379
|
+
this.targetTemporal = newTemporal;
|
|
265
380
|
shouldUpdate = true;
|
|
266
381
|
} else if (
|
|
267
382
|
mutation.target instanceof Element &&
|
|
268
|
-
(mutation.target
|
|
269
|
-
mutation.target.closest("ef-timegroup"))
|
|
383
|
+
isEFTemporal(mutation.target)
|
|
270
384
|
) {
|
|
271
|
-
// Handle childList changes within existing
|
|
385
|
+
// Handle childList changes within existing temporal elements
|
|
272
386
|
shouldUpdate = true;
|
|
273
387
|
}
|
|
274
388
|
} else if (mutation.type === "attributes") {
|
|
@@ -287,11 +401,7 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
287
401
|
mutation.attributeName || "",
|
|
288
402
|
) ||
|
|
289
403
|
(mutation.target instanceof Element &&
|
|
290
|
-
(mutation.target
|
|
291
|
-
mutation.target.tagName === "EF-VIDEO" ||
|
|
292
|
-
mutation.target.tagName === "EF-AUDIO" ||
|
|
293
|
-
mutation.target.tagName === "EF-CAPTIONS" ||
|
|
294
|
-
mutation.target.closest("ef-timegroup")))
|
|
404
|
+
isEFTemporal(mutation.target))
|
|
295
405
|
) {
|
|
296
406
|
shouldUpdate = true;
|
|
297
407
|
}
|
|
@@ -302,23 +412,23 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
302
412
|
// Trigger an update to ensure reactive properties recalculate
|
|
303
413
|
// Use a microtask to ensure DOM updates are complete
|
|
304
414
|
queueMicrotask(() => {
|
|
305
|
-
// Recalculate duration and endTime when
|
|
415
|
+
// Recalculate duration and endTime when temporal element changes
|
|
306
416
|
this.updateDurationProperties();
|
|
307
417
|
this.requestUpdate();
|
|
308
|
-
// Also ensure the
|
|
309
|
-
if (this.
|
|
310
|
-
this.
|
|
418
|
+
// Also ensure the targetTemporal updates its computed properties
|
|
419
|
+
if (this.targetTemporal) {
|
|
420
|
+
(this.targetTemporal as any).requestUpdate();
|
|
311
421
|
}
|
|
312
422
|
});
|
|
313
423
|
}
|
|
314
424
|
});
|
|
315
425
|
|
|
316
426
|
/**
|
|
317
|
-
* Update duration properties when
|
|
427
|
+
* Update duration properties when temporal element changes
|
|
318
428
|
*/
|
|
319
429
|
updateDurationProperties(): void {
|
|
320
|
-
const newDuration = this.
|
|
321
|
-
const newEndTime = this.
|
|
430
|
+
const newDuration = this.targetTemporal?.durationMs ?? 0;
|
|
431
|
+
const newEndTime = this.targetTemporal?.endTimeMs ?? 0;
|
|
322
432
|
|
|
323
433
|
if (this.durationMs !== newDuration) {
|
|
324
434
|
this.durationMs = newDuration;
|
|
@@ -332,8 +442,26 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
332
442
|
connectedCallback(): void {
|
|
333
443
|
super.connectedCallback();
|
|
334
444
|
|
|
335
|
-
//
|
|
336
|
-
this
|
|
445
|
+
// Create manual context providers for playback state
|
|
446
|
+
this.#playingProvider = new ContextProvider(this, {
|
|
447
|
+
context: playingContext,
|
|
448
|
+
initialValue: this.playing,
|
|
449
|
+
});
|
|
450
|
+
this.#loopProvider = new ContextProvider(this, {
|
|
451
|
+
context: loopContext,
|
|
452
|
+
initialValue: this.loop,
|
|
453
|
+
});
|
|
454
|
+
this.#currentTimeMsProvider = new ContextProvider(this, {
|
|
455
|
+
context: currentTimeContext,
|
|
456
|
+
initialValue: this.currentTimeMs,
|
|
457
|
+
});
|
|
458
|
+
this.#targetTemporalProvider = new ContextProvider(this, {
|
|
459
|
+
context: targetTemporalContext,
|
|
460
|
+
initialValue: this.targetTemporal,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Initialize targetTemporal to first root temporal element
|
|
464
|
+
this.targetTemporal = this.findRootTemporal();
|
|
337
465
|
// Initialize duration properties
|
|
338
466
|
this.updateDurationProperties();
|
|
339
467
|
|
|
@@ -342,164 +470,91 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
342
470
|
subtree: true,
|
|
343
471
|
attributes: true,
|
|
344
472
|
});
|
|
345
|
-
|
|
346
|
-
if (this.playing) {
|
|
347
|
-
this.startPlayback();
|
|
348
|
-
}
|
|
349
473
|
}
|
|
350
474
|
|
|
351
475
|
disconnectedCallback(): void {
|
|
352
476
|
super.disconnectedCallback();
|
|
353
477
|
this.#timegroupObserver.disconnect();
|
|
354
|
-
this.stopPlayback();
|
|
355
|
-
// Note: Global token cache is shared across all context providers
|
|
356
|
-
// No need to clear per-instance cache on disconnect
|
|
357
|
-
}
|
|
358
478
|
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
this
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (
|
|
368
|
-
changedProperties.has("currentTimeMs") &&
|
|
369
|
-
this.targetTimegroup &&
|
|
370
|
-
!Number.isNaN(this.currentTimeMs)
|
|
371
|
-
) {
|
|
372
|
-
if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
|
|
373
|
-
if (this.isConnected) {
|
|
374
|
-
if (this.targetTimegroup.currentTimeMs === this.currentTimeMs) {
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
this.targetTimegroup.currentTimeMs = this.currentTimeMs;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
479
|
+
// Unsubscribe from controller
|
|
480
|
+
if (this.#targetTemporal?.playbackController) {
|
|
481
|
+
this.#targetTemporal.playbackController.removeListener(
|
|
482
|
+
this.#onControllerUpdate,
|
|
483
|
+
);
|
|
484
|
+
this.#controllerSubscribed = false;
|
|
380
485
|
}
|
|
381
|
-
super.update(changedProperties);
|
|
382
|
-
}
|
|
383
486
|
|
|
384
|
-
|
|
385
|
-
this.playing = true;
|
|
487
|
+
this.pause();
|
|
386
488
|
}
|
|
387
489
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
490
|
+
updated(changedProperties: Map<string | number | symbol, unknown>) {
|
|
491
|
+
super.updated?.(changedProperties);
|
|
391
492
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (nextTimeMs !== this.currentTimeMs) {
|
|
402
|
-
this.currentTimeMs = nextTimeMs;
|
|
403
|
-
}
|
|
404
|
-
this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
|
|
405
|
-
this.#syncPlayheadToAudioContext(target, startMs);
|
|
406
|
-
});
|
|
407
|
-
}
|
|
493
|
+
// Subscribe to controller when it becomes available
|
|
494
|
+
if (
|
|
495
|
+
!this.#controllerSubscribed &&
|
|
496
|
+
this.#targetTemporal?.playbackController
|
|
497
|
+
) {
|
|
498
|
+
this.#targetTemporal.playbackController.addListener(
|
|
499
|
+
this.#onControllerUpdate,
|
|
500
|
+
);
|
|
501
|
+
this.#controllerSubscribed = true;
|
|
408
502
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
await this.#playbackAudioContext.close();
|
|
503
|
+
// Apply stored loop value when playbackController becomes available
|
|
504
|
+
if (this.#loop) {
|
|
505
|
+
this.#targetTemporal.playbackController.setLoop(this.#loop);
|
|
413
506
|
}
|
|
414
|
-
}
|
|
415
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
416
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
417
|
-
}
|
|
418
|
-
this.#playbackAudioContext = null;
|
|
419
|
-
this.#playbackAnimationFrameRequest = null;
|
|
420
|
-
}
|
|
421
507
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return;
|
|
508
|
+
// Trigger initial sync of context providers
|
|
509
|
+
this.#playingProvider.setValue(this.playing);
|
|
510
|
+
this.#loopProvider.setValue(this.loop);
|
|
511
|
+
this.#currentTimeMsProvider.setValue(this.currentTimeMs);
|
|
427
512
|
}
|
|
513
|
+
}
|
|
428
514
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
latencyHint: "playback",
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
446
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
447
|
-
}
|
|
448
|
-
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
449
|
-
const playbackContext = this.#playbackAudioContext;
|
|
450
|
-
if (playbackContext.state === "suspended") {
|
|
451
|
-
console.warn(
|
|
452
|
-
"AudioContext is suspended, media playback will not work until user has interacted with page.",
|
|
515
|
+
async play() {
|
|
516
|
+
// If targetTemporal is not set, try to find it now
|
|
517
|
+
// This handles cases where the DOM may not have been fully ready during connectedCallback
|
|
518
|
+
if (!this.targetTemporal) {
|
|
519
|
+
// Wait for any temporal custom elements to be defined
|
|
520
|
+
const potentialTemporalTags = Array.from(this.children)
|
|
521
|
+
.map((el) => el.tagName.toLowerCase())
|
|
522
|
+
.filter((tag) => tag.startsWith("ef-"));
|
|
523
|
+
|
|
524
|
+
await Promise.all(
|
|
525
|
+
potentialTemporalTags.map((tag) =>
|
|
526
|
+
customElements.whenDefined(tag).catch(() => {}),
|
|
527
|
+
),
|
|
453
528
|
);
|
|
454
|
-
this.playing = false;
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
await playbackContext.suspend();
|
|
458
529
|
|
|
459
|
-
|
|
460
|
-
if (
|
|
530
|
+
const foundTemporal = this.findRootTemporal();
|
|
531
|
+
if (foundTemporal) {
|
|
532
|
+
this.targetTemporal = foundTemporal;
|
|
533
|
+
// Wait for it to initialize
|
|
534
|
+
await (foundTemporal as any).updateComplete;
|
|
535
|
+
} else {
|
|
536
|
+
console.warn("No temporal element found to play");
|
|
461
537
|
return;
|
|
462
538
|
}
|
|
463
|
-
|
|
464
|
-
if (canFillBuffer) {
|
|
465
|
-
fillBuffer();
|
|
466
|
-
}
|
|
467
|
-
};
|
|
539
|
+
}
|
|
468
540
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
541
|
+
// If playbackController doesn't exist yet, wait for it
|
|
542
|
+
if (!this.targetTemporal.playbackController) {
|
|
543
|
+
await (this.targetTemporal as any).updateComplete;
|
|
544
|
+
// After waiting, check again
|
|
545
|
+
if (!this.targetTemporal.playbackController) {
|
|
546
|
+
console.warn("PlaybackController not available for temporal element");
|
|
547
|
+
return;
|
|
472
548
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
bufferCount++;
|
|
478
|
-
const source = playbackContext.createBufferSource();
|
|
479
|
-
source.buffer = audioBuffer;
|
|
480
|
-
source.connect(playbackContext.destination);
|
|
481
|
-
source.start((startMs - fromMs) / 1000);
|
|
482
|
-
source.onended = () => {
|
|
483
|
-
bufferCount--;
|
|
484
|
-
if (endMs >= toMs) {
|
|
485
|
-
this.pause();
|
|
486
|
-
if (this.loop) {
|
|
487
|
-
this.updateComplete.then(() => {
|
|
488
|
-
this.currentTimeMs = 0;
|
|
489
|
-
this.updateComplete.then(() => {
|
|
490
|
-
this.play();
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
} else {
|
|
495
|
-
fillBuffer();
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
return true;
|
|
499
|
-
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.targetTemporal.playbackController.play();
|
|
552
|
+
}
|
|
500
553
|
|
|
501
|
-
|
|
502
|
-
|
|
554
|
+
pause() {
|
|
555
|
+
if (this.targetTemporal?.playbackController) {
|
|
556
|
+
this.targetTemporal.playbackController.pause();
|
|
557
|
+
}
|
|
503
558
|
}
|
|
504
559
|
}
|
|
505
560
|
|