diff --git a/src/player.js b/src/player.js index 4121130..73f7d1b 100644 --- a/src/player.js +++ b/src/player.js @@ -5,10 +5,41 @@ import { api, streamUrl, coverUrl, downloadUrl } from './api.js'; import { loadRecommendations } from './views.js'; import { updateLyricsHighlight, onNpSongChange } from './nowplaying.js'; +// ─── PRELOAD (keeps network alive through Doze mode gap between tracks) ─── +let _preAudio = null; +let _preUrl = ''; +let _preId = ''; + +function _nextIdx() { + if(S.repeat === 'one') return S.qIdx; + if(S.shuffle) return -1; + if(S.qIdx + 1 < S.queue.length) return S.qIdx + 1; + if(S.repeat === 'all') return 0; + return -1; +} + +function _preloadNext() { + 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); // generate URL once — reused by main element + if(!_preAudio) { _preAudio = new Audio(); _preAudio.preload = 'auto'; } + _preAudio.volume = 0; + _preAudio.src = _preUrl; // Chrome starts buffering immediately +} + +function _clearPreload() { + _preId = ''; _preUrl = ''; + if(_preAudio) { _preAudio.src = ''; } +} + // ─── PLAYBACK ─── export function playQueue(songs, startIdx) { S.queue = songs.map(s => ({...s})); S.qIdx = startIdx; + _clearPreload(); playCurrent(); renderQueue(); if(S.radioMode) fillRadioQueue(); @@ -21,8 +52,11 @@ export function playCurrent() { updatePlayerUI(s); // update metadata/notification BEFORE src change onNpSongChange(s.id); renderQueue(); - S.audio.src = streamUrl(s.id); - S.audio.play().catch(() => {}); + // Reuse preloaded URL so Chrome can serve from its in-memory buffer + const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id); + _clearPreload(); + S.audio.src = src; + S.audio.play().catch(() => { S._trackChanging = false; }); } export function updatePlayerUI(s) { @@ -128,11 +162,13 @@ export function setPlayIcon(playing) { } export function prevSong() { + _clearPreload(); if(S.audio.currentTime > 3) { S.audio.currentTime = 0; return; } if(S.qIdx > 0) { S.qIdx--; playCurrent(); } } export function nextSong() { + _clearPreload(); if(S.shuffle) S.qIdx = Math.floor(Math.random() * S.queue.length); else if(S.qIdx < S.queue.length - 1) S.qIdx++; else if(S.repeat) S.qIdx = 0; @@ -200,6 +236,9 @@ let _androidPosTick = 0; export function updateProgress() { if(!S.audio.duration || S._scrubbing) return; syncMediaSessionPosition(); + // Preload next track at 30s remaining so network stays alive through Doze gap + const rem = S.audio.duration - S.audio.currentTime; + if(rem > 0 && rem < 30 && !S._trackChanging) _preloadNext(); // Update Android native position every ~5 seconds to keep lock screen scrubber in sync const now = Date.now(); if(now - _androidPosTick > 5000) {