feat: add shuffle toggle for upcoming tracks with S shortcut
This commit is contained in:
@@ -176,6 +176,7 @@ const DEFAULT_SETTINGS = {
|
||||
quality: 'auto', volume: 1, audioOnly: false, autoPreload: true,
|
||||
repeatMode: 'off', // 'off' | 'all' — repeat the playing list when it ends
|
||||
loopOne: false, // repeat the single current video
|
||||
shuffle: false, // shuffle upcoming tracks when playing a list
|
||||
theme: 'dark', // 'dark' | 'light' | 'contrast'
|
||||
fontScale: 'normal', // 'small' | 'normal' | 'large' | 'xl'
|
||||
density: 'comfortable', // 'comfortable' | 'compact'
|
||||
@@ -243,6 +244,7 @@ const els = {
|
||||
speed: $('speedSelect'),
|
||||
quality: $('qualitySelect'),
|
||||
fsBtn: $('fsBtn'),
|
||||
shuffleBtn: $('shuffleBtn'),
|
||||
loopBtn: $('loopBtn'),
|
||||
repeatBtn: $('repeatBtn'),
|
||||
queueBtn: $('queueBtn'),
|
||||
@@ -999,10 +1001,46 @@ function playFromList(list, index, source = '') {
|
||||
queue = list;
|
||||
queueIndex = index;
|
||||
queueSource = source;
|
||||
Player.loadVideo(list[index]);
|
||||
unshuffledQueue = null;
|
||||
if (data.settings.shuffle) applyShuffle();
|
||||
Player.loadVideo(queue[queueIndex]);
|
||||
renderUpNext();
|
||||
}
|
||||
|
||||
// ---------- Shuffle ----------
|
||||
// Shuffles only the UPCOMING part of the live queue (Fisher-Yates); the
|
||||
// already-played prefix and the current track stay where they are. The
|
||||
// pre-shuffle order is kept so turning shuffle off restores it.
|
||||
let unshuffledQueue = null;
|
||||
|
||||
function applyShuffle() {
|
||||
if (queue.length <= queueIndex + 2) return; // nothing meaningful to shuffle
|
||||
if (!unshuffledQueue) unshuffledQueue = queue.slice();
|
||||
const upcoming = queue.slice(queueIndex + 1);
|
||||
for (let i = upcoming.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[upcoming[i], upcoming[j]] = [upcoming[j], upcoming[i]];
|
||||
}
|
||||
queue = queue.slice(0, queueIndex + 1).concat(upcoming);
|
||||
}
|
||||
|
||||
function toggleShuffle() {
|
||||
data.settings.shuffle = !data.settings.shuffle;
|
||||
if (data.settings.shuffle) {
|
||||
applyShuffle();
|
||||
} else if (unshuffledQueue) {
|
||||
const playingId = current && current.meta && current.meta.id;
|
||||
queue = unshuffledQueue;
|
||||
unshuffledQueue = null;
|
||||
const idx = playingId ? queue.findIndex((v) => v.id === playingId) : -1;
|
||||
if (idx >= 0) queueIndex = idx;
|
||||
}
|
||||
persist();
|
||||
updateLoopRepeatButtons();
|
||||
renderUpNext();
|
||||
toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off');
|
||||
}
|
||||
|
||||
// ---------- Temporary queue ----------
|
||||
function updateQueueBadge() {
|
||||
const count = data.queue.length;
|
||||
@@ -1115,6 +1153,7 @@ function toggleRepeat() {
|
||||
function updateLoopRepeatButtons() {
|
||||
if (els.loopBtn) els.loopBtn.classList.toggle('active', !!data.settings.loopOne);
|
||||
if (els.repeatBtn) els.repeatBtn.classList.toggle('active', data.settings.repeatMode === 'all');
|
||||
if (els.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle);
|
||||
}
|
||||
function playPrev() {
|
||||
if (Player.master.currentTime > 3) { Player.seek(0); return; }
|
||||
@@ -2486,6 +2525,7 @@ document.querySelectorAll('.chip').forEach((c) => {
|
||||
els.playBtn.addEventListener('click', () => Player.toggle());
|
||||
els.nextBtn.addEventListener('click', playNext);
|
||||
els.prevBtn.addEventListener('click', playPrev);
|
||||
els.shuffleBtn.addEventListener('click', toggleShuffle);
|
||||
els.loopBtn.addEventListener('click', toggleLoopOne);
|
||||
els.repeatBtn.addEventListener('click', toggleRepeat);
|
||||
els.fsBtn.addEventListener('click', () => {
|
||||
@@ -2590,6 +2630,7 @@ document.querySelectorAll('.chip').forEach((c) => {
|
||||
else if (e.key === 'm') els.muteBtn.click();
|
||||
else if (e.key === 'l') toggleLoopOne();
|
||||
else if (e.key === 'r') toggleRepeat();
|
||||
else if (e.key === 's') toggleShuffle();
|
||||
else if (e.key === 'q') { if (current && current.meta) addToQueue(current.meta); }
|
||||
else if (e.key === 'a') { if (current) setAbA(); }
|
||||
else if (e.key === 'b') { if (current) setAbB(); }
|
||||
|
||||
@@ -160,6 +160,7 @@
|
||||
</label>
|
||||
|
||||
<button id="sleepTimerBtn" class="ctrl" title="Sleep timer">⏱</button>
|
||||
<button id="shuffleBtn" class="ctrl" title="Shuffle upcoming tracks (S)">🔀</button>
|
||||
<button id="loopBtn" class="ctrl" title="Loop current video (L)">🔂</button>
|
||||
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)">🔁</button>
|
||||
<button id="fsBtn" class="ctrl" title="Fullscreen">⛶</button>
|
||||
@@ -293,6 +294,7 @@
|
||||
<div class="shortcut-row"><kbd>↑</kbd><kbd>↓</kbd><span>Volume up / down</span></div>
|
||||
<div class="shortcut-row"><kbd>F</kbd><span>Toggle fullscreen</span></div>
|
||||
<div class="shortcut-row"><kbd>M</kbd><span>Toggle mute</span></div>
|
||||
<div class="shortcut-row"><kbd>S</kbd><span>Shuffle upcoming tracks</span></div>
|
||||
<div class="shortcut-row"><kbd>L</kbd><span>Loop current video</span></div>
|
||||
<div class="shortcut-row"><kbd>R</kbd><span>Repeat list when finished</span></div>
|
||||
<div class="shortcut-row"><kbd>Q</kbd><span>Add current video to queue</span></div>
|
||||
|
||||
Reference in New Issue
Block a user