@editframe/elements 0.6.0-beta.17 → 0.6.0-beta.18
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/lib/av/EncodedAsset.cjs +4 -1
- package/dist/lib/av/EncodedAsset.js +4 -1
- package/dist/packages/elements/src/EF_FRAMEGEN.d.ts +1 -1
- package/dist/packages/elements/src/elements/EFMedia.cjs +1 -1
- package/dist/packages/elements/src/elements/EFMedia.d.ts +1 -1
- package/dist/packages/elements/src/elements/EFMedia.js +1 -1
- package/dist/packages/elements/src/elements/EFTimegroup.cjs +38 -31
- package/dist/packages/elements/src/elements/EFTimegroup.d.ts +1 -1
- package/dist/packages/elements/src/elements/EFTimegroup.js +38 -31
- package/dist/packages/elements/src/gui/EFFilmstrip.cjs +128 -27
- package/dist/packages/elements/src/gui/EFFilmstrip.d.ts +7 -6
- package/dist/packages/elements/src/gui/EFFilmstrip.js +129 -28
- package/dist/packages/elements/src/gui/TWMixin.css.cjs +1 -1
- package/dist/packages/elements/src/gui/TWMixin.css.js +1 -1
- package/dist/packages/elements/src/index.cjs +6 -2
- package/dist/packages/elements/src/index.d.ts +2 -2
- package/dist/packages/elements/src/index.js +4 -3
- package/dist/style.css +21 -3
- package/package.json +2 -2
- package/src/elements/EFMedia.ts +1 -1
- package/src/elements/EFTimegroup.ts +14 -6
- package/src/gui/EFFilmstrip.ts +126 -14
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -103,7 +103,7 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
103
103
|
}
|
|
104
104
|
}}
|
|
105
105
|
?data-focused=${this.isFocused}
|
|
106
|
-
class="border-outset relative mb-[1px] block h-[1.
|
|
106
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
|
|
107
107
|
>
|
|
108
108
|
${this.animations()}
|
|
109
109
|
</div>
|
|
@@ -112,7 +112,7 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
|
|
115
|
-
return
|
|
115
|
+
return renderFilmstripChildren(
|
|
116
116
|
Array.from(this.element.children),
|
|
117
117
|
this.pixelsPerMs,
|
|
118
118
|
);
|
|
@@ -228,7 +228,7 @@ export class EFTimegroupFilmstrip extends FilmstripItem {
|
|
|
228
228
|
contents() {
|
|
229
229
|
return html`
|
|
230
230
|
<span>TIME GROUP</span>
|
|
231
|
-
${
|
|
231
|
+
${renderFilmstripChildren(
|
|
232
232
|
Array.from(this.element.children || []),
|
|
233
233
|
this.pixelsPerMs,
|
|
234
234
|
)}
|
|
@@ -242,7 +242,7 @@ export class EFHTMLFilmstrip extends FilmstripItem {
|
|
|
242
242
|
contents() {
|
|
243
243
|
return html`
|
|
244
244
|
<span>${this.element.tagName}</span>
|
|
245
|
-
${
|
|
245
|
+
${renderFilmstripChildren(
|
|
246
246
|
Array.from(this.element.children || []),
|
|
247
247
|
this.pixelsPerMs,
|
|
248
248
|
)}
|
|
@@ -282,8 +282,8 @@ class EFHierarchyItem<
|
|
|
282
282
|
<div
|
|
283
283
|
?data-focused=${this.isFocused}
|
|
284
284
|
class="peer
|
|
285
|
-
flex h-[1.
|
|
286
|
-
bg-slate-200 pl-2 text-
|
|
285
|
+
flex h-[1.1rem] items-center overflow-hidden text-nowrap border border-slate-500
|
|
286
|
+
bg-slate-200 pl-2 text-xs font-mono hover:bg-slate-400 data-[focused]:bg-slate-400"
|
|
287
287
|
@mouseenter=${() => {
|
|
288
288
|
if (this.focusContext) {
|
|
289
289
|
this.focusContext.focusedElement = this.element;
|
|
@@ -328,7 +328,7 @@ class EFAudioHierarchyItem extends EFHierarchyItem {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
displayLabel() {
|
|
331
|
-
return this.element.
|
|
331
|
+
return this.element.src ?? "(no src)";
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -339,7 +339,7 @@ class EFVideoHierarchyItem extends EFHierarchyItem {
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
displayLabel() {
|
|
342
|
-
return this.element.
|
|
342
|
+
return this.element.src ?? "(no src)";
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -375,7 +375,7 @@ class EFImageHierarchyItem extends EFHierarchyItem {
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
displayLabel() {
|
|
378
|
-
return this.element.
|
|
378
|
+
return this.element.src ?? "(no src)";
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -431,7 +431,7 @@ const renderHierarchyChildren = (
|
|
|
431
431
|
});
|
|
432
432
|
};
|
|
433
433
|
|
|
434
|
-
const
|
|
434
|
+
const renderFilmstripChildren = (
|
|
435
435
|
children: Element[],
|
|
436
436
|
pixelsPerMs: number,
|
|
437
437
|
): Array<TemplateResult<1> | typeof nothing> => {
|
|
@@ -481,7 +481,7 @@ const renderFilmStripChildren = (
|
|
|
481
481
|
};
|
|
482
482
|
|
|
483
483
|
@customElement("ef-filmstrip")
|
|
484
|
-
export class
|
|
484
|
+
export class EFFilmstrip extends TWMixin(LitElement) {
|
|
485
485
|
@property({ type: Number })
|
|
486
486
|
pixelsPerMs = 0.04;
|
|
487
487
|
|
|
@@ -511,8 +511,34 @@ export class EFFilmStrip extends TWMixin(LitElement) {
|
|
|
511
511
|
// where the filmstrip clobbers the time loaded from localStorage
|
|
512
512
|
this.currentTimeMs = target.currentTimeMs;
|
|
513
513
|
}
|
|
514
|
+
|
|
515
|
+
window.addEventListener("keypress", this.#handleKeyPress);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
disconnectedCallback(): void {
|
|
519
|
+
super.disconnectedCallback();
|
|
520
|
+
window.removeEventListener("keypress", this.#handleKeyPress);
|
|
514
521
|
}
|
|
515
522
|
|
|
523
|
+
#handleKeyPress = (event: KeyboardEvent) => {
|
|
524
|
+
// On spacebar, toggle playback
|
|
525
|
+
if (event.key === " ") {
|
|
526
|
+
// CSS selector to match all interactive elements
|
|
527
|
+
const interactiveSelector =
|
|
528
|
+
"input, textarea, button, select, a, [contenteditable]";
|
|
529
|
+
|
|
530
|
+
// Check if the event target or its ancestor matches an interactive element
|
|
531
|
+
const closestInteractive = (event.target as HTMLElement | null)?.closest(
|
|
532
|
+
interactiveSelector,
|
|
533
|
+
);
|
|
534
|
+
if (closestInteractive) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
this.playing = !this.playing;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
516
542
|
@eventOptions({ passive: false })
|
|
517
543
|
syncGutterScroll() {
|
|
518
544
|
if (this.gutter && this.hierarchyRef.value) {
|
|
@@ -531,6 +557,90 @@ export class EFFilmStrip extends TWMixin(LitElement) {
|
|
|
531
557
|
|
|
532
558
|
#lastTick?: DOMHighResTimeStamp;
|
|
533
559
|
|
|
560
|
+
#playbackAudioContext: AudioContext | null = null;
|
|
561
|
+
#playbackAnimationFrameRequest: number | null = null;
|
|
562
|
+
#AUDIO_PLAYBACK_SLICE_MS = 1000;
|
|
563
|
+
|
|
564
|
+
#syncPlayheadToAudioContext(target: EFTimegroup, startMs: number) {
|
|
565
|
+
target.currentTimeMs =
|
|
566
|
+
startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1000;
|
|
567
|
+
this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
|
|
568
|
+
this.#syncPlayheadToAudioContext(target, startMs);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async #stopPlayback() {
|
|
573
|
+
if (this.#playbackAudioContext) {
|
|
574
|
+
if (this.#playbackAudioContext.state !== "closed") {
|
|
575
|
+
await this.#playbackAudioContext.close();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
579
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
580
|
+
}
|
|
581
|
+
this.#playbackAudioContext = null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async #startPlayback() {
|
|
585
|
+
await this.#stopPlayback();
|
|
586
|
+
const timegroup = this.target;
|
|
587
|
+
if (!timegroup) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let currentMs = timegroup.currentTimeMs;
|
|
592
|
+
let bufferCount = 0;
|
|
593
|
+
this.#playbackAudioContext = new AudioContext({
|
|
594
|
+
latencyHint: "playback",
|
|
595
|
+
});
|
|
596
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
597
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
598
|
+
}
|
|
599
|
+
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
600
|
+
const playbackContext = this.#playbackAudioContext;
|
|
601
|
+
await playbackContext.suspend();
|
|
602
|
+
|
|
603
|
+
const fillBuffer = async () => {
|
|
604
|
+
if (bufferCount > 1) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const canFillBuffer = await queueBufferSource();
|
|
608
|
+
if (canFillBuffer) {
|
|
609
|
+
fillBuffer();
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const fromMs = currentMs;
|
|
614
|
+
const toMs = timegroup.endTimeMs;
|
|
615
|
+
|
|
616
|
+
const queueBufferSource = async () => {
|
|
617
|
+
if (currentMs >= toMs) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
const startMs = currentMs;
|
|
621
|
+
const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
622
|
+
currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
623
|
+
const audioBuffer = await timegroup.renderAudio(startMs, endMs);
|
|
624
|
+
bufferCount++;
|
|
625
|
+
const source = playbackContext.createBufferSource();
|
|
626
|
+
source.buffer = audioBuffer;
|
|
627
|
+
source.connect(playbackContext.destination);
|
|
628
|
+
source.start((startMs - fromMs) / 1000);
|
|
629
|
+
source.onended = () => {
|
|
630
|
+
bufferCount--;
|
|
631
|
+
if (endMs >= toMs) {
|
|
632
|
+
this.playing = false;
|
|
633
|
+
} else {
|
|
634
|
+
fillBuffer();
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
return true;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
await fillBuffer();
|
|
641
|
+
await playbackContext.resume();
|
|
642
|
+
}
|
|
643
|
+
|
|
534
644
|
advancePlayhead = (tick?: DOMHighResTimeStamp) => {
|
|
535
645
|
if (this.#lastTick && tick && this.target) {
|
|
536
646
|
this.target.currentTimeMs += tick - this.#lastTick;
|
|
@@ -702,7 +812,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
|
|
|
702
812
|
${ref(this.playheadRef)}
|
|
703
813
|
></div>
|
|
704
814
|
|
|
705
|
-
${
|
|
815
|
+
${renderFilmstripChildren(
|
|
706
816
|
Array.from(target?.children || []),
|
|
707
817
|
this.pixelsPerMs,
|
|
708
818
|
)}
|
|
@@ -714,7 +824,9 @@ export class EFFilmStrip extends TWMixin(LitElement) {
|
|
|
714
824
|
update(changedProperties: Map<string | number | symbol, unknown>) {
|
|
715
825
|
if (changedProperties.has("playing")) {
|
|
716
826
|
if (this.playing) {
|
|
717
|
-
this
|
|
827
|
+
this.#startPlayback();
|
|
828
|
+
} else {
|
|
829
|
+
this.#stopPlayback();
|
|
718
830
|
}
|
|
719
831
|
}
|
|
720
832
|
super.update(changedProperties);
|
|
@@ -746,7 +858,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
|
|
|
746
858
|
|
|
747
859
|
declare global {
|
|
748
860
|
interface HTMLElementTagNameMap {
|
|
749
|
-
"ef-filmstrip":
|
|
861
|
+
"ef-filmstrip": EFFilmstrip;
|
|
750
862
|
"ef-timegroup-hierarchy-item": EFTimegroupHierarchyItem;
|
|
751
863
|
"ef-audio-hierarchy-item": EFAudioHierarchyItem;
|
|
752
864
|
"ef-video-hierarchy-item": EFVideoHierarchyItem;
|