@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.
@@ -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.25rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
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 renderFilmStripChildren(
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
- ${renderFilmStripChildren(
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
- ${renderFilmStripChildren(
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.25rem] items-center overflow-hidden text-nowrap border border-slate-500
286
- bg-slate-200 pl-2 text-sm hover:bg-slate-400 data-[focused]:bg-slate-400"
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.getAttribute("src") ?? "(no src)";
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.getAttribute("src") ?? "(no src)";
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.getAttribute("src") ?? "(no src)";
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 renderFilmStripChildren = (
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 EFFilmStrip extends TWMixin(LitElement) {
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
- ${renderFilmStripChildren(
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.advancePlayhead(0);
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": EFFilmStrip;
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;