implement video prefetching for improved playback performance
This commit is contained in:
1 parent
cca5dd00ae
commit
b562b57bb4
1 file changed
+83
-5
+83
-5
@@ -50,6 +50,70 @@
|
|||||||
let sortMode = "oldest";
|
let sortMode = "oldest";
|
||||||
let current = null;
|
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) {
|
function tsToDate(ts) {
|
||||||
const m = /^(\d{4})-(\d{2})-(\d{2})[_ ](\d{2})-(\d{2})-(\d{2})/.exec(
|
const m = /^(\d{4})-(\d{2})-(\d{2})[_ ](\d{2})-(\d{2})-(\d{2})/.exec(
|
||||||
ts || "",
|
ts || "",
|
||||||
@@ -197,7 +261,24 @@
|
|||||||
current = clip;
|
current = clip;
|
||||||
player.src = clip.url;
|
player.src = clip.url;
|
||||||
player.load();
|
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;
|
placeholder.hidden = true;
|
||||||
badge.classList.add("visible");
|
badge.classList.add("visible");
|
||||||
@@ -356,10 +437,7 @@
|
|||||||
c.filename === current.filename
|
c.filename === current.filename
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
i =
|
i = e.key === "j" ? Math.min(list.length - 1, i + 1) : Math.max(0, i - 1);
|
||||||
e.key === "j"
|
|
||||||
? Math.min(list.length - 1, i + 1)
|
|
||||||
: Math.max(0, i - 1);
|
|
||||||
if (list[i]) playClip(list[i]);
|
if (list[i]) playClip(list[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user