diff --git a/tl/src/tl/views.cljs b/tl/src/tl/views.cljs index 51eb52b..51d77b2 100644 --- a/tl/src/tl/views.cljs +++ b/tl/src/tl/views.cljs @@ -15,6 +15,7 @@ (defonce body-scroll-el (atom nil)) ; the vertical (tracks) scroll container (defonce playhead-el (atom nil)) ; the single full-height playhead overlay (defonce played-track (atom nil)) ; last track auto-scrolled into view during playback +(defonce pending-seek (atom nil)) ; seconds to seek to once the element is seekable (iOS) (defonce play (atom nil)) ; {:ctx :segs :fps :idx} while playing, else nil (defonce active-insert! (atom nil)) ; the live content-editor's (insert! link) fn (declare commit-link!) @@ -67,7 +68,22 @@ (defn- seek-video! [fps source-frame] (when (and @video-el source-frame) - (set! (.-currentTime @video-el) (/ source-frame fps)))) + (let [v @video-el secs (/ source-frame fps)] + ;; iOS only honours currentTime once the element is seekable (readyState + ;; >= HAVE_METADATA). Before that — a deep link, or the very first tap that + ;; triggers loading — stash the target and apply it on loadedmetadata. + (if (>= (.-readyState v) 1) + (do (reset! pending-seek nil) (set! (.-currentTime v) secs)) + (reset! pending-seek secs))))) + +(defn- flush-pending-seek! + "Apply a seek that was deferred while the element wasn't seekable. Returns true + if it did one." + [] + (when-let [secs @pending-seek] + (reset! pending-seek nil) + (when-let [v @video-el] (set! (.-currentTime v) secs)) + true)) (defn- follow! ([fps zoom playhead] (follow! fps zoom playhead false)) @@ -219,13 +235,18 @@ ;; (Re)loading a clip resets the element to currentTime 0, so a restored ;; / deep-linked playhead would show in the UI but play from 0. Seek the ;; element to the current playhead once its metadata is ready. + ;; metadata is now loaded ⇒ the element is finally seekable. Apply any + ;; seek we deferred (the iOS deep-link / first-tap case); otherwise, if + ;; we're not driving playback, align the element to the playhead. :on-loaded-metadata (fn [_] - (when-not @play - (let [segs @(rf/subscribe [::subs/segments]) - fps @(rf/subscribe [::subs/fps]) - ph @(rf/subscribe [::subs/playhead])] - (seek-video! fps (scene/local->source segs ph))))) + (when-not (flush-pending-seek!) + (when-not @play + (let [segs @(rf/subscribe [::subs/segments]) + fps @(rf/subscribe [::subs/fps]) + ph @(rf/subscribe [::subs/playhead])] + (seek-video! fps (scene/local->source segs ph)))))) + :on-can-play (fn [_] (flush-pending-seek!)) ; belt-and-suspenders for iOS :on-play (fn [_] (engage-play!)) :on-pause (fn [_] (disengage!)) :ref (fn [n] (when n (reset! video-el n)))}])