@editframe/elements 0.6.0-beta.17 → 0.6.0-beta.19

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.
Files changed (84) hide show
  1. package/package.json +2 -2
  2. package/src/elements/EFMedia.ts +1 -1
  3. package/src/elements/EFTimegroup.ts +14 -6
  4. package/src/gui/EFFilmstrip.ts +144 -30
  5. package/dist/lib/av/EncodedAsset.cjs +0 -567
  6. package/dist/lib/av/EncodedAsset.js +0 -550
  7. package/dist/lib/av/MP4File.cjs +0 -182
  8. package/dist/lib/av/MP4File.js +0 -165
  9. package/dist/lib/av/msToTimeCode.cjs +0 -15
  10. package/dist/lib/av/msToTimeCode.js +0 -15
  11. package/dist/lib/util/awaitMicrotask.cjs +0 -4
  12. package/dist/lib/util/awaitMicrotask.js +0 -4
  13. package/dist/lib/util/memoize.cjs +0 -14
  14. package/dist/lib/util/memoize.js +0 -14
  15. package/dist/packages/elements/src/EF_FRAMEGEN.cjs +0 -200
  16. package/dist/packages/elements/src/EF_FRAMEGEN.d.ts +0 -45
  17. package/dist/packages/elements/src/EF_FRAMEGEN.js +0 -200
  18. package/dist/packages/elements/src/EF_INTERACTIVE.cjs +0 -4
  19. package/dist/packages/elements/src/EF_INTERACTIVE.d.ts +0 -1
  20. package/dist/packages/elements/src/EF_INTERACTIVE.js +0 -4
  21. package/dist/packages/elements/src/elements/CrossUpdateController.cjs +0 -16
  22. package/dist/packages/elements/src/elements/CrossUpdateController.d.ts +0 -9
  23. package/dist/packages/elements/src/elements/CrossUpdateController.js +0 -16
  24. package/dist/packages/elements/src/elements/EFAudio.cjs +0 -53
  25. package/dist/packages/elements/src/elements/EFAudio.d.ts +0 -10
  26. package/dist/packages/elements/src/elements/EFAudio.js +0 -54
  27. package/dist/packages/elements/src/elements/EFCaptions.cjs +0 -164
  28. package/dist/packages/elements/src/elements/EFCaptions.d.ts +0 -38
  29. package/dist/packages/elements/src/elements/EFCaptions.js +0 -166
  30. package/dist/packages/elements/src/elements/EFImage.cjs +0 -79
  31. package/dist/packages/elements/src/elements/EFImage.d.ts +0 -14
  32. package/dist/packages/elements/src/elements/EFImage.js +0 -80
  33. package/dist/packages/elements/src/elements/EFMedia.cjs +0 -334
  34. package/dist/packages/elements/src/elements/EFMedia.d.ts +0 -61
  35. package/dist/packages/elements/src/elements/EFMedia.js +0 -334
  36. package/dist/packages/elements/src/elements/EFSourceMixin.cjs +0 -55
  37. package/dist/packages/elements/src/elements/EFSourceMixin.d.ts +0 -12
  38. package/dist/packages/elements/src/elements/EFSourceMixin.js +0 -55
  39. package/dist/packages/elements/src/elements/EFTemporal.cjs +0 -198
  40. package/dist/packages/elements/src/elements/EFTemporal.d.ts +0 -36
  41. package/dist/packages/elements/src/elements/EFTemporal.js +0 -198
  42. package/dist/packages/elements/src/elements/EFTimegroup.browsertest.d.ts +0 -12
  43. package/dist/packages/elements/src/elements/EFTimegroup.cjs +0 -343
  44. package/dist/packages/elements/src/elements/EFTimegroup.d.ts +0 -39
  45. package/dist/packages/elements/src/elements/EFTimegroup.js +0 -344
  46. package/dist/packages/elements/src/elements/EFTimeline.cjs +0 -15
  47. package/dist/packages/elements/src/elements/EFTimeline.d.ts +0 -3
  48. package/dist/packages/elements/src/elements/EFTimeline.js +0 -15
  49. package/dist/packages/elements/src/elements/EFVideo.cjs +0 -109
  50. package/dist/packages/elements/src/elements/EFVideo.d.ts +0 -14
  51. package/dist/packages/elements/src/elements/EFVideo.js +0 -110
  52. package/dist/packages/elements/src/elements/EFWaveform.cjs +0 -235
  53. package/dist/packages/elements/src/elements/EFWaveform.d.ts +0 -28
  54. package/dist/packages/elements/src/elements/EFWaveform.js +0 -219
  55. package/dist/packages/elements/src/elements/FetchMixin.cjs +0 -28
  56. package/dist/packages/elements/src/elements/FetchMixin.d.ts +0 -8
  57. package/dist/packages/elements/src/elements/FetchMixin.js +0 -28
  58. package/dist/packages/elements/src/elements/TimegroupController.cjs +0 -20
  59. package/dist/packages/elements/src/elements/TimegroupController.d.ts +0 -14
  60. package/dist/packages/elements/src/elements/TimegroupController.js +0 -20
  61. package/dist/packages/elements/src/elements/durationConverter.cjs +0 -8
  62. package/dist/packages/elements/src/elements/durationConverter.d.ts +0 -4
  63. package/dist/packages/elements/src/elements/durationConverter.js +0 -8
  64. package/dist/packages/elements/src/elements/parseTimeToMs.cjs +0 -12
  65. package/dist/packages/elements/src/elements/parseTimeToMs.d.ts +0 -1
  66. package/dist/packages/elements/src/elements/parseTimeToMs.js +0 -12
  67. package/dist/packages/elements/src/elements/util.cjs +0 -11
  68. package/dist/packages/elements/src/elements/util.d.ts +0 -4
  69. package/dist/packages/elements/src/elements/util.js +0 -11
  70. package/dist/packages/elements/src/gui/EFFilmstrip.cjs +0 -719
  71. package/dist/packages/elements/src/gui/EFFilmstrip.d.ts +0 -143
  72. package/dist/packages/elements/src/gui/EFFilmstrip.js +0 -727
  73. package/dist/packages/elements/src/gui/EFWorkbench.cjs +0 -213
  74. package/dist/packages/elements/src/gui/EFWorkbench.d.ts +0 -45
  75. package/dist/packages/elements/src/gui/EFWorkbench.js +0 -214
  76. package/dist/packages/elements/src/gui/TWMixin.cjs +0 -28
  77. package/dist/packages/elements/src/gui/TWMixin.css.cjs +0 -3
  78. package/dist/packages/elements/src/gui/TWMixin.css.js +0 -4
  79. package/dist/packages/elements/src/gui/TWMixin.d.ts +0 -3
  80. package/dist/packages/elements/src/gui/TWMixin.js +0 -28
  81. package/dist/packages/elements/src/index.cjs +0 -47
  82. package/dist/packages/elements/src/index.d.ts +0 -10
  83. package/dist/packages/elements/src/index.js +0 -23
  84. package/dist/style.css +0 -769
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/elements",
3
- "version": "0.6.0-beta.17",
3
+ "version": "0.6.0-beta.19",
4
4
  "description": "",
