fix: instant OS notification update + real audio preload for gapless playback
- Call updateMediaSession immediately on track change so skip buttons and cover art in the OS notification bar update without waiting for audio load - Replace link[rel=prefetch] (no-op for audio streams) with a shadow Audio element (preload=auto) that actually buffers the next track - Start preloading 60s before track end instead of 30s for more buffer margin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f04d87254b
commit
128437821a
1 file changed
+13
-13
+13
-13
@@ -273,10 +273,10 @@ function _setAudioSession() {
|
||||
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
||||
}
|
||||
|
||||
// ─── PRELOAD (resource hint keeps connection open through Doze gap) ───────
|
||||
let _preUrl = '';
|
||||
let _preId = '';
|
||||
let _preLink = null;
|
||||
// ─── PRELOAD — shadow Audio element actually buffers the next track ──────────
|
||||
let _preUrl = '';
|
||||
let _preId = '';
|
||||
let _preAudio = null;
|
||||
|
||||
function _nextIdx() {
|
||||
if(S.repeat === 'one') return S.qIdx;
|
||||
@@ -286,19 +286,18 @@ function _nextIdx() {
|
||||
}
|
||||
|
||||
function _preloadNext() {
|
||||
if (HAS_BRIDGE) return; // ExoPlayer handles buffering natively
|
||||
if (HAS_BRIDGE) return;
|
||||
const ni = _nextIdx();
|
||||
if(ni < 0) return;
|
||||
const next = S.queue[ni];
|
||||
if(!next || _preId === next.id) return;
|
||||
_preId = next.id;
|
||||
_preUrl = streamUrl(next.id);
|
||||
if(_preLink) _preLink.remove();
|
||||
_preLink = document.createElement('link');
|
||||
_preLink.rel = 'prefetch';
|
||||
_preLink.as = 'fetch';
|
||||
_preLink.href = _preUrl;
|
||||
document.head.appendChild(_preLink);
|
||||
// Real Audio preload — actually buffers the stream so canplay fires instantly
|
||||
if(_preAudio) { _preAudio.src = ''; _preAudio = null; }
|
||||
_preAudio = new Audio();
|
||||
_preAudio.preload = 'auto';
|
||||
_preAudio.src = _preUrl;
|
||||
if(next.coverArt) {
|
||||
[256, 512, 1024].forEach(size => {
|
||||
const img = new Image();
|
||||
@@ -309,7 +308,7 @@ function _preloadNext() {
|
||||
|
||||
function _clearPreload() {
|
||||
_preId = ''; _preUrl = '';
|
||||
if(_preLink) { _preLink.remove(); _preLink = null; }
|
||||
if(_preAudio) { _preAudio.src = ''; _preAudio = null; }
|
||||
}
|
||||
|
||||
// ─── PLAY HISTORY (for prev-in-shuffle) ──────────────────────────────────────
|
||||
@@ -388,6 +387,7 @@ export function playCurrent() {
|
||||
_resetProgressUI();
|
||||
if(navigator.mediaSession) navigator.mediaSession.playbackState = 'playing';
|
||||
updatePlayerUI(s, true);
|
||||
updateMediaSession(s);
|
||||
onNpSongChange(s.id);
|
||||
renderQueue();
|
||||
_scheduleScrobble(s.id, s.duration);
|
||||
@@ -830,7 +830,7 @@ export function updateProgress() {
|
||||
if(!S.audio.duration || S._scrubbing) return;
|
||||
syncMediaSessionPosition();
|
||||
const rem = S.audio.duration - S.audio.currentTime;
|
||||
if(rem > 0 && rem < 30 && !S._trackChanging) _preloadNext();
|
||||
if(rem > 0 && rem < 60 && !S._trackChanging) _preloadNext();
|
||||
const p = (S.audio.currentTime / S.audio.duration) * 100;
|
||||
document.getElementById('progress-fill').style.width = p + '%';
|
||||
document.getElementById('time-cur').textContent = formatDur(S.audio.currentTime);
|
||||
|
||||
Reference in new issue
Block a user