Drive playhead from the video element; reseek on stack moves
- the <video>'s own play/pause now engage/disengage the segment-aware tick (:on-play/:on-pause), so the native controls drive the playhead and follow the multicam cuts, not just the toolbar button (which now just toggles the video) - entering/leaving a context (expand/collapse/pop-to) pauses and seeks the video to that context's remembered playhead ([:view :playheads ctx], 0 = its start), so the playhead tracks the timeline stack instead of ticking a stale context Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ee2cdf7b9
commit
5861113aeb
2 changed files with 51 additions and 28 deletions
|
|
@ -71,13 +71,21 @@
|
|||
(rf/reg-event-db ::set-zoom (fn [db [_ z]] (assoc-in db [:view :zoom] z)))
|
||||
(rf/reg-event-db ::set-row-h (fn [db [_ h]] (assoc-in db [:view :row-h] h)))
|
||||
|
||||
(rf/reg-event-db ::expand (fn [db [_ gid]] (update-in db [:view :stack] conj gid)))
|
||||
(rf/reg-event-db ::collapse (fn [db _] (update-in db [:view :stack]
|
||||
(fn [s] (if (> (count s) 1) (pop s) s)))))
|
||||
(rf/reg-event-db ::pop-to
|
||||
(fn [db [_ gid]]
|
||||
(update-in db [:view :stack]
|
||||
(fn [s] (let [i (first (keep-indexed #(when (= gid %2) %1) s))]
|
||||
;; moving around the timeline stack: pause and seek the video to the context
|
||||
;; we land in, at its remembered playhead (0 = that context's start).
|
||||
(defn- enter-ctx
|
||||
[db stack-fn]
|
||||
(let [db (update-in db [:view :stack] stack-fn)
|
||||
ctx (peek (get-in db [:view :stack]))
|
||||
segs (scene/content-segments (:scene db) ctx)
|
||||
sf (scene/local->source segs (scene/playhead (:view db) ctx))]
|
||||
{:db db :player/pause true :player/seek (when sf (/ sf (:fps db)))}))
|
||||
|
||||
(rf/reg-event-fx ::expand (fn [{:keys [db]} [_ gid]] (enter-ctx db #(conj % gid))))
|
||||
(rf/reg-event-fx ::collapse (fn [{:keys [db]} _] (enter-ctx db #(if (> (count %) 1) (pop %) %))))
|
||||
(rf/reg-event-fx ::pop-to
|
||||
(fn [{:keys [db]} [_ gid]]
|
||||
(enter-ctx db (fn [s] (let [i (first (keep-indexed #(when (= gid %2) %1) s))]
|
||||
(if i (subvec s 0 (inc i)) s))))))
|
||||
|
||||
;; --- authoring ------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -23,15 +23,19 @@
|
|||
(set! (.-currentTime @video-el) (/ source-frame fps))))
|
||||
|
||||
(defn- seg-at
|
||||
"Index of the segment whose local range contains `local` (0 if none)."
|
||||
"Index of the segment whose LOCAL range contains `local` (0 if none)."
|
||||
[segs local]
|
||||
(or (first (keep-indexed (fn [i {[c d] :local}] (when (<= c local d) i)) segs)) 0))
|
||||
|
||||
(defn- stop-play! []
|
||||
(when @raf (js/cancelAnimationFrame @raf) (reset! raf nil))
|
||||
(reset! play nil)
|
||||
(when-let [v @video-el] (.pause v))
|
||||
(rf/dispatch [::events/set-playing false]))
|
||||
(defn- seg-at-src
|
||||
"Index of the segment whose SOURCE range contains source frame `sf` (0 if none)."
|
||||
[segs sf]
|
||||
(or (first (keep-indexed (fn [i {[a b] :src}] (when (<= a sf b) i)) segs)) 0))
|
||||
|
||||
;; The <video> element is the source of truth for play/pause. Its :on-play /
|
||||
;; :on-pause (below) engage/disengage this segment-aware tick, so the native
|
||||
;; controls and the toolbar button behave identically. `play` holds the frozen
|
||||
;; context being driven; context changes pause + reseek (see ::expand etc).
|
||||
|
||||
(defn- play-tick []
|
||||
(when-let [{:keys [ctx segs fps idx]} @play]
|
||||
|
|
@ -44,25 +48,34 @@
|
|||
(when (> (js/Math.abs (- sf ns)) 1.5) ; only seek at a real discontinuity
|
||||
(seek-video! fps ns))
|
||||
(rf/dispatch [::events/set-playhead ctx nl]))
|
||||
(stop-play!))
|
||||
(.pause v)) ; end → pause event disengages
|
||||
(rf/dispatch [::events/set-playhead ctx (+ ls (- sf ss))])))
|
||||
(when @play (reset! raf (js/requestAnimationFrame play-tick)))))
|
||||
|
||||
(defn- start-play! []
|
||||
(let [ctx @(rf/subscribe [::subs/context])
|
||||
segs @(rf/subscribe [::subs/segments])
|
||||
(defn- engage-play!
|
||||
"Drive the playhead from the (already-playing) video, in the current context,
|
||||
from wherever currentTime is. Idempotent."
|
||||
[]
|
||||
(let [segs @(rf/subscribe [::subs/segments])
|
||||
ctx @(rf/subscribe [::subs/context])
|
||||
fps @(rf/subscribe [::subs/fps])
|
||||
ph @(rf/subscribe [::subs/playhead])]
|
||||
(when (and @video-el (seq segs))
|
||||
(let [idx (seg-at segs ph)
|
||||
{[ss _] :src [ls _] :local} (nth segs idx)]
|
||||
(reset! play {:ctx ctx :segs segs :fps fps :idx idx})
|
||||
(seek-video! fps (+ ss (- ph ls))) ; start from the playhead's source
|
||||
(.play @video-el)
|
||||
v @video-el]
|
||||
(when (and v (seq segs) (not @play))
|
||||
(reset! play {:ctx ctx :segs segs :fps fps :idx (seg-at-src segs (* (.-currentTime v) fps))})
|
||||
(rf/dispatch [::events/set-playing true])
|
||||
(reset! raf (js/requestAnimationFrame play-tick))))))
|
||||
(reset! raf (js/requestAnimationFrame play-tick)))))
|
||||
|
||||
(defn- toggle-play! [] (if @play (stop-play!) (start-play!)))
|
||||
(defn- disengage! []
|
||||
(when @raf (js/cancelAnimationFrame @raf) (reset! raf nil))
|
||||
(reset! play nil)
|
||||
(rf/dispatch [::events/set-playing false]))
|
||||
|
||||
(defn- toggle-play! []
|
||||
(when-let [v @video-el] (if (.-paused v) (.play v) (.pause v))))
|
||||
|
||||
;; effects the stack events use to keep the player on the current context
|
||||
(rf/reg-fx :player/pause (fn [_] (when-let [v @video-el] (.pause v))))
|
||||
(rf/reg-fx :player/seek (fn [secs] (when (and @video-el secs) (set! (.-currentTime @video-el) secs))))
|
||||
|
||||
(defn- goto! [local]
|
||||
"Move the playhead to local frame `local` and seek the video to match."
|
||||
|
|
@ -76,6 +89,8 @@
|
|||
(defn video-monitor []
|
||||
[:video {:src @(rf/subscribe [::subs/clip-url]) :controls true :preload "auto"
|
||||
:plays-inline true :webkit-playsinline "true"
|
||||
:on-play (fn [_] (engage-play!))
|
||||
:on-pause (fn [_] (disengage!))
|
||||
:ref (fn [n] (when n (reset! video-el n)))}])
|
||||
|
||||
(defn- clip-label
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue