polish(web): warm next track via fetch to shrink media-tile gap
Removing the second <audio> element fixed most of the OS media-tile flicker, but skips now load the next track fresh → a brief gap where Windows shows the generic site entry. Prefetch the next track's first ~4MB with fetch() (not a media element, so no Chromium session flip) to warm the HTTP cache + TLS connection; the src-swap then starts faster and the gap shrinks. Abortable on queue change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
f252586398
commit
6131eea9ec
1 file changed
+18
-8
+18
-8
@@ -276,10 +276,13 @@ function _setAudioSession() {
|
|||||||
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── PRELOAD — shadow Audio element actually buffers the next track ──────────
|
// ─── PRELOAD — warm the next track via fetch (NO second <audio> element) ─────
|
||||||
|
// A second media element makes Chromium flip the OS media session on skip (tile flicker).
|
||||||
|
// So we prefetch the next track's first chunk with fetch() to warm the HTTP cache + TLS
|
||||||
|
// connection — the main element's src-swap then starts faster → smaller gap → less tile flash.
|
||||||
let _preUrl = '';
|
let _preUrl = '';
|
||||||
let _preId = '';
|
let _preId = '';
|
||||||
let _preAudio = null;
|
let _preAbort = null;
|
||||||
|
|
||||||
function _nextIdx() {
|
function _nextIdx() {
|
||||||
if(S.repeat === 'one') return S.qIdx;
|
if(S.repeat === 'one') return S.qIdx;
|
||||||
@@ -293,11 +296,17 @@ function _preloadNext() {
|
|||||||
const ni = _nextIdx();
|
const ni = _nextIdx();
|
||||||
if(ni < 0) return;
|
if(ni < 0) return;
|
||||||
const next = S.queue[ni];
|
const next = S.queue[ni];
|
||||||
if(!next) return;
|
if(!next || _preId === next.id) return;
|
||||||
// (b) EXPERIMENT — no separate preload <audio> element. Navidrome's player keeps a single
|
_preId = next.id;
|
||||||
// element only; a second buffering media element appears to make Chromium flip the OS media
|
_preUrl = streamUrl(next.id);
|
||||||
// session on skip (Windows tile flicker). We rely on the main element's preload="auto" + the
|
// Warm the cache/connection with the first few MB (enough to start playback instantly).
|
||||||
// browser HTTP cache instead, and just warm the next cover art. Easy to revert if it doesn't help.
|
try {
|
||||||
|
_preAbort?.abort();
|
||||||
|
_preAbort = new AbortController();
|
||||||
|
fetch(_preUrl, { signal: _preAbort.signal, headers: { Range: 'bytes=0-4194303' } })
|
||||||
|
.then(r => r.arrayBuffer()) // drain so the bytes land in the HTTP cache
|
||||||
|
.catch(() => {});
|
||||||
|
} catch {}
|
||||||
if(next.coverArt) {
|
if(next.coverArt) {
|
||||||
[256, 512, 1024].forEach(size => {
|
[256, 512, 1024].forEach(size => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@@ -308,7 +317,8 @@ function _preloadNext() {
|
|||||||
|
|
||||||
function _clearPreload() {
|
function _clearPreload() {
|
||||||
_preId = ''; _preUrl = '';
|
_preId = ''; _preUrl = '';
|
||||||
if(_preAudio) { _preAudio.src = ''; _preAudio = null; }
|
try { _preAbort?.abort(); } catch {}
|
||||||
|
_preAbort = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── PLAY HISTORY (for prev-in-shuffle) ──────────────────────────────────────
|
// ─── PLAY HISTORY (for prev-in-shuffle) ──────────────────────────────────────
|
||||||
|
|||||||
Reference in new issue
Block a user