diff --git a/webui/static/library.js b/webui/static/library.js index baeeb7ec..2eae1a2c 100644 --- a/webui/static/library.js +++ b/webui/static/library.js @@ -4085,6 +4085,13 @@ function _buildTrackRow(track, album, admin) { const queueTd = document.createElement('td'); queueTd.className = 'col-queue'; if (!track._missingExpected && track.file_path) { + const playNextBtn = document.createElement('button'); + playNextBtn.className = 'enhanced-playnext-btn'; + // Play-next glyph (queue-with-arrow feel) + playNextBtn.innerHTML = '⇥'; // ⇥ + playNextBtn.title = 'Play next'; + queueTd.appendChild(playNextBtn); + const queueBtn = document.createElement('button'); queueBtn.className = 'enhanced-queue-btn'; queueBtn.innerHTML = '+'; @@ -4270,8 +4277,10 @@ function _attachTableDelegation(table, album) { } } - // Queue button - if (target.closest('.enhanced-queue-btn')) { + // Queue / Play-next buttons (share the same track payload) + const isQueueBtn = target.closest('.enhanced-queue-btn'); + const isPlayNextBtn = target.closest('.enhanced-playnext-btn'); + if (isQueueBtn || isPlayNextBtn) { e.stopPropagation(); if (track.file_path) { const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : ''; @@ -4279,7 +4288,7 @@ function _attachTableDelegation(table, album) { if (!albumArt && artistDetailPageState.enhancedData) { albumArt = artistDetailPageState.enhancedData.artist?.thumb_url; } - addToQueue({ + const payload = { title: track.title || 'Unknown Track', artist: artistName || 'Unknown Artist', album: album.title || 'Unknown Album', @@ -4292,7 +4301,9 @@ function _attachTableDelegation(table, album) { album_id: album.id, bitrate: track.bitrate, sample_rate: track.sample_rate - }); + }; + if (isPlayNextBtn && typeof playNext === 'function') playNext(payload); + else addToQueue(payload); } return; } diff --git a/webui/static/media-player.js b/webui/static/media-player.js index da97c492..bb3ccc8d 100644 --- a/webui/static/media-player.js +++ b/webui/static/media-player.js @@ -2104,6 +2104,19 @@ function addToQueue(track) { } } +// Insert a track to play right after the current one (Spotify "Play next"). +function playNext(track) { + if (npQueue.length === 0 || npQueueIndex < 0) { + // Nothing queued / playing — same as add-to-queue (which auto-plays). + addToQueue(track); + return; + } + npQueue.splice(npQueueIndex + 1, 0, track); + showToast('Playing next', 'success'); + renderNpQueue(); + updateNpPrevNextButtons(); +} + function removeFromQueue(index) { if (index < 0 || index >= npQueue.length) return; const wasCurrentTrack = (index === npQueueIndex); diff --git a/webui/static/style.css b/webui/static/style.css index 06260e8a..e5d7a6b3 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -48808,26 +48808,29 @@ textarea.enhanced-meta-field-input { width: 36px; text-align: center; } -.enhanced-queue-btn { +.enhanced-queue-btn, +.enhanced-playnext-btn { background: none; - border: 1px solid rgba(29, 185, 84, 0.2); - color: rgba(29, 185, 84, 0.5); + border: 1px solid rgba(var(--accent-rgb), 0.2); + color: rgba(var(--accent-rgb), 0.5); border-radius: 50%; width: 24px; height: 24px; font-size: 14px; font-weight: bold; cursor: pointer; - display: flex; + display: inline-flex; align-items: center; justify-content: center; transition: all 0.15s ease; padding: 0; + margin: 0 2px; } -.enhanced-queue-btn:hover { - background: rgba(29, 185, 84, 0.12); - color: rgba(29, 185, 84, 0.9); - border-color: rgba(29, 185, 84, 0.4); +.enhanced-queue-btn:hover, +.enhanced-playnext-btn:hover { + background: rgba(var(--accent-rgb), 0.12); + color: rgba(var(--accent-rgb), 0.9); + border-color: rgba(var(--accent-rgb), 0.4); transform: scale(1.1); }