implement video prefetching for improved playback performance
This commit is contained in:
1 parent
cca5dd00ae
commit
b562b57bb4
1 file changed
+84
-6
+84
-6
@@ -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();
|
||||
})();
|
||||
})();
|
||||
Reference in new issue
Block a user