feat: prev-in-shuffle retraces actual play order via history stack

Added a play history stack (_playHist) that records S.qIdx before each
forward advance (manual next, auto-advance via onTrackChanged). prevSong
now pops from this stack instead of blindly doing S.qIdx--, so pressing
back during shuffle always returns to the song that was actually playing
before, regardless of where shuffle jumped. History clears on new queue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-28 01:10:06 +02:00
1 parent 2d303083a0
commit 050aff6dbe
1 file changed
+20 -4
+20 -4
View File
@@ -199,6 +199,7 @@ window.NSPlayer = {
const trackId = p.trackId || '';
if (trackId && S.queue[idx]?.id && String(trackId) !== String(S.queue[idx].id) && p.reason !== 'queue') return;
const indexChanged = (idx !== S.qIdx);
if (indexChanged && p.reason !== 'queue') _histPush(S.qIdx);
S.qIdx = idx;
const s = S.queue[S.qIdx];
if (!s) return;
@@ -314,6 +315,17 @@ function _clearPreload() {
if(_preLink) { _preLink.remove(); _preLink = null; }
}
// ─── PLAY HISTORY (for prev-in-shuffle) ──────────────────────────────────────
// Records the qIdx before each forward advance so prevSong() can retrace the
// actual play order instead of blindly decrementing (which is wrong in shuffle).
const _playHist = [];
function _histPush(idx) {
if (_playHist[_playHist.length - 1] === idx) return;
_playHist.push(idx);
if (_playHist.length > 50) _playHist.shift();
}
function _histClear() { _playHist.length = 0; }
// ─── PLAYBACK ─────────────────────────────────────────────────────────────
let _playGen = 0;
// Incremented on every playQueue() call so in-flight fillRadioQueue callbacks
@@ -324,6 +336,7 @@ export function playQueue(songs, startIdx) {
_queueGen++;
_radioFilling = false; // cancel any in-flight radio fill for the old queue
S._origQueue = null;
_histClear();
if (!HAS_BRIDGE && S.shuffle && songs.length > 1) {
const start = songs[startIdx];
const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5);
@@ -647,11 +660,13 @@ export function prevSong() {
_nsBridge('seekTo', 0);
return;
}
if (S.qIdx > 0) {
S.qIdx--;
// Pop history to retrace actual play order (correct in shuffle where next jumps randomly).
// Fall back to S.qIdx-1 when history is empty (e.g. at the very start of a queue).
const prevIdx = _playHist.length > 0 ? _playHist.pop() : (S.qIdx > 0 ? S.qIdx - 1 : -1);
if (prevIdx >= 0 && prevIdx < S.queue.length) {
S.qIdx = prevIdx;
const s = S.queue[S.qIdx];
if (s) { _resetProgressUI(); updatePlayerUI(s, true); onNpSongChange(s.id); renderQueue(); }
// Explicit index so native doesn't have to agree on what "previous" means.
if (s) { _resetProgressUI(); updatePlayerUI(s, true); syncNowPlaying(); onNpSongChange(s.id); renderQueue(); }
_nsBridge('seekToIndex', S.qIdx, 0);
_nsBridge('play');
}
@@ -667,6 +682,7 @@ export function nextSong() {
_setSkipping();
const ni = _computeNextIdx();
if (ni >= 0 && ni < S.queue.length) {
_histPush(S.qIdx);
S.qIdx = ni;
const s = S.queue[S.qIdx];
if (s) { _resetProgressUI(); updatePlayerUI(s, true); onNpSongChange(s.id); renderQueue(); }