fix: Spotify-style shuffle — fixed queue order, not random-each-skip
- playQueue: unified shuffle for both web and Android — pre-shuffles the songs array with the tapped song pinned first, saves _origQueue for restore. Passes S.qIdx (0 when shuffled) to NSBridge.setQueue instead of startIdx. - _computeNextIdx: removed the random-pick shuffle branch entirely. Since the queue is already shuffled, sequential traversal IS the shuffle order. - toggleShuffle: removed !HAS_BRIDGE guard so queue reorder runs on Android too. Re-sends reordered queue to ExoPlayer so native order stays in sync with JS. Removed setShuffleMode call (we manage order ourselves via pre-shuffled array). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f1a5cd3a6
commit
0552b3f5d0
1 file changed
+12
-14
+12
-14
@@ -359,12 +359,9 @@ export function playQueue(songs, startIdx) {
|
||||
_radioFilling = false; // cancel any in-flight radio fill for the old queue
|
||||
S._origQueue = null;
|
||||
_histClear();
|
||||
// On Android with shuffle on, start at a random position so we don't always
|
||||
// begin at index 0 (the first album/playlist track).
|
||||
if (HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
||||
startIdx = Math.floor(Math.random() * songs.length);
|
||||
}
|
||||
if (!HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
||||
// Spotify-style shuffle: build a fixed shuffled order once, pin the tapped song first.
|
||||
// Applies to both web and Android — queue is traversed sequentially in this fixed order.
|
||||
if (S.shuffle && songs.length > 1) {
|
||||
const start = songs[startIdx];
|
||||
const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5);
|
||||
S._origQueue = songs.map(s => ({...s}));
|
||||
@@ -387,7 +384,7 @@ export function playQueue(songs, startIdx) {
|
||||
// @JavascriptInterface call (which blocks the JS thread while Java
|
||||
// parses the JSON and creates MediaItems).
|
||||
requestAnimationFrame(() => {
|
||||
try { window.NSBridge.setQueue(trackListJson, startIdx, true); }
|
||||
try { window.NSBridge.setQueue(trackListJson, S.qIdx, true); }
|
||||
catch (e) { console.warn('NSBridge.setQueue failed', e); }
|
||||
// fillRadioQueue AFTER setQueue so addToQueue lands in the new native queue,
|
||||
// not the old one that setQueue is about to wipe.
|
||||
@@ -655,11 +652,7 @@ export function setPlayIcon(playing) {
|
||||
|
||||
function _computeNextIdx() {
|
||||
if (S.repeat === 'one') return S.qIdx;
|
||||
if (S.shuffle && S.queue.length > 1) {
|
||||
let n = S.qIdx;
|
||||
while (n === S.qIdx) n = Math.floor(Math.random() * S.queue.length);
|
||||
return n;
|
||||
}
|
||||
// No special shuffle case — queue is already in shuffled order when S.shuffle is on.
|
||||
if (S.qIdx + 1 < S.queue.length) return S.qIdx + 1;
|
||||
if (S.repeat === 'all') return 0;
|
||||
return -1;
|
||||
@@ -759,7 +752,7 @@ export function _applyShuffleRepeat() {
|
||||
export function toggleShuffle() {
|
||||
S.shuffle = !S.shuffle;
|
||||
localStorage.setItem('ns_shuffle', S.shuffle ? '1' : '0');
|
||||
if (!HAS_BRIDGE && S.queue.length > 1) {
|
||||
if (S.queue.length > 1) {
|
||||
if (S.shuffle) {
|
||||
const cur = S.queue[S.qIdx];
|
||||
S._origQueue = [...S.queue];
|
||||
@@ -774,9 +767,14 @@ export function toggleShuffle() {
|
||||
S.qIdx = idx >= 0 ? idx : 0;
|
||||
}
|
||||
renderQueue();
|
||||
// Re-send the reordered queue to native so ExoPlayer's order matches JS.
|
||||
if (HAS_BRIDGE) {
|
||||
const tl = JSON.stringify(buildTrackList());
|
||||
try { window.NSBridge.setQueue(tl, S.qIdx, _nativePlaying); }
|
||||
catch(e) { console.warn('NSBridge.setQueue failed', e); }
|
||||
}
|
||||
}
|
||||
_applyShuffleRepeat();
|
||||
if (HAS_BRIDGE) _nsBridge('setShuffleMode', S.shuffle);
|
||||
}
|
||||
|
||||
export function toggleRepeat() {
|
||||
|
||||
Reference in new issue
Block a user