diff --git a/src/player.js b/src/player.js index 5702f63..ae6e7d7 100644 --- a/src/player.js +++ b/src/player.js @@ -243,9 +243,11 @@ window.NSPlayer = { setPlayIcon(false); if (S.radioMode) { const prevLen = S.queue.length; - // fillRadioQueue sends addToQueue to native internally — only trigger play here. fillRadioQueue().then(() => { - if (S.queue.length > prevLen) _nsBridge('play'); + if (S.queue.length > prevLen) { _nsBridge('play'); return; } + // fillRadioQueue was locked by a concurrent fill — wait for it to finish + // then trigger play so native doesn't stay stopped after queue end. + if (_radioFilling) setTimeout(() => { if (S.queue.length > prevLen) _nsBridge('play'); }, 3000); }); } }, @@ -320,6 +322,7 @@ let _queueGen = 0; export function playQueue(songs, startIdx) { _queueGen++; + _radioFilling = false; // cancel any in-flight radio fill for the old queue S.queue = songs.map(s => ({...s})); S.qIdx = startIdx; _clearPreload(); @@ -675,8 +678,11 @@ export async function toggleRadioMode() { if(S.radioMode) await fillRadioQueue(); } +let _radioFilling = false; + export async function fillRadioQueue() { - if(!S.radioMode) return; + if (!S.radioMode || _radioFilling) return; + _radioFilling = true; const gen = _queueGen; try { const seed = S.queue[S.qIdx]; @@ -697,21 +703,30 @@ export async function fillRadioQueue() { if (gen !== _queueGen) return; if (!newSongs.length) return; - // Deduplicate only against the last 50 queued songs so the radio never - // stalls on a small library. Songs cycle back after ~50 tracks rather than - // blocking forever once they've been seen once. const recentIds = new Set(S.queue.slice(-50).map(s => s.id)); - const deduped = newSongs.filter(s => !recentIds.has(s.id)); - // If every candidate was a recent duplicate (very small library), allow - // repeats rather than letting the queue run dry and music stop. - newSongs = deduped.length > 0 ? deduped : newSongs.slice(0, 5); + const deduped = newSongs.filter(s => !recentIds.has(s.id)); + if (deduped.length === 0) { + // All similar songs recently played (small library) — try random as final fallback + if (gen !== _queueGen) return; + const rr = await api('getRandomSongs', '&size=20'); + const randomFresh = (rr.randomSongs?.song || []).filter(s => !recentIds.has(s.id)); + // If random is also exhausted, allow repeats to keep music playing + newSongs = randomFresh.length > 0 ? randomFresh : newSongs.slice(0, 5); + } else { + newSongs = deduped; + } + + if (gen !== _queueGen) return; const prevLen = S.queue.length; S.queue.push(...newSongs); renderQueue(); // Append-only: never call setQueue here (that resets position to 0). S.queue.slice(prevLen).forEach(s => _nsBridge('addToQueue', JSON.stringify(_trackToNative(s)), -1)); - } catch {} + } catch { + } finally { + _radioFilling = false; + } } // ─── PROGRESS & SCRUBBING ─────────────────────────────────────────────────