implement video prefetching for improved playback performance

This commit is contained in:
zwitschi committed 2026-07-24 14:49:36 +02:00
1 parent cca5dd00ae
commit b562b57bb4
1 file changed
+84 -6
+84 -6
View File
@@ -50,6 +50,70 @@
let sortMode = "oldest";
let current = null;
// ---- Prefetch helpers ----
// Track already-prefetched URLs so we don't re-fetch
var prefetchedUrls = {};
/**
* Prefetch a video URL using a <link rel="prefetch"> element.
* The browser downloads the resource in the background at low priority
* and caches it for instant playback later.
*/
function prefetchVideo(url) {
if (!url || prefetchedUrls[url]) return;
prefetchedUrls[url] = true;
var link = document.createElement("link");
link.rel = "prefetch";
link.as = "video";
link.href = url;
document.head.appendChild(link);
}
/**
* After the current video starts playing, prefetch:
* 1. All sibling camera angles for the same timestamp
* 2. The next clip in the same camera's timeline
*/
function schedulePrefetch(clip) {
// Use requestIdleCallback to run after the browser has handled the
// current frame (i.e. the video has started loading). Fall back to a
// short setTimeout if requestIdleCallback isn't available.
var doPrefetch = function () {
// 1. Sibling camera angles (same timestamp, different cameras)
var group = byTimestamp[clip.timestamp] || {};
Object.keys(group).forEach(function (key) {
if (key !== clip.cam) {
prefetchVideo(group[key].url);
}
});
// 2. Next clip in the same camera's timeline
var sameCam = clips
.filter(function (c) {
return c.cam === clip.cam;
})
.sort(function (a, b) {
if (sortMode === "name") return a.filename.localeCompare(b.filename);
var da = tsToDate(a.timestamp),
db = tsToDate(b.timestamp);
return (da ? da.getTime() : 0) - (db ? db.getTime() : 0);
});
var idx = sameCam.findIndex(function (c) {
return c.timestamp === clip.timestamp && c.filename === clip.filename;
});
if (idx > -1 && sameCam[idx + 1]) {
prefetchVideo(sameCam[idx + 1].url);
}
};
if (typeof requestIdleCallback === "function") {
requestIdleCallback(doPrefetch, { timeout: 2000 });
} else {
setTimeout(doPrefetch, 100);
}
}
function tsToDate(ts) {
const m = /^(\d{4})-(\d{2})-(\d{2})[_ ](\d{2})-(\d{2})-(\d{2})/.exec(
ts || "",
@@ -197,7 +261,24 @@
current = clip;
player.src = clip.url;
player.load();
player.play().catch(function () {});
// Start playback first, prefetch only after the video is actually playing
var prefetchDone = false;
function onPlaying() {
if (prefetchDone) return;
prefetchDone = true;
player.removeEventListener("playing", onPlaying);
schedulePrefetch(clip);
}
player.addEventListener("playing", onPlaying);
player.play().catch(function () {
// Even if autoplay is blocked, still prefetch for when the user clicks play
if (!prefetchDone) {
prefetchDone = true;
player.removeEventListener("playing", onPlaying);
schedulePrefetch(clip);
}
});
placeholder.hidden = true;
badge.classList.add("visible");
@@ -356,10 +437,7 @@
c.filename === current.filename
);
});
i =
e.key === "j"
? Math.min(list.length - 1, i + 1)
: Math.max(0, i - 1);
i = e.key === "j" ? Math.min(list.length - 1, i + 1) : Math.max(0, i - 1);
if (list[i]) playClip(list[i]);
}
});
@@ -391,4 +469,4 @@
});
render();
})();
})();