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,
|
quality: 'auto', volume: 1, audioOnly: false, autoPreload: true,
|
||||||
repeatMode: 'off', // 'off' | 'all' — repeat the playing list when it ends
|
repeatMode: 'off', // 'off' | 'all' — repeat the playing list when it ends
|
||||||
loopOne: false, // repeat the single current video
|
loopOne: false, // repeat the single current video
|
||||||
|
shuffle: false, // shuffle upcoming tracks when playing a list
|
||||||
theme: 'dark', // 'dark' | 'light' | 'contrast'
|
theme: 'dark', // 'dark' | 'light' | 'contrast'
|
||||||
fontScale: 'normal', // 'small' | 'normal' | 'large' | 'xl'
|
fontScale: 'normal', // 'small' | 'normal' | 'large' | 'xl'
|
||||||
density: 'comfortable', // 'comfortable' | 'compact'
|
density: 'comfortable', // 'comfortable' | 'compact'
|
||||||
@@ -243,6 +244,7 @@ const els = {
|
|||||||
speed: $('speedSelect'),
|
speed: $('speedSelect'),
|
||||||
quality: $('qualitySelect'),
|
quality: $('qualitySelect'),
|
||||||
fsBtn: $('fsBtn'),
|
fsBtn: $('fsBtn'),
|
||||||
|
shuffleBtn: $('shuffleBtn'),
|
||||||
loopBtn: $('loopBtn'),
|
loopBtn: $('loopBtn'),
|
||||||
repeatBtn: $('repeatBtn'),
|
repeatBtn: $('repeatBtn'),
|
||||||
queueBtn: $('queueBtn'),
|
queueBtn: $('queueBtn'),
|
||||||
@@ -999,10 +1001,46 @@ function playFromList(list, index, source = '') {
|
|||||||
queue = list;
|
queue = list;
|
||||||
queueIndex = index;
|
queueIndex = index;
|
||||||
queueSource = source;
|
queueSource = source;
|
||||||
Player.loadVideo(list[index]);
|
unshuffledQueue = null;
|
||||||
|
if (data.settings.shuffle) applyShuffle();
|
||||||
|
Player.loadVideo(queue[queueIndex]);
|
||||||
renderUpNext();
|
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 ----------
|
// ---------- Temporary queue ----------
|
||||||
function updateQueueBadge() {
|
function updateQueueBadge() {
|
||||||
const count = data.queue.length;
|
const count = data.queue.length;
|
||||||
@@ -1115,6 +1153,7 @@ function toggleRepeat() {
|
|||||||
function updateLoopRepeatButtons() {
|
function updateLoopRepeatButtons() {
|
||||||
if (els.loopBtn) els.loopBtn.classList.toggle('active', !!data.settings.loopOne);
|
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.repeatBtn) els.repeatBtn.classList.toggle('active', data.settings.repeatMode === 'all');
|
||||||
|
if (els.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle);
|
||||||
}
|
}
|
||||||
function playPrev() {
|
function playPrev() {
|
||||||
if (Player.master.currentTime > 3) { Player.seek(0); return; }
|
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.playBtn.addEventListener('click', () => Player.toggle());
|
||||||
els.nextBtn.addEventListener('click', playNext);
|
els.nextBtn.addEventListener('click', playNext);
|
||||||
els.prevBtn.addEventListener('click', playPrev);
|
els.prevBtn.addEventListener('click', playPrev);
|
||||||
|
els.shuffleBtn.addEventListener('click', toggleShuffle);
|
||||||
els.loopBtn.addEventListener('click', toggleLoopOne);
|
els.loopBtn.addEventListener('click', toggleLoopOne);
|
||||||
els.repeatBtn.addEventListener('click', toggleRepeat);
|
els.repeatBtn.addEventListener('click', toggleRepeat);
|
||||||
els.fsBtn.addEventListener('click', () => {
|
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 === 'm') els.muteBtn.click();
|
||||||
else if (e.key === 'l') toggleLoopOne();
|
else if (e.key === 'l') toggleLoopOne();
|
||||||
else if (e.key === 'r') toggleRepeat();
|
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 === 'q') { if (current && current.meta) addToQueue(current.meta); }
|
||||||
else if (e.key === 'a') { if (current) setAbA(); }
|
else if (e.key === 'a') { if (current) setAbA(); }
|
||||||
else if (e.key === 'b') { if (current) setAbB(); }
|
else if (e.key === 'b') { if (current) setAbB(); }
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button id="sleepTimerBtn" class="ctrl" title="Sleep timer">⏱</button>
|
<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="loopBtn" class="ctrl" title="Loop current video (L)">🔂</button>
|
||||||
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)">🔁</button>
|
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)">🔁</button>
|
||||||
<button id="fsBtn" class="ctrl" title="Fullscreen">⛶</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>↑</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>F</kbd><span>Toggle fullscreen</span></div>
|
||||||
<div class="shortcut-row"><kbd>M</kbd><span>Toggle mute</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>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>R</kbd><span>Repeat list when finished</span></div>
|
||||||
<div class="shortcut-row"><kbd>Q</kbd><span>Add current video to queue</span></div>
|
<div class="shortcut-row"><kbd>Q</kbd><span>Add current video to queue</span></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user