@bycrux/editor 0.5.0 → 0.5.2
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/package.json
CHANGED
|
@@ -388,7 +388,9 @@ function ReviewSurface<P extends Project>({
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
return (
|
|
391
|
-
<div className="flex flex-1 overflow-hidden">
|
|
391
|
+
<div className="flex flex-col flex-1 overflow-hidden">
|
|
392
|
+
{/* Work area — editor body + version rail, side by side */}
|
|
393
|
+
<div className="flex flex-1 overflow-hidden min-h-0">
|
|
392
394
|
{/* Main: preview + timeline */}
|
|
393
395
|
<div className="flex flex-col flex-1 overflow-hidden">
|
|
394
396
|
<div className="flex-1 flex items-center justify-center bg-black overflow-hidden p-2">
|
|
@@ -473,8 +475,8 @@ function ReviewSurface<P extends Project>({
|
|
|
473
475
|
</div>
|
|
474
476
|
</div>
|
|
475
477
|
|
|
476
|
-
{/* Right
|
|
477
|
-
{(adapter.listVersionHistory || slots?.
|
|
478
|
+
{/* Right rail — version history + run history slot */}
|
|
479
|
+
{(adapter.listVersionHistory || slots?.runHistory) && (
|
|
478
480
|
<div className="w-48 shrink-0 border-l border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-950 flex flex-col overflow-hidden">
|
|
479
481
|
{adapter.listVersionHistory && (
|
|
480
482
|
<VersionPanel versions={versions} restoring={restoring} onRestore={handleRestoreVersion} />
|
|
@@ -483,7 +485,16 @@ function ReviewSurface<P extends Project>({
|
|
|
483
485
|
RunSnapshot / project.history are host-only types — the package never
|
|
484
486
|
reads them. When absent nothing is rendered. */}
|
|
485
487
|
{slots?.runHistory}
|
|
486
|
-
|
|
488
|
+
</div>
|
|
489
|
+
)}
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
{/* Project media / assets — full-width region stacked BELOW the editor,
|
|
493
|
+
mirroring CarouselEditor's layout (was previously crammed into the
|
|
494
|
+
narrow right rail). The host's panel manages its own scroll. */}
|
|
495
|
+
{slots?.assetsPanel && (
|
|
496
|
+
<div className="shrink-0 border-t border-gray-200 dark:border-gray-800 w-full flex flex-col max-h-[45%] overflow-hidden">
|
|
497
|
+
{slots.assetsPanel}
|
|
487
498
|
</div>
|
|
488
499
|
)}
|
|
489
500
|
|
|
@@ -50,6 +50,10 @@ export function useVideoPlayback(
|
|
|
50
50
|
const loopOffsetRef = useRef(0)
|
|
51
51
|
const rafRef = useRef<number | null>(null)
|
|
52
52
|
const rafLastMs = useRef<number | null>(null)
|
|
53
|
+
// rAF clock for VIDEO projects — drives clip-boundary detection at ~60Hz
|
|
54
|
+
// instead of the <video> element's coarse `timeupdate` event (~4Hz). See the
|
|
55
|
+
// effect below for why.
|
|
56
|
+
const videoRafRef = useRef<number | null>(null)
|
|
53
57
|
const audioRefsMap = useRef<Map<string, HTMLAudioElement>>(new Map())
|
|
54
58
|
const audioSrcMap = useRef<Map<string, string>>(new Map())
|
|
55
59
|
// Web Audio API: GainNode per audio track allows volume > 1.0 (amplification).
|
|
@@ -693,6 +697,39 @@ export function useVideoPlayback(
|
|
|
693
697
|
handleTimeUpdate()
|
|
694
698
|
}, [handleTimeUpdate])
|
|
695
699
|
|
|
700
|
+
// ── Video boundary clock ─────────────────────────────────────────────────
|
|
701
|
+
// Drive clip-boundary detection from requestAnimationFrame (~60Hz) rather
|
|
702
|
+
// than relying on the <video> element's `timeupdate` event, which only fires
|
|
703
|
+
// ~every 250ms. Under timeupdate-gating the active clip plays up to a full
|
|
704
|
+
// ~250ms PAST its outPoint before the swap fires; on a silence-trimmed
|
|
705
|
+
// single-source timeline that overshoot is trimmed-out footage playing past
|
|
706
|
+
// the cut — the "underlying video keeps playing in the background" bug.
|
|
707
|
+
// Polling currentTime every frame tightens the boundary to ~16ms.
|
|
708
|
+
// handleTimeUpdate is idempotent (preload + swap are guarded), so the
|
|
709
|
+
// timeupdate event firing in addition to this is harmless. Canvas projects
|
|
710
|
+
// advance time via their own rAF above and are excluded here.
|
|
711
|
+
useEffect(() => {
|
|
712
|
+
if (isCanvasProject || !isPlaying) {
|
|
713
|
+
if (videoRafRef.current !== null) {
|
|
714
|
+
cancelAnimationFrame(videoRafRef.current)
|
|
715
|
+
videoRafRef.current = null
|
|
716
|
+
}
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
function pump() {
|
|
720
|
+
// The gap clock owns time during gaps; handleTimeUpdate no-ops then.
|
|
721
|
+
if (!inGapRef.current) handleTimeUpdate()
|
|
722
|
+
videoRafRef.current = requestAnimationFrame(pump)
|
|
723
|
+
}
|
|
724
|
+
videoRafRef.current = requestAnimationFrame(pump)
|
|
725
|
+
return () => {
|
|
726
|
+
if (videoRafRef.current !== null) {
|
|
727
|
+
cancelAnimationFrame(videoRafRef.current)
|
|
728
|
+
videoRafRef.current = null
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}, [isPlaying, isCanvasProject, handleTimeUpdate])
|
|
732
|
+
|
|
696
733
|
function togglePlay() {
|
|
697
734
|
// GESTURE-ANCHORED: same rationale as the keydown handler — resume the
|
|
698
735
|
// AudioContext synchronously inside this user-gesture call so a wired
|