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
+25 -10
+25 -10
View File
@@ -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);
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 ─────────────────────────────────────────────────