Add up-next queue panel
Shows upcoming videos in the autoplay queue below now-playing meta. Clickable to jump to any video. Hidden when queue has 1 item or empty. Updates on playFromList, playNext, playPrev. Hidden in empty player state. Files: - frontend/index.html: upnext section in player pane - frontend/app.js: renderUpNext(), wired into playFromList/playNext/playPrev - frontend/styles.css: .upnext, .upnext-item, .upnext-list styles
This commit is contained in:
@@ -24,7 +24,7 @@ bottom as they come up.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
- [x] Drag-to-reorder videos within a playlist.
|
- [x] Drag-to-reorder videos within a playlist.
|
||||||
- [ ] "Up next" queue panel showing the autoplay queue.
|
- [x] "Up next" queue panel showing the autoplay queue.
|
||||||
- [ ] Persistent mini now-playing bar when browsing other views.
|
- [ ] Persistent mini now-playing bar when browsing other views.
|
||||||
- [ ] Cache size cap in Settings (auto-evict oldest past a limit).
|
- [ ] Cache size cap in Settings (auto-evict oldest past a limit).
|
||||||
- [ ] Export / import playlists as JSON.
|
- [ ] Export / import playlists as JSON.
|
||||||
|
|||||||
@@ -578,13 +578,40 @@ function playFromList(list, index) {
|
|||||||
queue = list;
|
queue = list;
|
||||||
queueIndex = index;
|
queueIndex = index;
|
||||||
Player.loadVideo(list[index]);
|
Player.loadVideo(list[index]);
|
||||||
|
renderUpNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpNext() {
|
||||||
|
const upcoming = queue.slice(queueIndex + 1);
|
||||||
|
if (!upcoming.length) { $('upnext').classList.add('hidden'); return; }
|
||||||
|
$('upnext').classList.remove('hidden');
|
||||||
|
$('upnextCount').textContent = String(upcoming.length);
|
||||||
|
const list = $('upnextList');
|
||||||
|
list.innerHTML = '';
|
||||||
|
upcoming.forEach((v) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'upnext-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<img src="${v.thumbnail || ''}" alt="" loading="lazy" />
|
||||||
|
<div class="upnext-info">
|
||||||
|
<div class="upnext-title">${v.title}</div>
|
||||||
|
<div class="upnext-channel">${v.channel || ''}</div>
|
||||||
|
</div>`;
|
||||||
|
item.addEventListener('click', () => {
|
||||||
|
const idx = queue.findIndex((x) => x.id === v.id);
|
||||||
|
if (idx >= 0) { queueIndex = idx - 1; playNext(); }
|
||||||
|
});
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function playNext() {
|
function playNext() {
|
||||||
if (queueIndex >= 0 && queueIndex < queue.length - 1) {
|
if (queueIndex >= 0 && queueIndex < queue.length - 1) {
|
||||||
queueIndex++;
|
queueIndex++;
|
||||||
Player.loadVideo(queue[queueIndex]);
|
Player.loadVideo(queue[queueIndex]);
|
||||||
|
renderUpNext();
|
||||||
} else {
|
} else {
|
||||||
updatePlayBtn();
|
updatePlayBtn();
|
||||||
|
$('upnext').classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function playPrev() {
|
function playPrev() {
|
||||||
@@ -592,6 +619,7 @@ function playPrev() {
|
|||||||
if (queueIndex > 0) {
|
if (queueIndex > 0) {
|
||||||
queueIndex--;
|
queueIndex--;
|
||||||
Player.loadVideo(queue[queueIndex]);
|
Player.loadVideo(queue[queueIndex]);
|
||||||
|
renderUpNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,15 @@
|
|||||||
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
|
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Up next queue -->
|
||||||
|
<div id="upnext" class="upnext hidden">
|
||||||
|
<div class="upnext-header">
|
||||||
|
<span>Up next</span>
|
||||||
|
<span id="upnextCount" class="upnext-count">0</span>
|
||||||
|
</div>
|
||||||
|
<div id="upnextList" class="upnext-list"></div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- List pane -->
|
<!-- List pane -->
|
||||||
|
|||||||
@@ -517,7 +517,62 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.player-pane.empty .controls,
|
.player-pane.empty .controls,
|
||||||
.player-pane.empty .now-meta { display: none; }
|
.player-pane.empty .now-meta,
|
||||||
|
.player-pane.empty .upnext { display: none; }
|
||||||
|
|
||||||
|
/* ===================== Up next queue ===================== */
|
||||||
|
.upnext {
|
||||||
|
margin-top: 16px;
|
||||||
|
background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
.upnext-header {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px; font-weight: 700;
|
||||||
|
text-transform: uppercase; letter-spacing: 0.16em;
|
||||||
|
color: var(--text-dim);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.upnext-count {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--accent);
|
||||||
|
background: rgba(255,75,50,0.12);
|
||||||
|
padding: 0 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
line-height: 17px;
|
||||||
|
}
|
||||||
|
.upnext-list {
|
||||||
|
display: flex; flex-direction: column; gap: 6px;
|
||||||
|
max-height: 200px; overflow-y: auto;
|
||||||
|
}
|
||||||
|
.upnext-item {
|
||||||
|
display: flex; gap: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 9px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.16s;
|
||||||
|
}
|
||||||
|
.upnext-item:hover { background: var(--bg-3); }
|
||||||
|
.upnext-item img {
|
||||||
|
width: 80px; aspect-ratio: 16/9;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #000;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.upnext-info { min-width: 0; display: flex; flex-direction: column; justify-content: center; }
|
||||||
|
.upnext-title {
|
||||||
|
font-size: 12.5px; font-weight: 600;
|
||||||
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||||
|
}
|
||||||
|
.upnext-channel {
|
||||||
|
font-size: 11px; color: var(--text-dim);
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===================== List pane ===================== */
|
/* ===================== List pane ===================== */
|
||||||
.list-pane {
|
.list-pane {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user