diff --git a/frontend/app.js b/frontend/app.js
index 8da49bd..c174b25 100755
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -623,8 +623,9 @@ const API = {
// Pre-resolve streams for videos the user is likely to play next (server
// /api/streams/warm). Each id is warmed at most once per 20 min per tab.
const warmedAt = new Map();
+const knownServerCachedIds = new Set();
function warmStreams(ids) {
- if (!WEB || navigator.onLine === false) return;
+ if (!WEB || navigator.onLine === false || navigator.connection?.saveData) return;
const now = Date.now();
for (const id of ids) {
if (!id || !/^[A-Za-z0-9_-]{11}$/.test(id) || cachedIds.has(id)) continue;
@@ -634,6 +635,14 @@ function warmStreams(ids) {
}
}
+function warmNextQueue() {
+ if (!window.PrewarmNext || queueIndex < 0) return;
+ warmStreams(window.PrewarmNext.candidates(queue, queueIndex, data.settings, 2, {
+ offline: cachedIds,
+ serverCached: knownServerCachedIds,
+ }));
+}
+
// Resolve a native file path to a URL the WebView can load (Tauri asset
// protocol). Returns null on shells without it.
function toAssetUrl(path) {
@@ -2332,6 +2341,7 @@ const Player = {
if (seq !== this._loadSeq) return; // a newer load took over meanwhile
if (!res || !res.ok) throw new Error(res?.error || 'Could not load streams');
current = res.data;
+ if (current.serverCached && current.meta?.id) knownServerCachedIds.add(current.meta.id);
// Merge richer metadata we may already have from the list card.
current.meta = { ...videoObj, ...current.meta };
@@ -3014,7 +3024,7 @@ function wirePlayerEvents() {
perfMeasure('ytp:tap-to-play', 'ytp:tap');
try { performance.clearMarks('ytp:tap'); } catch { /* ignore */ }
}
- if (Array.isArray(queue) && queueIndex >= 0) warmStreams(queue.slice(queueIndex + 1, queueIndex + 3).map((x) => x && x.id));
+ warmNextQueue();
showSpinner(false);
Player.clearBufferGrace();
if (Player.secondary && !el.paused && Player.secondary.paused) {
@@ -8043,6 +8053,7 @@ function playFromList(list, index, source = '', { playFull = false } = {}) {
unshuffledQueue = null;
// Play-in-full keeps the list order — a service set plays as arranged.
if (data.settings.shuffle && !playFull) applyShuffle();
+ warmNextQueue();
Player.loadVideo(queue[queueIndex]);
renderUpNext();
}
diff --git a/frontend/index.html b/frontend/index.html
index b4de34a..fa61d53 100755
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -693,6 +693,7 @@
+