perf: smoother video player — rAF-driven scrub bar, less cold-start stutter
scrub fill/time were driven off timeupdate, which browsers fire irregularly (a handful of times a second) — visible as the bar stepping instead of gliding. now driven by requestAnimationFrame while playing (display refresh rate), with a direct sync on pause/seeked so it still tracks immediately when not running.\n\npreload=none meant zero data was fetched until the play button was pressed, causing a visible stall right at playback start. preload=metadata lets the browser prime the connection and read the moov atom ahead of time without fetching the full video for visitors who never press play.
This commit is contained in:
+20
-4
@@ -23,14 +23,30 @@
|
||||
if (big) big.addEventListener("click", toggle);
|
||||
v.addEventListener("click", toggle);
|
||||
|
||||
v.addEventListener("play", function () { root.classList.add("is-playing"); if (play) play.textContent = "❚❚"; });
|
||||
v.addEventListener("pause", function () { root.classList.remove("is-playing"); if (play) play.textContent = "▶"; });
|
||||
v.addEventListener("loadedmetadata", function () { if (time) time.textContent = "00:00 / " + fmt(v.duration); });
|
||||
v.addEventListener("timeupdate", function () {
|
||||
// drive the scrub bar off rAF instead of `timeupdate` — timeupdate only
|
||||
// fires a handful of times a second (browser-dependent), which made the
|
||||
// fill visibly step instead of glide; rAF ties it to the display's own
|
||||
// refresh rate, only running while actually playing.
|
||||
var raf = null;
|
||||
function sync() {
|
||||
var p = v.duration ? v.currentTime / v.duration : 0;
|
||||
if (fill) fill.style.width = (p * 100) + "%";
|
||||
if (time) time.textContent = fmt(v.currentTime) + " / " + fmt(v.duration || 0);
|
||||
}
|
||||
function tick() { sync(); raf = requestAnimationFrame(tick); }
|
||||
|
||||
v.addEventListener("play", function () {
|
||||
root.classList.add("is-playing"); if (play) play.textContent = "❚❚";
|
||||
if (!raf) raf = requestAnimationFrame(tick);
|
||||
});
|
||||
v.addEventListener("pause", function () {
|
||||
root.classList.remove("is-playing"); if (play) play.textContent = "▶";
|
||||
if (raf) { cancelAnimationFrame(raf); raf = null; }
|
||||
sync(); // one last sync so the bar lands exactly on the paused frame
|
||||
});
|
||||
v.addEventListener("ended", function () { if (raf) { cancelAnimationFrame(raf); raf = null; } });
|
||||
v.addEventListener("loadedmetadata", function () { if (time) time.textContent = "00:00 / " + fmt(v.duration); });
|
||||
v.addEventListener("seeked", sync); // scrubbing while paused still needs to reflect immediately
|
||||
if (scrub) scrub.addEventListener("click", function (e) {
|
||||
var r = scrub.getBoundingClientRect();
|
||||
if (v.duration) v.currentTime = ((e.clientX - r.left) / r.width) * v.duration;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<figure class="tui__preview">
|
||||
{{ if $video }}
|
||||
<div class="player">
|
||||
<video preload="none" playsinline{{ with .Params.thumbnail }} poster="{{ . }}"{{ end }}>
|
||||
<video preload="metadata" playsinline{{ with .Params.thumbnail }} poster="{{ . }}"{{ end }}>
|
||||
<source src="{{ $video }}" type="video/mp4">
|
||||
</video>
|
||||
<button class="player__big" type="button" aria-label="play">▶</button>
|
||||
|
||||
Reference in New Issue
Block a user