@editframe/elements 0.14.0-beta.3 → 0.15.0-beta.10
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 +0 -2
- package/dist/elements/EFAudio.d.ts +0 -1
- package/dist/elements/EFAudio.js +1 -5
- package/dist/elements/EFCaptions.js +1 -1
- package/dist/elements/EFImage.d.ts +2 -1
- package/dist/elements/EFImage.js +9 -3
- package/dist/elements/EFMedia.d.ts +8 -0
- package/dist/elements/EFMedia.js +247 -8
- package/dist/elements/EFTemporal.d.ts +7 -3
- package/dist/elements/EFTemporal.js +19 -1
- package/dist/elements/EFTimegroup.d.ts +1 -5
- package/dist/elements/EFTimegroup.js +5 -6
- package/dist/elements/EFWaveform.d.ts +16 -7
- package/dist/elements/EFWaveform.js +273 -163
- package/dist/elements/TargetController.d.ts +25 -0
- package/dist/elements/TargetController.js +164 -0
- package/dist/elements/TargetController.test.d.ts +19 -0
- package/dist/gui/EFPreview.d.ts +1 -1
- package/dist/gui/EFPreview.js +1 -0
- package/dist/gui/EFWorkbench.js +1 -1
- package/dist/gui/TWMixin.css.js +1 -1
- package/dist/style.css +3 -0
- package/package.json +10 -4
- package/src/elements/EFAudio.ts +1 -4
- package/src/elements/EFCaptions.ts +1 -1
- package/src/elements/EFImage.browsertest.ts +33 -2
- package/src/elements/EFImage.ts +10 -3
- package/src/elements/EFMedia.ts +304 -6
- package/src/elements/EFTemporal.ts +37 -5
- package/src/elements/EFTimegroup.ts +5 -7
- package/src/elements/EFWaveform.ts +341 -194
- package/src/elements/TargetController.test.ts +229 -0
- package/src/elements/TargetController.ts +219 -0
- package/src/gui/EFPreview.ts +10 -9
- package/src/gui/EFWorkbench.ts +1 -1
- package/types.json +1 -0
package/src/elements/EFMedia.ts
CHANGED
|
@@ -14,9 +14,44 @@ import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
|
14
14
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
15
15
|
import { EFTemporal, isEFTemporal } from "./EFTemporal.js";
|
|
16
16
|
import { FetchMixin } from "./FetchMixin.js";
|
|
17
|
+
import { EFTargetable } from "./TargetController.ts";
|
|
17
18
|
|
|
18
19
|
const log = debug("ef:elements:EFMedia");
|
|
19
20
|
|
|
21
|
+
const freqWeightsCache = new Map<number, Float32Array>();
|
|
22
|
+
|
|
23
|
+
class LRUCache<K, V> {
|
|
24
|
+
private cache = new Map<K, V>();
|
|
25
|
+
private readonly maxSize: number;
|
|
26
|
+
|
|
27
|
+
constructor(maxSize: number) {
|
|
28
|
+
this.maxSize = maxSize;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get(key: K): V | undefined {
|
|
32
|
+
const value = this.cache.get(key);
|
|
33
|
+
if (value) {
|
|
34
|
+
// Refresh position by removing and re-adding
|
|
35
|
+
this.cache.delete(key);
|
|
36
|
+
this.cache.set(key, value);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(key: K, value: V): void {
|
|
42
|
+
if (this.cache.has(key)) {
|
|
43
|
+
this.cache.delete(key);
|
|
44
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
45
|
+
// Remove oldest entry (first item in map)
|
|
46
|
+
const firstKey = this.cache.keys().next().value;
|
|
47
|
+
if (firstKey) {
|
|
48
|
+
this.cache.delete(firstKey);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
this.cache.set(key, value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
20
55
|
export const deepGetMediaElements = (
|
|
21
56
|
element: Element,
|
|
22
57
|
medias: EFMedia[] = [],
|
|
@@ -31,9 +66,11 @@ export const deepGetMediaElements = (
|
|
|
31
66
|
return medias;
|
|
32
67
|
};
|
|
33
68
|
|
|
34
|
-
export class EFMedia extends
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
export class EFMedia extends EFTargetable(
|
|
70
|
+
EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
71
|
+
assetType: "isobmff_files",
|
|
72
|
+
}),
|
|
73
|
+
) {
|
|
37
74
|
static styles = [
|
|
38
75
|
css`
|
|
39
76
|
:host {
|
|
@@ -284,7 +321,8 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
284
321
|
},
|
|
285
322
|
});
|
|
286
323
|
|
|
287
|
-
@state()
|
|
324
|
+
@state()
|
|
325
|
+
desiredSeekTimeMs = 0;
|
|
288
326
|
|
|
289
327
|
protected async executeSeek(seekToMs: number) {
|
|
290
328
|
this.desiredSeekTimeMs = seekToMs;
|
|
@@ -294,7 +332,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
294
332
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
295
333
|
): void {
|
|
296
334
|
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
297
|
-
this.executeSeek(this.
|
|
335
|
+
this.executeSeek(this.currentSourceTimeMs);
|
|
298
336
|
}
|
|
299
337
|
// TODO: this is copied straight from EFTimegroup.ts
|
|
300
338
|
// and should be refactored to be shared/reduce bad duplication of
|
|
@@ -312,7 +350,6 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
312
350
|
return;
|
|
313
351
|
}
|
|
314
352
|
this.style.display = "";
|
|
315
|
-
|
|
316
353
|
const animations = this.getAnimations({ subtree: true });
|
|
317
354
|
|
|
318
355
|
this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
|
|
@@ -546,4 +583,265 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
546
583
|
this.trimEndMs,
|
|
547
584
|
};
|
|
548
585
|
}
|
|
586
|
+
|
|
587
|
+
@property({ type: Number })
|
|
588
|
+
fftSize = 512; // Default FFT size
|
|
589
|
+
|
|
590
|
+
@property({ type: Number })
|
|
591
|
+
fftDecay = 8; // Default number of frames to analyze
|
|
592
|
+
|
|
593
|
+
private static readonly MIN_DB = -90;
|
|
594
|
+
private static readonly MAX_DB = -20;
|
|
595
|
+
private static readonly DECAY_WEIGHT = 0.7;
|
|
596
|
+
|
|
597
|
+
// Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
|
|
598
|
+
get FREQ_WEIGHTS() {
|
|
599
|
+
if (freqWeightsCache.has(this.fftSize)) {
|
|
600
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to prior has check
|
|
601
|
+
return freqWeightsCache.get(this.fftSize)!;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
|
|
605
|
+
const frequency = (i * 48000) / this.fftSize;
|
|
606
|
+
if (frequency < 60) return 0.3;
|
|
607
|
+
if (frequency < 250) return 0.4;
|
|
608
|
+
if (frequency < 500) return 0.6;
|
|
609
|
+
if (frequency < 2000) return 0.8;
|
|
610
|
+
if (frequency < 4000) return 1.2;
|
|
611
|
+
if (frequency < 8000) return 1.6;
|
|
612
|
+
return 2.0;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
freqWeightsCache.set(this.fftSize, weights);
|
|
616
|
+
return weights;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#byteTimeDomainCache = new LRUCache<string, Uint8Array>(100);
|
|
620
|
+
|
|
621
|
+
byteTimeDomainTask = new Task(this, {
|
|
622
|
+
autoRun: EF_INTERACTIVE,
|
|
623
|
+
args: () =>
|
|
624
|
+
[
|
|
625
|
+
this.audioBufferTask.status,
|
|
626
|
+
this.currentSourceTimeMs,
|
|
627
|
+
this.fftSize,
|
|
628
|
+
this.fftDecay,
|
|
629
|
+
] as const,
|
|
630
|
+
task: async () => {
|
|
631
|
+
await this.audioBufferTask.taskComplete;
|
|
632
|
+
if (!this.audioBufferTask.value) return null;
|
|
633
|
+
if (this.currentSourceTimeMs <= 0) return null;
|
|
634
|
+
|
|
635
|
+
const currentTimeMs = this.currentSourceTimeMs;
|
|
636
|
+
const startOffsetMs = this.audioBufferTask.value.startOffsetMs;
|
|
637
|
+
const audioBuffer = this.audioBufferTask.value.buffer;
|
|
638
|
+
const smoothedKey = `${this.fftSize}:${this.fftDecay}:${startOffsetMs}:${currentTimeMs}`;
|
|
639
|
+
|
|
640
|
+
const cachedSmoothedData = this.#byteTimeDomainCache.get(smoothedKey);
|
|
641
|
+
if (cachedSmoothedData) {
|
|
642
|
+
return cachedSmoothedData;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const framesData = await Promise.all(
|
|
646
|
+
Array.from({ length: this.fftDecay }, async (_, i) => {
|
|
647
|
+
const frameOffset = i * (1000 / 30);
|
|
648
|
+
const startTime = Math.max(
|
|
649
|
+
0,
|
|
650
|
+
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const cacheKey = `${this.fftSize}:${startOffsetMs}:${startTime}`;
|
|
654
|
+
const cachedFrame = this.#byteTimeDomainCache.get(cacheKey);
|
|
655
|
+
if (cachedFrame) {
|
|
656
|
+
return cachedFrame;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const audioContext = new OfflineAudioContext(
|
|
660
|
+
2,
|
|
661
|
+
48000 * (1 / 30),
|
|
662
|
+
48000,
|
|
663
|
+
);
|
|
664
|
+
const analyser = audioContext.createAnalyser();
|
|
665
|
+
analyser.fftSize = this.fftSize;
|
|
666
|
+
|
|
667
|
+
// Increase gain even more for better signal
|
|
668
|
+
const gainNode = audioContext.createGain();
|
|
669
|
+
gainNode.gain.value = 10.0; // Try a higher gain
|
|
670
|
+
|
|
671
|
+
// More aggressive settings for the analyzer
|
|
672
|
+
analyser.smoothingTimeConstant = 0.4;
|
|
673
|
+
analyser.minDecibels = -90;
|
|
674
|
+
analyser.maxDecibels = -10;
|
|
675
|
+
|
|
676
|
+
const audioBufferSource = audioContext.createBufferSource();
|
|
677
|
+
audioBufferSource.buffer = audioBuffer;
|
|
678
|
+
|
|
679
|
+
// Add a bandpass filter to focus on the most active frequency ranges
|
|
680
|
+
const filter = audioContext.createBiquadFilter();
|
|
681
|
+
filter.type = "bandpass";
|
|
682
|
+
filter.frequency.value = 1000; // Center frequency in Hz
|
|
683
|
+
filter.Q.value = 0.5; // Width of the band
|
|
684
|
+
|
|
685
|
+
audioBufferSource.connect(gainNode);
|
|
686
|
+
gainNode.connect(filter);
|
|
687
|
+
filter.connect(analyser);
|
|
688
|
+
analyser.connect(audioContext.destination);
|
|
689
|
+
|
|
690
|
+
audioBufferSource.start(0, startTime, 1 / 30);
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
await audioContext.startRendering();
|
|
694
|
+
// Change to time domain data
|
|
695
|
+
const frameData = new Uint8Array(analyser.fftSize);
|
|
696
|
+
analyser.getByteTimeDomainData(frameData);
|
|
697
|
+
|
|
698
|
+
this.#byteTimeDomainCache.set(cacheKey, frameData);
|
|
699
|
+
return frameData;
|
|
700
|
+
} finally {
|
|
701
|
+
audioBufferSource.disconnect();
|
|
702
|
+
analyser.disconnect();
|
|
703
|
+
}
|
|
704
|
+
}),
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
const frameLength = framesData[0]?.length ?? 0;
|
|
708
|
+
const smoothedData = new Uint8Array(frameLength);
|
|
709
|
+
|
|
710
|
+
// Combine frames with decay
|
|
711
|
+
for (let i = 0; i < frameLength; i++) {
|
|
712
|
+
let weightedSum = 0;
|
|
713
|
+
let weightSum = 0;
|
|
714
|
+
|
|
715
|
+
framesData.forEach((frame, frameIndex) => {
|
|
716
|
+
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
717
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to forEach
|
|
718
|
+
weightedSum += frame[i]! * decayWeight;
|
|
719
|
+
weightSum += decayWeight;
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Remove frequency weighting since we're using time domain data
|
|
726
|
+
// No need to slice the data either since we want the full waveform
|
|
727
|
+
|
|
728
|
+
this.#byteTimeDomainCache.set(
|
|
729
|
+
smoothedKey,
|
|
730
|
+
smoothedData.slice(0, Math.floor(smoothedData.length * 0.8)),
|
|
731
|
+
);
|
|
732
|
+
return smoothedData;
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
#frequencyDataCache = new LRUCache<string, Uint8Array>(100);
|
|
737
|
+
|
|
738
|
+
frequencyDataTask = new Task(this, {
|
|
739
|
+
autoRun: EF_INTERACTIVE,
|
|
740
|
+
args: () =>
|
|
741
|
+
[
|
|
742
|
+
this.audioBufferTask.status,
|
|
743
|
+
this.currentSourceTimeMs,
|
|
744
|
+
this.fftSize, // Add fftSize to dependency array
|
|
745
|
+
this.fftDecay, // Add fftDecay to dependency array
|
|
746
|
+
] as const,
|
|
747
|
+
task: async () => {
|
|
748
|
+
await this.audioBufferTask.taskComplete;
|
|
749
|
+
if (!this.audioBufferTask.value) return null;
|
|
750
|
+
if (this.currentSourceTimeMs <= 0) return null;
|
|
751
|
+
|
|
752
|
+
const currentTimeMs = this.currentSourceTimeMs;
|
|
753
|
+
const startOffsetMs = this.audioBufferTask.value.startOffsetMs;
|
|
754
|
+
const audioBuffer = this.audioBufferTask.value.buffer;
|
|
755
|
+
const smoothedKey = `${this.fftSize}:${this.fftDecay}:${startOffsetMs}:${currentTimeMs}`;
|
|
756
|
+
|
|
757
|
+
const cachedSmoothedData = this.#frequencyDataCache.get(smoothedKey);
|
|
758
|
+
if (cachedSmoothedData) {
|
|
759
|
+
return cachedSmoothedData;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const framesData = await Promise.all(
|
|
763
|
+
Array.from({ length: this.fftDecay }, async (_, i) => {
|
|
764
|
+
const frameOffset = i * (1000 / 30);
|
|
765
|
+
const startTime = Math.max(
|
|
766
|
+
0,
|
|
767
|
+
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Cache key for this specific frame
|
|
771
|
+
const cacheKey = `${this.fftSize}:${startOffsetMs}:${startTime}`;
|
|
772
|
+
|
|
773
|
+
// Check cache for this specific frame
|
|
774
|
+
const cachedFrame = this.#frequencyDataCache.get(cacheKey);
|
|
775
|
+
if (cachedFrame) {
|
|
776
|
+
return cachedFrame;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const audioContext = new OfflineAudioContext(
|
|
780
|
+
2,
|
|
781
|
+
48000 * (1 / 30),
|
|
782
|
+
48000,
|
|
783
|
+
);
|
|
784
|
+
const analyser = audioContext.createAnalyser();
|
|
785
|
+
analyser.fftSize = this.fftSize;
|
|
786
|
+
analyser.minDecibels = EFMedia.MIN_DB;
|
|
787
|
+
analyser.maxDecibels = EFMedia.MAX_DB;
|
|
788
|
+
|
|
789
|
+
const audioBufferSource = audioContext.createBufferSource();
|
|
790
|
+
audioBufferSource.buffer = audioBuffer;
|
|
791
|
+
|
|
792
|
+
audioBufferSource.connect(analyser);
|
|
793
|
+
analyser.connect(audioContext.destination);
|
|
794
|
+
|
|
795
|
+
audioBufferSource.start(0, startTime, 1 / 30);
|
|
796
|
+
|
|
797
|
+
try {
|
|
798
|
+
await audioContext.startRendering();
|
|
799
|
+
const frameData = new Uint8Array(this.fftSize / 2);
|
|
800
|
+
analyser.getByteFrequencyData(frameData);
|
|
801
|
+
|
|
802
|
+
// Cache this frame's analysis
|
|
803
|
+
this.#frequencyDataCache.set(cacheKey, frameData);
|
|
804
|
+
return frameData;
|
|
805
|
+
} finally {
|
|
806
|
+
audioBufferSource.disconnect();
|
|
807
|
+
analyser.disconnect();
|
|
808
|
+
}
|
|
809
|
+
}),
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
const frameLength = framesData[0]?.length ?? 0;
|
|
813
|
+
|
|
814
|
+
// Combine frames with decay
|
|
815
|
+
const smoothedData = new Uint8Array(frameLength);
|
|
816
|
+
for (let i = 0; i < frameLength; i++) {
|
|
817
|
+
let weightedSum = 0;
|
|
818
|
+
let weightSum = 0;
|
|
819
|
+
|
|
820
|
+
framesData.forEach((frame, frameIndex) => {
|
|
821
|
+
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
822
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to forEach
|
|
823
|
+
weightedSum += frame[i]! * decayWeight;
|
|
824
|
+
weightSum += decayWeight;
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Apply frequency weights using instance FREQ_WEIGHTS
|
|
831
|
+
smoothedData.forEach((value, i) => {
|
|
832
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to forEach
|
|
833
|
+
const freqWeight = this.FREQ_WEIGHTS[i]!;
|
|
834
|
+
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// Only return the lower half of the frequency data
|
|
838
|
+
// The top half is zeroed out, which makes for aesthetically unpleasing waveforms
|
|
839
|
+
const slicedData = smoothedData.slice(
|
|
840
|
+
0,
|
|
841
|
+
Math.floor(smoothedData.length / 2),
|
|
842
|
+
);
|
|
843
|
+
this.#frequencyDataCache.set(smoothedKey, slicedData);
|
|
844
|
+
return slicedData;
|
|
845
|
+
},
|
|
846
|
+
});
|
|
549
847
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { consume, createContext } from "@lit/context";
|
|
2
|
-
import type { LitElement, ReactiveController } from "lit";
|
|
2
|
+
import type { LitElement, PropertyValueMap, ReactiveController } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
import type { EFTimegroup } from "./EFTimegroup.js";
|
|
5
5
|
|
|
@@ -13,6 +13,10 @@ export const timegroupContext = createContext<EFTimegroup>(
|
|
|
13
13
|
|
|
14
14
|
export declare class TemporalMixinInterface {
|
|
15
15
|
get hasOwnDuration(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the element has a duration set as an attribute.
|
|
18
|
+
*/
|
|
19
|
+
get hasExplicitDuration(): boolean;
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Used to trim the start of the media.
|
|
@@ -147,18 +151,18 @@ export declare class TemporalMixinInterface {
|
|
|
147
151
|
* elements.
|
|
148
152
|
*
|
|
149
153
|
* For example, if the media has a `sourcein` value of 10s, when `ownCurrentTimeMs` is 0s,
|
|
150
|
-
* `
|
|
154
|
+
* `currentSourceTimeMs` will be 10s.
|
|
151
155
|
*
|
|
152
156
|
* sourcein=10s sourceout=10s
|
|
153
157
|
* / / /
|
|
154
158
|
* |--------|=================|---------|
|
|
155
159
|
* ^
|
|
156
160
|
* |_
|
|
157
|
-
*
|
|
161
|
+
* currentSourceTimeMs === 10s
|
|
158
162
|
* |_
|
|
159
163
|
* ownCurrentTimeMs === 0s
|
|
160
164
|
*/
|
|
161
|
-
get
|
|
165
|
+
get currentSourceTimeMs(): number;
|
|
162
166
|
|
|
163
167
|
set duration(value: string);
|
|
164
168
|
get duration(): string;
|
|
@@ -461,6 +465,10 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
461
465
|
return parent as EFTimegroup | undefined;
|
|
462
466
|
}
|
|
463
467
|
|
|
468
|
+
get hasExplicitDuration() {
|
|
469
|
+
return this._durationMs !== undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
464
472
|
get hasOwnDuration() {
|
|
465
473
|
return false;
|
|
466
474
|
}
|
|
@@ -559,6 +567,10 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
559
567
|
return this.startTimeMs + this.durationMs;
|
|
560
568
|
}
|
|
561
569
|
|
|
570
|
+
/**
|
|
571
|
+
* The current time of the element within itself.
|
|
572
|
+
* Compare with `currentTimeMs` to see the current time with respect to the root timegroup
|
|
573
|
+
*/
|
|
562
574
|
get ownCurrentTimeMs() {
|
|
563
575
|
if (this.rootTimegroup) {
|
|
564
576
|
return Math.min(
|
|
@@ -573,7 +585,7 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
573
585
|
* Used to calculate the internal currentTimeMs of the element. This is useful
|
|
574
586
|
* for mapping to internal media time codes for audio/video elements.
|
|
575
587
|
*/
|
|
576
|
-
get
|
|
588
|
+
get currentSourceTimeMs() {
|
|
577
589
|
if (this.rootTimegroup) {
|
|
578
590
|
if (this.sourceInMs && this.sourceOutMs) {
|
|
579
591
|
return Math.min(
|
|
@@ -613,6 +625,26 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
613
625
|
}
|
|
614
626
|
},
|
|
615
627
|
});
|
|
628
|
+
|
|
629
|
+
protected updated(
|
|
630
|
+
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
631
|
+
): void {
|
|
632
|
+
super.updated(changedProperties);
|
|
633
|
+
if (
|
|
634
|
+
changedProperties.has("currentTime") ||
|
|
635
|
+
changedProperties.has("ownCurrentTimeMs")
|
|
636
|
+
) {
|
|
637
|
+
const timelineTimeMs = (this.rootTimegroup ?? this).ownCurrentTimeMs;
|
|
638
|
+
if (
|
|
639
|
+
this.startTimeMs > timelineTimeMs ||
|
|
640
|
+
this.endTimeMs < timelineTimeMs
|
|
641
|
+
) {
|
|
642
|
+
this.style.display = "none";
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
this.style.display = "";
|
|
646
|
+
}
|
|
647
|
+
}
|
|
616
648
|
}
|
|
617
649
|
|
|
618
650
|
Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {
|
|
@@ -39,7 +39,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
39
39
|
display: block;
|
|
40
40
|
width: 100%;
|
|
41
41
|
height: 100%;
|
|
42
|
-
position:
|
|
42
|
+
position: absolute;
|
|
43
43
|
transform-origin: center center;
|
|
44
44
|
}
|
|
45
45
|
`;
|
|
@@ -53,7 +53,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
53
53
|
type: String,
|
|
54
54
|
attribute: "mode",
|
|
55
55
|
})
|
|
56
|
-
mode: "fixed" | "sequence" | "contain" = "
|
|
56
|
+
mode: "fixed" | "sequence" | "contain" = "contain";
|
|
57
57
|
|
|
58
58
|
@property({
|
|
59
59
|
type: Number,
|
|
@@ -67,7 +67,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
67
67
|
|
|
68
68
|
#resizeObserver?: ResizeObserver;
|
|
69
69
|
|
|
70
|
-
@property({ type: Number })
|
|
70
|
+
@property({ type: Number, attribute: "currenttime" })
|
|
71
71
|
set currentTime(time: number) {
|
|
72
72
|
this.#currentTime = Math.max(0, Math.min(time, this.durationMs / 1000));
|
|
73
73
|
try {
|
|
@@ -227,10 +227,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
227
227
|
* in calculations and it was not clear why.
|
|
228
228
|
*/
|
|
229
229
|
async waitForMediaDurations() {
|
|
230
|
+
const mediaElements = deepGetMediaElements(this);
|
|
230
231
|
return await Promise.all(
|
|
231
|
-
|
|
232
|
-
(media) => media.initSegmentsLoader.taskComplete,
|
|
233
|
-
),
|
|
232
|
+
mediaElements.map((m) => m.trackFragmentIndexLoader.taskComplete),
|
|
234
233
|
);
|
|
235
234
|
}
|
|
236
235
|
|
|
@@ -261,7 +260,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
261
260
|
return;
|
|
262
261
|
}
|
|
263
262
|
this.style.display = "";
|
|
264
|
-
|
|
265
263
|
const animations = this.getAnimations({ subtree: true });
|
|
266
264
|
this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
|
|
267
265
|
this.style.setProperty(
|