@aicut/core 0.4.3 → 0.5.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/README.md +112 -1
- package/dist/i18n-B-DFWgKe.d.cts +51 -0
- package/dist/i18n-B-DFWgKe.d.ts +51 -0
- package/dist/index.cjs +429 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +200 -6
- package/dist/index.d.ts +200 -6
- package/dist/index.js +371 -17
- package/dist/index.js.map +1 -1
- package/dist/lighting/index.d.cts +2 -1
- package/dist/lighting/index.d.ts +2 -1
- package/dist/playback/webcodecs/index.cjs +10572 -0
- package/dist/playback/webcodecs/index.cjs.map +1 -0
- package/dist/playback/webcodecs/index.d.cts +109 -0
- package/dist/playback/webcodecs/index.d.ts +109 -0
- package/dist/playback/webcodecs/index.js +10568 -0
- package/dist/playback/webcodecs/index.js.map +1 -0
- package/dist/types-CHplD9V5.d.cts +70 -0
- package/dist/types-CHplD9V5.d.ts +70 -0
- package/dist/types-DvKlxylu.d.cts +75 -0
- package/dist/types-rwZx6FxE.d.ts +75 -0
- package/package.json +15 -4
- package/styles/theme.css +7 -2
- package/dist/types-C95koNwJ.d.cts +0 -120
- package/dist/types-C95koNwJ.d.ts +0 -120
package/dist/index.js
CHANGED
|
@@ -152,6 +152,14 @@ var HANDLE_PX = 8;
|
|
|
152
152
|
var CLIP_INSET = 6;
|
|
153
153
|
var SCALE_MIN = 10;
|
|
154
154
|
var SCALE_MAX = 400;
|
|
155
|
+
function setTimelineMetrics(opts) {
|
|
156
|
+
if (opts.trackHeight != null && opts.trackHeight > 0) {
|
|
157
|
+
TRACK_HEIGHT = Math.round(opts.trackHeight);
|
|
158
|
+
}
|
|
159
|
+
if (opts.rulerHeight != null && opts.rulerHeight > 0) {
|
|
160
|
+
RULER_HEIGHT = Math.round(opts.rulerHeight);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
155
163
|
var SCROLLBAR_THICKNESS = 10;
|
|
156
164
|
var SCROLLBAR_MIN_THUMB = 24;
|
|
157
165
|
var SCROLLBAR_INSET = 2;
|
|
@@ -254,8 +262,8 @@ function uncoveredIntervals(project) {
|
|
|
254
262
|
return gaps;
|
|
255
263
|
}
|
|
256
264
|
|
|
257
|
-
// src/playback.ts
|
|
258
|
-
var
|
|
265
|
+
// src/playback/html-video.ts
|
|
266
|
+
var HtmlVideoEngine = class {
|
|
259
267
|
host;
|
|
260
268
|
mount;
|
|
261
269
|
videos = /* @__PURE__ */ new Map();
|
|
@@ -271,9 +279,9 @@ var PlaybackEngine = class {
|
|
|
271
279
|
onError;
|
|
272
280
|
onReady;
|
|
273
281
|
onSourceMetadata;
|
|
274
|
-
constructor(
|
|
275
|
-
this.host = host;
|
|
276
|
-
this.project = project;
|
|
282
|
+
constructor(opts) {
|
|
283
|
+
this.host = opts.host;
|
|
284
|
+
this.project = opts.project;
|
|
277
285
|
this.mount = document.createElement("div");
|
|
278
286
|
this.mount.className = "aicut-preview";
|
|
279
287
|
this.host.appendChild(this.mount);
|
|
@@ -515,6 +523,328 @@ var PlaybackEngine = class {
|
|
|
515
523
|
this.onTimeUpdate?.(this.timeMs);
|
|
516
524
|
}
|
|
517
525
|
};
|
|
526
|
+
var htmlVideoEngineFactory = (opts) => new HtmlVideoEngine(opts);
|
|
527
|
+
|
|
528
|
+
// src/playback/canvas-compositor.ts
|
|
529
|
+
var CanvasCompositorEngine = class {
|
|
530
|
+
host;
|
|
531
|
+
mount;
|
|
532
|
+
canvas;
|
|
533
|
+
ctx;
|
|
534
|
+
/** Only created when constructed with `debug: true`. */
|
|
535
|
+
badge = null;
|
|
536
|
+
videos = /* @__PURE__ */ new Map();
|
|
537
|
+
project;
|
|
538
|
+
currentClipId = null;
|
|
539
|
+
playing = false;
|
|
540
|
+
timeMs = 0;
|
|
541
|
+
rafHandle = null;
|
|
542
|
+
lastFrameTs = 0;
|
|
543
|
+
paintedFrames = 0;
|
|
544
|
+
onTimeUpdate;
|
|
545
|
+
onEnded;
|
|
546
|
+
onError;
|
|
547
|
+
onReady;
|
|
548
|
+
onSourceMetadata;
|
|
549
|
+
constructor(opts) {
|
|
550
|
+
this.host = opts.host;
|
|
551
|
+
this.project = opts.project;
|
|
552
|
+
this.mount = document.createElement("div");
|
|
553
|
+
this.mount.className = "aicut-preview aicut-preview--canvas";
|
|
554
|
+
Object.assign(this.mount.style, {
|
|
555
|
+
position: "absolute",
|
|
556
|
+
inset: "0",
|
|
557
|
+
width: "100%",
|
|
558
|
+
height: "100%"
|
|
559
|
+
});
|
|
560
|
+
this.canvas = document.createElement("canvas");
|
|
561
|
+
Object.assign(this.canvas.style, {
|
|
562
|
+
position: "absolute",
|
|
563
|
+
inset: "0",
|
|
564
|
+
width: "100%",
|
|
565
|
+
height: "100%",
|
|
566
|
+
// Stretch with letterboxing handled by the draw loop.
|
|
567
|
+
objectFit: "contain",
|
|
568
|
+
// Black until the first frame is drawn so the swap from the
|
|
569
|
+
// previous engine doesn't flash the host background.
|
|
570
|
+
background: "#000"
|
|
571
|
+
});
|
|
572
|
+
this.mount.appendChild(this.canvas);
|
|
573
|
+
const ctx = this.canvas.getContext("2d");
|
|
574
|
+
if (!ctx) throw new Error("CanvasCompositorEngine: 2d context unavailable");
|
|
575
|
+
this.ctx = ctx;
|
|
576
|
+
if (opts.debug) {
|
|
577
|
+
const badge = document.createElement("div");
|
|
578
|
+
badge.className = "aicut-preview__badge";
|
|
579
|
+
Object.assign(badge.style, {
|
|
580
|
+
position: "absolute",
|
|
581
|
+
top: "8px",
|
|
582
|
+
left: "8px",
|
|
583
|
+
padding: "4px 8px",
|
|
584
|
+
borderRadius: "6px",
|
|
585
|
+
background: "rgba(0, 0, 0, 0.55)",
|
|
586
|
+
color: "rgba(255, 255, 255, 0.92)",
|
|
587
|
+
font: "11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
588
|
+
pointerEvents: "none",
|
|
589
|
+
zIndex: "2",
|
|
590
|
+
letterSpacing: "0.02em"
|
|
591
|
+
});
|
|
592
|
+
badge.textContent = "engine: canvas compositor";
|
|
593
|
+
this.mount.appendChild(badge);
|
|
594
|
+
this.badge = badge;
|
|
595
|
+
}
|
|
596
|
+
this.host.appendChild(this.mount);
|
|
597
|
+
this.syncSources();
|
|
598
|
+
this.resizeCanvas();
|
|
599
|
+
this.startTickLoop();
|
|
600
|
+
}
|
|
601
|
+
setProject(next) {
|
|
602
|
+
this.project = next;
|
|
603
|
+
this.syncSources();
|
|
604
|
+
const clip = this.clipAtTime(this.timeMs);
|
|
605
|
+
if (!clip) {
|
|
606
|
+
this.timeMs = 0;
|
|
607
|
+
this.activate(null);
|
|
608
|
+
this.onTimeUpdate?.(0);
|
|
609
|
+
} else {
|
|
610
|
+
this.activate(clip);
|
|
611
|
+
this.seekVideoToClipOffset(clip, this.timeMs - clip.start);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
play() {
|
|
615
|
+
if (this.playing) return;
|
|
616
|
+
if (this.totalDuration() <= 0) return;
|
|
617
|
+
const clip = this.clipAtTime(this.timeMs) ?? this.nextClipAfterTime(this.timeMs);
|
|
618
|
+
if (!clip) return;
|
|
619
|
+
if (this.timeMs < clip.start) this.timeMs = clip.start;
|
|
620
|
+
this.activate(clip);
|
|
621
|
+
this.seekVideoToClipOffset(clip, this.timeMs - clip.start);
|
|
622
|
+
const v = this.videos.get(clip.sourceId);
|
|
623
|
+
if (!v) return;
|
|
624
|
+
void v.play().catch((err) => this.onError?.(err));
|
|
625
|
+
this.playing = true;
|
|
626
|
+
this.lastFrameTs = performance.now();
|
|
627
|
+
}
|
|
628
|
+
pause() {
|
|
629
|
+
if (!this.playing) return;
|
|
630
|
+
this.playing = false;
|
|
631
|
+
if (this.currentClipId) {
|
|
632
|
+
const clip = this.clipById(this.currentClipId);
|
|
633
|
+
if (clip) this.videos.get(clip.sourceId)?.pause();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
isPlaying() {
|
|
637
|
+
return this.playing;
|
|
638
|
+
}
|
|
639
|
+
getTime() {
|
|
640
|
+
return this.timeMs;
|
|
641
|
+
}
|
|
642
|
+
seek(timeMs) {
|
|
643
|
+
const total = this.totalDuration();
|
|
644
|
+
if (total <= 0) {
|
|
645
|
+
this.timeMs = 0;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const clamped = Math.max(0, Math.min(timeMs, total));
|
|
649
|
+
this.timeMs = clamped;
|
|
650
|
+
const clip = this.clipAtTime(clamped);
|
|
651
|
+
if (clip) {
|
|
652
|
+
this.activate(clip);
|
|
653
|
+
this.seekVideoToClipOffset(clip, clamped - clip.start);
|
|
654
|
+
} else {
|
|
655
|
+
this.activate(null);
|
|
656
|
+
}
|
|
657
|
+
this.onTimeUpdate?.(clamped);
|
|
658
|
+
}
|
|
659
|
+
destroy() {
|
|
660
|
+
this.stopTickLoop();
|
|
661
|
+
for (const v of this.videos.values()) {
|
|
662
|
+
v.pause();
|
|
663
|
+
v.removeAttribute("src");
|
|
664
|
+
v.load();
|
|
665
|
+
}
|
|
666
|
+
this.videos.clear();
|
|
667
|
+
this.mount.remove();
|
|
668
|
+
}
|
|
669
|
+
// --- internals -------------------------------------------------------
|
|
670
|
+
syncSources() {
|
|
671
|
+
const wanted = new Set(this.project.sources.map((s) => s.id));
|
|
672
|
+
for (const [id, v] of this.videos) {
|
|
673
|
+
if (!wanted.has(id)) {
|
|
674
|
+
v.pause();
|
|
675
|
+
this.videos.delete(id);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
for (const src of this.project.sources) {
|
|
679
|
+
if (src.kind !== "video") continue;
|
|
680
|
+
if (this.videos.has(src.id)) continue;
|
|
681
|
+
const v = document.createElement("video");
|
|
682
|
+
v.preload = "auto";
|
|
683
|
+
v.playsInline = true;
|
|
684
|
+
v.muted = false;
|
|
685
|
+
v.src = src.url;
|
|
686
|
+
const sourceId = src.id;
|
|
687
|
+
v.addEventListener(
|
|
688
|
+
"error",
|
|
689
|
+
() => this.onError?.(new Error(`Failed to load ${src.url}`))
|
|
690
|
+
);
|
|
691
|
+
v.addEventListener("loadedmetadata", () => {
|
|
692
|
+
this.onReady?.();
|
|
693
|
+
const durMs = Math.round(v.duration * 1e3);
|
|
694
|
+
if (Number.isFinite(durMs) && durMs > 0) {
|
|
695
|
+
this.onSourceMetadata?.(sourceId, durMs);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
this.videos.set(src.id, v);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
activate(clip) {
|
|
702
|
+
if (clip?.id === this.currentClipId) return;
|
|
703
|
+
if (this.currentClipId) {
|
|
704
|
+
const prev = this.clipById(this.currentClipId);
|
|
705
|
+
if (prev) this.videos.get(prev.sourceId)?.pause();
|
|
706
|
+
}
|
|
707
|
+
this.currentClipId = clip ? clip.id : null;
|
|
708
|
+
}
|
|
709
|
+
seekVideoToClipOffset(clip, offsetMs) {
|
|
710
|
+
const v = this.videos.get(clip.sourceId);
|
|
711
|
+
if (!v) return;
|
|
712
|
+
const target = (clip.in + Math.max(0, offsetMs)) / 1e3;
|
|
713
|
+
if (Math.abs(v.currentTime - target) > 0.05) {
|
|
714
|
+
v.currentTime = target;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
clipById(id) {
|
|
718
|
+
for (const t of this.project.tracks) {
|
|
719
|
+
for (const c of t.clips) if (c.id === id) return c;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
clipAtTime(timeMs) {
|
|
724
|
+
for (const t of this.project.tracks) {
|
|
725
|
+
if (t.kind !== "video") continue;
|
|
726
|
+
for (const c of t.clips) {
|
|
727
|
+
if (timeMs >= c.start && timeMs < c.start + (c.out - c.in)) return c;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
nextClipAfterTime(timeMs) {
|
|
733
|
+
let best = null;
|
|
734
|
+
for (const t of this.project.tracks) {
|
|
735
|
+
if (t.kind !== "video") continue;
|
|
736
|
+
for (const c of t.clips) {
|
|
737
|
+
if (c.start >= timeMs && (!best || c.start < best.start)) best = c;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return best;
|
|
741
|
+
}
|
|
742
|
+
totalDuration() {
|
|
743
|
+
let max = 0;
|
|
744
|
+
for (const t of this.project.tracks) {
|
|
745
|
+
if (t.kind !== "video") continue;
|
|
746
|
+
for (const c of t.clips) {
|
|
747
|
+
const e = c.start + (c.out - c.in);
|
|
748
|
+
if (e > max) max = e;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return max;
|
|
752
|
+
}
|
|
753
|
+
resizeCanvas() {
|
|
754
|
+
const rect = this.mount.getBoundingClientRect();
|
|
755
|
+
const dpr = window.devicePixelRatio || 1;
|
|
756
|
+
const w = Math.max(1, Math.floor(rect.width * dpr));
|
|
757
|
+
const h = Math.max(1, Math.floor(rect.height * dpr));
|
|
758
|
+
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
759
|
+
this.canvas.width = w;
|
|
760
|
+
this.canvas.height = h;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
startTickLoop() {
|
|
764
|
+
this.lastFrameTs = performance.now();
|
|
765
|
+
const tick = (now) => {
|
|
766
|
+
this.resizeCanvas();
|
|
767
|
+
if (this.playing) {
|
|
768
|
+
const dtMs = now - this.lastFrameTs;
|
|
769
|
+
this.lastFrameTs = now;
|
|
770
|
+
this.advance(dtMs);
|
|
771
|
+
}
|
|
772
|
+
this.paint();
|
|
773
|
+
this.rafHandle = requestAnimationFrame(tick);
|
|
774
|
+
};
|
|
775
|
+
this.rafHandle = requestAnimationFrame(tick);
|
|
776
|
+
}
|
|
777
|
+
stopTickLoop() {
|
|
778
|
+
if (this.rafHandle != null) {
|
|
779
|
+
cancelAnimationFrame(this.rafHandle);
|
|
780
|
+
this.rafHandle = null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
advance(dtMs) {
|
|
784
|
+
if (this.project.tracks.length === 0) return;
|
|
785
|
+
this.timeMs += dtMs;
|
|
786
|
+
const totalDur = this.totalDuration();
|
|
787
|
+
if (this.timeMs >= totalDur) {
|
|
788
|
+
this.timeMs = totalDur;
|
|
789
|
+
this.onTimeUpdate?.(this.timeMs);
|
|
790
|
+
this.pause();
|
|
791
|
+
this.onEnded?.();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const clip = this.clipAtTime(this.timeMs);
|
|
795
|
+
if (!clip) {
|
|
796
|
+
const next = this.nextClipAfterTime(this.timeMs);
|
|
797
|
+
if (next) {
|
|
798
|
+
this.timeMs = next.start;
|
|
799
|
+
this.activate(next);
|
|
800
|
+
this.seekVideoToClipOffset(next, 0);
|
|
801
|
+
const v = this.videos.get(next.sourceId);
|
|
802
|
+
if (v) void v.play().catch((err) => this.onError?.(err));
|
|
803
|
+
} else {
|
|
804
|
+
this.pause();
|
|
805
|
+
this.onEnded?.();
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
} else if (clip.id !== this.currentClipId) {
|
|
809
|
+
this.activate(clip);
|
|
810
|
+
this.seekVideoToClipOffset(clip, this.timeMs - clip.start);
|
|
811
|
+
const v = this.videos.get(clip.sourceId);
|
|
812
|
+
if (v) void v.play().catch((err) => this.onError?.(err));
|
|
813
|
+
}
|
|
814
|
+
this.onTimeUpdate?.(this.timeMs);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* One paint per rAF — clears the canvas, draws the current active
|
|
818
|
+
* video frame letterboxed to fit, then refreshes the HUD. Done
|
|
819
|
+
* unconditionally (not just on `playing`) so the HUD frame counter
|
|
820
|
+
* and the seek preview both update when paused.
|
|
821
|
+
*/
|
|
822
|
+
paint() {
|
|
823
|
+
const cw = this.canvas.width;
|
|
824
|
+
const ch = this.canvas.height;
|
|
825
|
+
this.ctx.clearRect(0, 0, cw, ch);
|
|
826
|
+
const clip = this.currentClipId ? this.clipById(this.currentClipId) : null;
|
|
827
|
+
const v = clip ? this.videos.get(clip.sourceId) : null;
|
|
828
|
+
if (v && v.videoWidth > 0 && v.videoHeight > 0) {
|
|
829
|
+
const vw = v.videoWidth;
|
|
830
|
+
const vh = v.videoHeight;
|
|
831
|
+
const scale = Math.min(cw / vw, ch / vh);
|
|
832
|
+
const dw = vw * scale;
|
|
833
|
+
const dh = vh * scale;
|
|
834
|
+
const dx = (cw - dw) / 2;
|
|
835
|
+
const dy = (ch - dh) / 2;
|
|
836
|
+
this.ctx.drawImage(v, dx, dy, dw, dh);
|
|
837
|
+
this.paintedFrames += 1;
|
|
838
|
+
}
|
|
839
|
+
this.updateBadge();
|
|
840
|
+
}
|
|
841
|
+
updateBadge() {
|
|
842
|
+
if (!this.badge) return;
|
|
843
|
+
const sec = (this.timeMs / 1e3).toFixed(2);
|
|
844
|
+
this.badge.textContent = `engine: canvas compositor \u2022 t=${sec}s \u2022 frames painted: ${this.paintedFrames}`;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
var canvasCompositorEngineFactory = (opts) => new CanvasCompositorEngine(opts);
|
|
518
848
|
|
|
519
849
|
// src/ui/thumbnails.ts
|
|
520
850
|
var THUMB_HEIGHT = 44;
|
|
@@ -574,11 +904,19 @@ var ThumbnailRibbon = class {
|
|
|
574
904
|
/**
|
|
575
905
|
* Paint thumbnails for the clip's visible window onto `ctx`. The
|
|
576
906
|
* canvas is the per-clip strip — width = clip's px width, height =
|
|
577
|
-
*
|
|
578
|
-
* and the px range we're
|
|
907
|
+
* `pxHeight` (defaults to the cached `THUMB_HEIGHT`). Source-time
|
|
908
|
+
* range derives from the clip's `in/out` and the px range we're
|
|
909
|
+
* drawing into.
|
|
910
|
+
*
|
|
911
|
+
* `pxHeight` lets the caller stretch thumbs to fill a taller clip
|
|
912
|
+
* body when `trackHeight` is configured above the default. Aspect
|
|
913
|
+
* ratio is already broken per-thumb (we slice variable widths from a
|
|
914
|
+
* fixed-aspect cached bitmap), so stretching height too is fine — it
|
|
915
|
+
* preserves the "filmstrip" look without leaving an empty bottom
|
|
916
|
+
* band of the brand gradient showing through.
|
|
579
917
|
*/
|
|
580
|
-
paintStrip(ctx, sourceId, sourceInMs, sourceOutMs, pxWidth) {
|
|
581
|
-
ctx.clearRect(0, 0, pxWidth,
|
|
918
|
+
paintStrip(ctx, sourceId, sourceInMs, sourceOutMs, pxWidth, pxHeight = THUMB_HEIGHT) {
|
|
919
|
+
ctx.clearRect(0, 0, pxWidth, pxHeight);
|
|
582
920
|
const st = this.sources.get(sourceId);
|
|
583
921
|
if (!st) return;
|
|
584
922
|
if (sourceOutMs <= sourceInMs || pxWidth <= 0) return;
|
|
@@ -591,10 +929,10 @@ var ThumbnailRibbon = class {
|
|
|
591
929
|
const x = Math.round(i * pxWidth / count);
|
|
592
930
|
const w = Math.round((i + 1) * pxWidth / count) - x;
|
|
593
931
|
if (bmp) {
|
|
594
|
-
ctx.drawImage(bmp, x, 0, w,
|
|
932
|
+
ctx.drawImage(bmp, x, 0, w, pxHeight);
|
|
595
933
|
} else {
|
|
596
934
|
ctx.fillStyle = "rgba(255,255,255,0.04)";
|
|
597
|
-
ctx.fillRect(x, 0, w,
|
|
935
|
+
ctx.fillRect(x, 0, w, pxHeight);
|
|
598
936
|
this.enqueue(st, bucket);
|
|
599
937
|
}
|
|
600
938
|
}
|
|
@@ -949,7 +1287,7 @@ function drawClipAt(ctx, clip, trackIndex, startMs, sources, state, style, thumb
|
|
|
949
1287
|
roundRect(ctx, startX, y, widthPx, h, 6);
|
|
950
1288
|
ctx.clip();
|
|
951
1289
|
ctx.translate(startX, y);
|
|
952
|
-
thumbs.paintStrip(ctx, clip.sourceId, clip.in, clip.out, widthPx);
|
|
1290
|
+
thumbs.paintStrip(ctx, clip.sourceId, clip.in, clip.out, widthPx, h);
|
|
953
1291
|
ctx.restore();
|
|
954
1292
|
ctx.strokeStyle = "rgba(255,255,255,0.2)";
|
|
955
1293
|
ctx.lineWidth = 1;
|
|
@@ -1220,7 +1558,7 @@ function hitTest(x, y, ctx) {
|
|
|
1220
1558
|
if (track2.clips.length === 0) {
|
|
1221
1559
|
const btnSize = 18;
|
|
1222
1560
|
const btnLeft = HEADER_WIDTH - btnSize - 6;
|
|
1223
|
-
const btnTop = RULER_HEIGHT + ti2 *
|
|
1561
|
+
const btnTop = RULER_HEIGHT + ti2 * TRACK_HEIGHT + (TRACK_HEIGHT - btnSize) / 2 - ctx.scrollTop;
|
|
1224
1562
|
if (x >= btnLeft && x <= btnLeft + btnSize && y >= btnTop && y <= btnTop + btnSize) {
|
|
1225
1563
|
return { kind: "header-delete", trackIndex: ti2 };
|
|
1226
1564
|
}
|
|
@@ -1520,7 +1858,6 @@ var Timeline = class _Timeline {
|
|
|
1520
1858
|
const dpr = window.devicePixelRatio || 1;
|
|
1521
1859
|
this.canvas.width = Math.floor(this.viewportWidth * dpr);
|
|
1522
1860
|
this.canvas.height = Math.floor(this.viewportHeight * dpr);
|
|
1523
|
-
this.canvas.style.height = `${this.viewportHeight}px`;
|
|
1524
1861
|
this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1525
1862
|
}
|
|
1526
1863
|
computeFitScale() {
|
|
@@ -1937,7 +2274,8 @@ var Timeline = class _Timeline {
|
|
|
1937
2274
|
const tiRaw = this.trackIndexAtY(y);
|
|
1938
2275
|
const phantomIdx = this.project.tracks.length;
|
|
1939
2276
|
const phantomScreenY = RULER_HEIGHT + phantomIdx * TRACK_HEIGHT - this.scrollTop;
|
|
1940
|
-
const
|
|
2277
|
+
const viewportBottom = this.viewportHeight - SCROLLBAR_THICKNESS;
|
|
2278
|
+
const onPhantom = y >= phantomScreenY && y < Math.max(phantomScreenY + TRACK_HEIGHT, viewportBottom);
|
|
1941
2279
|
const intendedTrackIndex = onPhantom ? phantomIdx : tiRaw >= 0 ? tiRaw : drag.trackIndex;
|
|
1942
2280
|
let ghostTrackIndex = intendedTrackIndex;
|
|
1943
2281
|
let overlap = false;
|
|
@@ -2702,7 +3040,19 @@ var Editor = class _Editor {
|
|
|
2702
3040
|
this.pxPerSec = clampScale2(opts.initialScale ?? DEFAULT_PX_PER_SEC);
|
|
2703
3041
|
this.snap = opts.initialSnap !== false;
|
|
2704
3042
|
this.locale = mergeLocale(opts.locale);
|
|
3043
|
+
if (opts.trackHeight != null || opts.rulerHeight != null) {
|
|
3044
|
+
setTimelineMetrics({
|
|
3045
|
+
...opts.trackHeight != null ? { trackHeight: opts.trackHeight } : {},
|
|
3046
|
+
...opts.rulerHeight != null ? { rulerHeight: opts.rulerHeight } : {}
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
2705
3049
|
applyTheme(this.container, opts.theme);
|
|
3050
|
+
if (opts.timelineHeight != null && opts.timelineHeight > 0) {
|
|
3051
|
+
this.container.style.setProperty(
|
|
3052
|
+
"--aicut-timeline-height",
|
|
3053
|
+
`${Math.round(opts.timelineHeight)}px`
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
2706
3056
|
this.ui = new EditorUI(this.container, this, {
|
|
2707
3057
|
onPlayToggle: () => this.togglePlay(),
|
|
2708
3058
|
onSplit: () => this.split(),
|
|
@@ -2720,7 +3070,11 @@ var Editor = class _Editor {
|
|
|
2720
3070
|
onMoveClip: (id, opts2) => this.moveClip(id, opts2),
|
|
2721
3071
|
onResizeClip: (id, edits) => this.resizeClip(id, edits)
|
|
2722
3072
|
});
|
|
2723
|
-
|
|
3073
|
+
const engineFactory = opts.playbackEngine ?? ((o) => new HtmlVideoEngine(o));
|
|
3074
|
+
this.engine = engineFactory({
|
|
3075
|
+
host: this.ui.previewHost,
|
|
3076
|
+
project: this.project
|
|
3077
|
+
});
|
|
2724
3078
|
this.engine.onTimeUpdate = (ms) => {
|
|
2725
3079
|
this.bus.emit("time", { timeMs: ms });
|
|
2726
3080
|
this.ui.onTimeTick(ms);
|
|
@@ -3265,6 +3619,6 @@ function clampScale2(s) {
|
|
|
3265
3619
|
return Math.max(MIN_PX_PER_SEC, Math.min(MAX_PX_PER_SEC, s));
|
|
3266
3620
|
}
|
|
3267
3621
|
|
|
3268
|
-
export { Editor, HEADER_WIDTH, RULER_HEIGHT, TRACK_HEIGHT, Timeline, createEmptyProject, createId, normalizeProject };
|
|
3622
|
+
export { CanvasCompositorEngine, Editor, HEADER_WIDTH, HtmlVideoEngine, RULER_HEIGHT, TRACK_HEIGHT, Timeline, canvasCompositorEngineFactory, createEmptyProject, createId, htmlVideoEngineFactory, normalizeProject, setTimelineMetrics };
|
|
3269
3623
|
//# sourceMappingURL=index.js.map
|
|
3270
3624
|
//# sourceMappingURL=index.js.map
|