5
5
  "exports": {
6
6
  ".": {
@@ -19,7 +19,7 @@
19
19
  "author": "",
20
20
  "license": "UNLICENSED",
21
21
  "dependencies": {
22
- "@editframe/assets": "0.6.0-beta.17",
22
+ "@editframe/assets": "0.6.0-beta.19",
23
23
  "@lit/context": "^1.1.1",
24
24
  "@lit/task": "^1.0.0",
25
25
  "d3": "^7.9.0",
@@ -52,7 +52,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
52
52
  if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
53
53
  return `${this.src}/index`;
54
54
  }
55
- return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
55
+ return `/@ef-track-fragment-index/${this.src ?? ""}`;
56
56
  }
57
57
 
58
58
  fragmentTrackPath(trackId: string) {
@@ -292,15 +292,14 @@ export class EFTimegroup extends EFTemporal(LitElement) {
292
292
  );
293
293
  }
294
294
 
295
- async renderAudio(fromMs: number, toMs: number) {
295
+ async #addAudioToContext(
296
+ audioContext: AudioContext | OfflineAudioContext,
297
+ fromMs: number,
298
+ toMs: number,
299
+ ) {
296
300
  await this.waitForMediaDurations();
297
301
 
298
302
  const durationMs = toMs - fromMs;
299
- const audioContext = new OfflineAudioContext(
300
- 2,
301
- Math.round((48000 * durationMs) / 1000),
302
- 48000,
303
- );
304
303
 
305
304
  await Promise.all(
306
305
  deepGetMediaElements(this).map(async (mediaElement) => {
@@ -338,7 +337,16 @@ export class EFTimegroup extends EFTemporal(LitElement) {
338
337
  );
339
338
  }),
340
339
  );
340
+ }
341
341
 
342
+ async renderAudio(fromMs: number, toMs: number) {
343
+ const durationMs = toMs - fromMs;
344
+ const audioContext = new OfflineAudioContext(
345
+ 2,
346
+ Math.round((48000 * durationMs) / 1000),
347
+ 48000,
348
+ );
349
+ await this.#addAudioToContext(audioContext, fromMs, toMs);
342
350
  return await audioContext.startRendering();
343
351
  }
344
352
 
@@ -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,14 +481,14 @@ 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
 
488
488
  @property({ type: Number })
489
489
  currentTimeMs = 0;
490
490
 
491
- @property({ type: String, attribute: "target" })
491
+ @property({ type: String, attribute: "target", reflect: true })
492
492
  targetSelector = "";
493
493
 
494
494
  @state()
@@ -504,15 +504,41 @@ export class EFFilmStrip extends TWMixin(LitElement) {
504
504
 
505
505
  connectedCallback(): void {
506
506
  super.connectedCallback();
507
- const target = this.target;
507
+ const target = this.targetTimegroup;
508
508
  if (target) {
509
509
  this.timegroupController = new TimegroupController(target, this);
510
510
  // Set the current time to the last saved time to avoid a cycle
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,10 +557,96 @@ 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.targetTimegroup;
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
- if (this.#lastTick && tick && this.target) {
536
- this.target.currentTimeMs += tick - this.#lastTick;
537
- if (this.target.currentTimeMs >= this.target.durationMs) {
645
+ if (this.#lastTick && tick && this.targetTimegroup) {
646
+ this.targetTimegroup.currentTimeMs += tick - this.#lastTick;
647
+ if (
648
+ this.targetTimegroup.currentTimeMs >= this.targetTimegroup.durationMs
649
+ ) {
538
650
  this.playing = false;
539
651
  }
540
652
  }
@@ -558,9 +670,9 @@ export class EFFilmStrip extends TWMixin(LitElement) {
558
670
  return;
559
671
  }
560
672
  const rect = gutter.getBoundingClientRect();
561
- if (this.target) {
673
+ if (this.targetTimegroup) {
562
674
  const layerX = e.pageX - rect.left + gutter.scrollLeft;
563
- this.target.currentTimeMs = layerX / this.pixelsPerMs;
675
+ this.targetTimegroup.currentTimeMs = layerX / this.pixelsPerMs;
564
676
  }
565
677
  }
566
678
 
@@ -576,9 +688,9 @@ export class EFFilmStrip extends TWMixin(LitElement) {
576
688
  return;
577
689
  }
578
690
  const rect = gutter.getBoundingClientRect();
579
- if (this.target) {
691
+ if (this.targetTimegroup) {
580
692
  const layerX = e.pageX - rect.left + gutter.scrollLeft;
581
- this.target.currentTimeMs = layerX / this.pixelsPerMs;
693
+ this.targetTimegroup.currentTimeMs = layerX / this.pixelsPerMs;
582
694
  }
583
695
  });
584
696
  addEventListener(
@@ -592,7 +704,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
592
704
 
593
705
  @eventOptions({ passive: false })
594
706
  scrollScrub(e: WheelEvent) {
595
- if (this.target && this.gutter && !this.playing) {
707
+ if (this.targetTimegroup && this.gutter && !this.playing) {
596
708
  e.preventDefault();
597
709
  // Avoid over-scrolling to the left
598
710
  if (
@@ -616,7 +728,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
616
728
 
617
729
  if (this) {
618
730
  this.gutter.scrollBy(e.deltaX, e.deltaY);
619
- this.target.currentTimeMs += e.deltaX / this.pixelsPerMs;
731
+ this.targetTimegroup.currentTimeMs += e.deltaX / this.pixelsPerMs;
620
732
  }
621
733
  }
622
734
  }
@@ -630,7 +742,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
630
742
  }
631
743
 
632
744
  render() {
633
- const target = this.target;
745
+ const target = this.targetTimegroup;
634
746
 
635
747
  return html` <div
636
748
  class="grid h-full bg-slate-100"
@@ -702,7 +814,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
702
814
  ${ref(this.playheadRef)}
703
815
  ></div>
704
816
 
705
- ${renderFilmStripChildren(
817
+ ${renderFilmstripChildren(
706
818
  Array.from(target?.children || []),
707
819
  this.pixelsPerMs,
708
820
  )}
@@ -714,24 +826,26 @@ export class EFFilmStrip extends TWMixin(LitElement) {
714
826
  update(changedProperties: Map<string | number | symbol, unknown>) {
715
827
  if (changedProperties.has("playing")) {
716
828
  if (this.playing) {
717
- this.advancePlayhead(0);
829
+ this.#startPlayback();
830
+ } else {
831
+ this.#stopPlayback();
718
832
  }
719
833
  }
720
834
  super.update(changedProperties);
721
835
  }
722
836
 
723
837
  updated(changes: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
724
- if (!this.target) {
838
+ if (!this.targetTimegroup) {
725
839
  return;
726
840
  }
727
841
  if (changes.has("currentTimeMs")) {
728
- if (this.target.currentTimeMs !== this.currentTimeMs) {
729
- this.target.currentTimeMs = this.currentTimeMs;
842
+ if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
843
+ this.targetTimegroup.currentTimeMs = this.currentTimeMs;
730
844
  }
731
845
  }
732
846
  }
733
847
 
734
- get target() {
848
+ get targetTimegroup() {
735
849
  if (this.getAttribute("target")) {
736
850
  const target = document.querySelector(this.getAttribute("target") ?? "");
737
851
  if (target instanceof EFTimegroup) {
@@ -746,7 +860,7 @@ export class EFFilmStrip extends TWMixin(LitElement) {
746
860
 
747
861
  declare global {
748
862
  interface HTMLElementTagNameMap {
749
- "ef-filmstrip": EFFilmStrip;
863
+ "ef-filmstrip": EFFilmstrip;
750
864
  "ef-timegroup-hierarchy-item": EFTimegroupHierarchyItem;
751
865
  "ef-audio-hierarchy-item": EFAudioHierarchyItem;
752
866
  "ef-video-hierarchy-item": EFVideoHierarchyItem;