Merge pull request #60 from XuVIIJay/feat/forward-stack

随机播放模式双向可回溯
This commit is contained in:
TIANYAO ZHANG authored and GitHub committed 2026-05-12 22:23:35 +08:00
commit ecdb2d253e
1 file changed
+47 -24
+47 -24
View File
@@ -22,6 +22,7 @@ export class PlayQueue {
private mode: PlayMode = PlayMode.Sequential; private mode: PlayMode = PlayMode.Sequential;
private playedIndices = new Set<number>(); private playedIndices = new Set<number>();
private history: number[] = []; private history: number[] = [];
private forwardStack: number[] = [];
private static readonly HISTORY_LIMIT = 50; private static readonly HISTORY_LIMIT = 50;
private pushHistory(idx: number): void { private pushHistory(idx: number): void {
@@ -100,12 +101,14 @@ export class PlayQueue {
this.currentIndex = -1; this.currentIndex = -1;
this.playedIndices.clear(); this.playedIndices.clear();
this.history = []; this.history = [];
this.forwardStack = [];
} }
play(): QueuedSong | null { play(): QueuedSong | null {
if (this.songs.length === 0) return null; if (this.songs.length === 0) return null;
this.playedIndices.clear(); this.playedIndices.clear();
this.history = []; this.history = [];
this.forwardStack = [];
this.currentIndex = 0; this.currentIndex = 0;
this.playedIndices.add(0); this.playedIndices.add(0);
return this.songs[0]; return this.songs[0];
@@ -118,6 +121,7 @@ export class PlayQueue {
// shuffle from this point. History tracking is independent and // shuffle from this point. History tracking is independent and
// unaffected by this clear. // unaffected by this clear.
this.playedIndices.clear(); this.playedIndices.clear();
this.forwardStack = [];
this.currentIndex = index; this.currentIndex = index;
this.playedIndices.add(index); this.playedIndices.add(index);
return this.songs[index]; return this.songs[index];
@@ -139,32 +143,45 @@ export class PlayQueue {
this.currentIndex = (this.currentIndex + 1) % this.songs.length; this.currentIndex = (this.currentIndex + 1) % this.songs.length;
return this.songs[this.currentIndex]; return this.songs[this.currentIndex];
} }
case PlayMode.Random: { case PlayMode.Random:
const unplayed: number[] = [];
for (let i = 0; i < this.songs.length; i++) {
if (!this.playedIndices.has(i)) unplayed.push(i);
}
if (unplayed.length === 0) return null;
const nextIndex =
unplayed[Math.floor(Math.random() * unplayed.length)];
this.pushHistory(this.currentIndex);
this.currentIndex = nextIndex;
this.playedIndices.add(nextIndex);
return this.songs[nextIndex];
}
case PlayMode.RandomLoop: { case PlayMode.RandomLoop: {
if (this.songs.length === 1) { // 优先回到前进栈记录的位置(prev 退回的歌)
if (this.forwardStack.length > 0) {
const target = this.forwardStack.pop()!;
if (target !== this.currentIndex) {
this.pushHistory(this.currentIndex);
this.currentIndex = target;
this.playedIndices.add(target);
return this.songs[target];
}
}
// 前进栈为空,走纯随机逻辑
if (this.mode === PlayMode.Random) {
const unplayed: number[] = [];
for (let i = 0; i < this.songs.length; i++) {
if (!this.playedIndices.has(i)) unplayed.push(i);
}
if (unplayed.length === 0) return null;
const nextIndex =
unplayed[Math.floor(Math.random() * unplayed.length)];
this.pushHistory(this.currentIndex); this.pushHistory(this.currentIndex);
this.currentIndex = 0; this.currentIndex = nextIndex;
return this.songs[0]; this.playedIndices.add(nextIndex);
return this.songs[nextIndex];
} else {
if (this.songs.length === 1) {
this.pushHistory(this.currentIndex);
this.currentIndex = 0;
return this.songs[0];
}
let idx: number;
do {
idx = Math.floor(Math.random() * this.songs.length);
} while (idx === this.currentIndex);
this.pushHistory(this.currentIndex);
this.currentIndex = idx;
return this.songs[idx];
} }
let idx: number;
do {
idx = Math.floor(Math.random() * this.songs.length);
} while (idx === this.currentIndex);
this.pushHistory(this.currentIndex);
this.currentIndex = idx;
return this.songs[idx];
} }
} }
} }
@@ -172,13 +189,18 @@ export class PlayQueue {
prev(): QueuedSong | null { prev(): QueuedSong | null {
if (this.songs.length === 0) return null; if (this.songs.length === 0) return null;
// 记录当前位置到前进栈,供 next 优先返回
if (this.currentIndex >= 0 && this.forwardStack.length < PlayQueue.HISTORY_LIMIT) {
this.forwardStack.push(this.currentIndex);
}
// Preferred: pop from the back-stack so prev means "the song I // Preferred: pop from the back-stack so prev means "the song I
// actually played before this one," not "the previous array slot." // actually played before this one," not "the previous array slot."
while (this.history.length > 0) { while (this.history.length > 0) {
const idx = this.history.pop()!; const idx = this.history.pop()!;
if (idx >= 0 && idx < this.songs.length) { if (idx >= 0 && idx < this.songs.length) {
this.currentIndex = idx; this.currentIndex = idx;
this.playedIndices.add(idx); this.playedIndices = new Set([...this.history, this.currentIndex]);
return this.songs[idx]; return this.songs[idx];
} }
// Stale entry (song removed) — keep popping. // Stale entry (song removed) — keep popping.
@@ -227,6 +249,7 @@ export class PlayQueue {
this.mode = mode; this.mode = mode;
this.playedIndices.clear(); this.playedIndices.clear();
this.history = []; this.history = [];
this.forwardStack = [];
if (this.currentIndex >= 0) { if (this.currentIndex >= 0) {
this.playedIndices.add(this.currentIndex); this.playedIndices.add(this.currentIndex);
} }