@bycrux/editor 0.5.1 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bycrux/editor",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -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