fix: start video at deep-linked time via #t= media fragment (iOS)

The real iOS issue: it won't load the media until the first tap and ignores JS
currentTime until the element is seekable (populated very late), so every
load/seek hack started playback from 0. A #t=<seconds> media fragment on the
src is honoured by the media loader during the tap-triggered load itself, so it
starts at the right frame (and renders that frame instead of black). Bake the
deep-linked playhead into the src fragment, cached per clip so live playhead
changes don't reload the element; currentTime still drives in-session seeks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-30 11:21:33 -04:00
parent a62af2584b
commit d9059ad132

View file

@ -216,11 +216,34 @@
(scroll-to-seg-track! segs local)))))))
(defn video-monitor []
[:video {:src @(rf/subscribe [::subs/clip-url]) :controls true :preload "auto"
;; The src carries a #t= media fragment for the deep-linked playhead. That is
;; the only thing that reliably starts iOS at the right time: iOS won't load
;; the media until the first tap and ignores JS currentTime until the element
;; is seekable (which happens very late), but it honours #t= during the media
;; load itself — so the tap loads-and-starts at the right frame. (#t= also
;; nudges iOS to render that frame instead of a black box.) currentTime still
;; drives all in-session seeks once the element is loaded.
;;
;; Cache the src per clip so live playhead changes don't rewrite the fragment
;; and reload the element — the fragment only matters at (re)load time.
(let [cache (atom nil)] ; {:clip url :src "url#t=secs"}
(fn []
(let [clip @(rf/subscribe [::subs/clip-url])
src (cond
(nil? clip) nil
(= clip (:clip @cache)) (:src @cache)
:else
(let [segs @(rf/subscribe [::subs/segments])
fps @(rf/subscribe [::subs/fps])
ph @(rf/subscribe [::subs/playhead])
t (max 0.001 (/ (scene/local->source segs ph) fps))
s (str clip "#t=" (.toFixed t 3))]
(reset! cache {:clip clip :src s})
s))]
[:video {:src src :controls true :preload "auto"
:plays-inline true :webkit-playsinline "true"
;; (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.
;; desktop / once-loaded: align to the playhead when metadata is
;; ready (iOS already arrived there via the #t= fragment).
:on-loaded-metadata
(fn [_]
(when-not @play
@ -230,7 +253,7 @@
(seek-video! fps (scene/local->source segs ph)))))
:on-play (fn [_] (engage-play!))
:on-pause (fn [_] (disengage!))
:ref (fn [n] (when n (reset! video-el n)))}])
:ref (fn [n] (when n (reset! video-el n)))}]))))
(defn- clip-label
"Track-local clip label for the content segment whose :mark is `mid`."