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
|
_radioFilling = false; // cancel any in-flight radio fill for the old queue
|
||||||
S._origQueue = null;
|
S._origQueue = null;
|
||||||
_histClear();
|
_histClear();
|
||||||
// On Android with shuffle on, start at a random position so we don't always
|
// Spotify-style shuffle: build a fixed shuffled order once, pin the tapped song first.
|
||||||
// begin at index 0 (the first album/playlist track).
|
// Applies to both web and Android — queue is traversed sequentially in this fixed order.
|
||||||
if (HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
if (S.shuffle && songs.length > 1) {
|
||||||
startIdx = Math.floor(Math.random() * songs.length);
|
|
||||||
}
|
|
||||||
if (!HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
|
||||||
const start = songs[startIdx];
|
const start = songs[startIdx];
|
||||||
const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5);
|
const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5);
|
||||||
S._origQueue = songs.map(s => ({...s}));
|
S._origQueue = songs.map(s => ({...s}));
|
||||||
@@ -387,7 +384,7 @@ export function playQueue(songs, startIdx) {
|
|||||||
// @JavascriptInterface call (which blocks the JS thread while Java
|
// @JavascriptInterface call (which blocks the JS thread while Java
|
||||||
// parses the JSON and creates MediaItems).
|
// parses the JSON and creates MediaItems).
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
try { window.NSBridge.setQueue(trackListJson, startIdx, true); }
|
try { window.NSBridge.setQueue(trackListJson, S.qIdx, true); }
|
||||||
catch (e) { console.warn('NSBridge.setQueue failed', e); }
|
catch (e) { console.warn('NSBridge.setQueue failed', e); }
|
||||||
// fillRadioQueue AFTER setQueue so addToQueue lands in the new native queue,
|
// fillRadioQueue AFTER setQueue so addToQueue lands in the new native queue,
|
||||||
// not the old one that setQueue is about to wipe.
|
// not the old one that setQueue is about to wipe.
|
||||||
@@ -655,11 +652,7 @@ export function setPlayIcon(playing) {
|
|||||||
|
|
||||||
function _computeNextIdx() {
|
function _computeNextIdx() {
|
||||||
if (S.repeat === 'one') return S.qIdx;
|
if (S.repeat === 'one') return S.qIdx;
|
||||||
if (S.shuffle && S.queue.length > 1) {
|
// No special shuffle case — queue is already in shuffled order when S.shuffle is on.
|
||||||
let n = S.qIdx;
|
|
||||||
while (n === S.qIdx) n = Math.floor(Math.random() * S.queue.length);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
if (S.qIdx + 1 < S.queue.length) return S.qIdx + 1;
|
if (S.qIdx + 1 < S.queue.length) return S.qIdx + 1;
|
||||||
if (S.repeat === 'all') return 0;
|
if (S.repeat === 'all') return 0;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -759,7 +752,7 @@ export function _applyShuffleRepeat() {
|
|||||||
export function toggleShuffle() {
|
export function toggleShuffle() {
|
||||||
S.shuffle = !S.shuffle;
|
S.shuffle = !S.shuffle;
|
||||||
localStorage.setItem('ns_shuffle', S.shuffle ? '1' : '0');
|
localStorage.setItem('ns_shuffle', S.shuffle ? '1' : '0');
|
||||||
if (!HAS_BRIDGE && S.queue.length > 1) {
|
if (S.queue.length > 1) {
|
||||||
if (S.shuffle) {
|
if (S.shuffle) {
|
||||||
const cur = S.queue[S.qIdx];
|
const cur = S.queue[S.qIdx];
|
||||||
S._origQueue = [...S.queue];
|
S._origQueue = [...S.queue];
|
||||||
@@ -774,9 +767,14 @@ export function toggleShuffle() {
|
|||||||
S.qIdx = idx >= 0 ? idx : 0;
|
S.qIdx = idx >= 0 ? idx : 0;
|
||||||
}
|
}
|
||||||
renderQueue();
|
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();
|
_applyShuffleRepeat();
|
||||||
if (HAS_BRIDGE) _nsBridge('setShuffleMode', S.shuffle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleRepeat() {
|
export function toggleRepeat() {
|
||||||
|
|||||||
Reference in new issue
Block a user