Repeat Modes (Canonical Behavior)
Supersedes: REPEAT_MODE_FIX.md and REPEAT_ONE_SKIP_FIX.md (kept for historical context).
Modes: none, all, one.
- Next/Previous with
one: restart current track at 0:00 - End-of-track with
one: restart current track at 0:00 - With
all: advance and loop at end - With
none: stop at end of queue
Implementation references:
nextTrack: () =>
update((s) => {
if (!s.currentTrack) return s;
const currentIndex = s.queue.findIndex(
(t) => t.id === s.currentTrack.id,
);
if (s.repeatMode === "one") {
// In repeat-one, "next" means restart current track
return { ...s, currentTime: 0, isPlaying: true };
}
if (s.isShuffled) {
const randomIndex = Math.floor(Math.random() * s.queue.length);
return { ...s, currentTrack: s.queue[randomIndex], isPlaying: true };
}
const nextIndex = currentIndex + 1;
if (nextIndex >= s.queue.length) {
if (s.repeatMode === "all") {
return { ...s, currentTrack: s.queue[0], isPlaying: true };
} else {
return { ...s, isPlaying: false }; // End of queue
}
} else {
return { ...s, currentTrack: s.queue[nextIndex], isPlaying: true };
}
}), prevTrack: () =>
update((s) => {
if (!s.currentTrack || s.queue.length === 0) return s;
if (s.repeatMode === "one") {
// In repeat-one, "previous" means restart current track
return { ...s, currentTime: 0, isPlaying: true };
}
const currentIndex = s.queue.findIndex(
(t) => t.id === s.currentTrack.id,
);
const prevIndex = (currentIndex - 1 + s.queue.length) % s.queue.length;
return { ...s, currentTrack: s.queue[prevIndex], isPlaying: true };
}),User-facing logic aligns with standard player UX (Spotify, Apple Music, etc.).