46aff56577
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.
67 lines
3.0 KiB
JavaScript
67 lines
3.0 KiB
JavaScript
/* minimal terminal-style video player — custom controls in our own font.
|
|
re-inits after pjax navigation. */
|
|
(function () {
|
|
"use strict";
|
|
function fmt(t) {
|
|
t = Math.max(0, t | 0);
|
|
var m = (t / 60) | 0, s = t % 60;
|
|
return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
|
|
}
|
|
function init(root) {
|
|
var v = root.querySelector("video");
|
|
var play = root.querySelector(".player__play");
|
|
var big = root.querySelector(".player__big");
|
|
var scrub = root.querySelector(".player__scrub");
|
|
var fill = root.querySelector(".player__fill");
|
|
var time = root.querySelector(".player__time");
|
|
var mute = root.querySelector(".player__mute");
|
|
var full = root.querySelector(".player__full");
|
|
if (!v) return;
|
|
|
|
function toggle() { if (v.paused) v.play(); else v.pause(); }
|
|
if (play) play.addEventListener("click", toggle);
|
|
if (big) big.addEventListener("click", toggle);
|
|
v.addEventListener("click", toggle);
|
|
|
|
// 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;
|
|
});
|
|
if (mute) mute.addEventListener("click", function () { v.muted = !v.muted; mute.textContent = v.muted ? "muted" : "vol"; });
|
|
if (full) full.addEventListener("click", function () {
|
|
if (document.fullscreenElement) document.exitFullscreen();
|
|
else if (root.requestFullscreen) root.requestFullscreen();
|
|
});
|
|
}
|
|
function initAll() {
|
|
var ps = document.querySelectorAll(".player");
|
|
for (var i = 0; i < ps.length; i++) if (!ps[i].__init) { ps[i].__init = 1; init(ps[i]); }
|
|
}
|
|
initAll();
|
|
document.addEventListener("pjax:load", initAll);
|
|
})();
|