fix: radio fill lock prevents concurrent fills; random fallback when similar songs exhausted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-21 13:15:55 +02:00
1 parent b9a5c6468f
commit 89aeeb7421
1 file changed
+26 -11
+26 -11
View File
@@ -243,9 +243,11 @@ window.NSPlayer = {
setPlayIcon(false); setPlayIcon(false);
if (S.radioMode) { if (S.radioMode) {
const prevLen = S.queue.length; const prevLen = S.queue.length;
// fillRadioQueue sends addToQueue to native internally — only trigger play here.
fillRadioQueue().then(() => { 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) { export function playQueue(songs, startIdx) {
_queueGen++; _queueGen++;
_radioFilling = false; // cancel any in-flight radio fill for the old queue
S.queue = songs.map(s => ({...s})); S.queue = songs.map(s => ({...s}));
S.qIdx = startIdx; S.qIdx = startIdx;
_clearPreload(); _clearPreload();
@@ -675,8 +678,11 @@ export async function toggleRadioMode() {
if(S.radioMode) await fillRadioQueue(); if(S.radioMode) await fillRadioQueue();
} }
let _radioFilling = false;
export async function fillRadioQueue() { export async function fillRadioQueue() {
if(!S.radioMode) return; if (!S.radioMode || _radioFilling) return;
_radioFilling = true;
const gen = _queueGen; const gen = _queueGen;
try { try {
const seed = S.queue[S.qIdx]; const seed = S.queue[S.qIdx];
@@ -697,21 +703,30 @@ export async function fillRadioQueue() {
if (gen !== _queueGen) return; if (gen !== _queueGen) return;
if (!newSongs.length) 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 recentIds = new Set(S.queue.slice(-50).map(s => s.id));
const deduped = newSongs.filter(s => !recentIds.has(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);
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; const prevLen = S.queue.length;
S.queue.push(...newSongs); S.queue.push(...newSongs);
renderQueue(); renderQueue();
// Append-only: never call setQueue here (that resets position to 0). // Append-only: never call setQueue here (that resets position to 0).
S.queue.slice(prevLen).forEach(s => _nsBridge('addToQueue', JSON.stringify(_trackToNative(s)), -1)); S.queue.slice(prevLen).forEach(s => _nsBridge('addToQueue', JSON.stringify(_trackToNative(s)), -1));
} catch {} } catch {
} finally {
_radioFilling = false;
}
} }
// ─── PROGRESS & SCRUBBING ───────────────────────────────────────────────── // ─── PROGRESS & SCRUBBING ─────────────────────────────────────────────────