From cdc367a972a22d1a8926645a7907922b43c5781e Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Sat, 3 Oct 2026 18:28:58 +0800 Subject: [PATCH] Restore the playing playlist when returning to Now Playing --- frontend/app.js | 60 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 672153d..a003b1f 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -7126,6 +7126,7 @@ const SectionRail = (() => { const el = elOf(sec); if (!el) return; flashLabel(btn); + if (sec.id === 'player' || sec.id === 'meta') restoreNowPlayingList(); if (st.active === sec.id && sec.fold) { // Already here: this is the second tap — fold or unfold the card. setFolded(sec.id, !isFolded(sec.id)); @@ -8048,6 +8049,27 @@ function playQueue(index = 0) { playFromList(data.queue.slice(), index, 'queue'); } +// Browsing replaces the shared list pane independently of the playing queue. +// Rebuild it whenever the player is revealed, including when home-over was +// already false: merely removing that class leaves Search's hidden pane stale. +function restoreNowPlayingList({ remember = true } = {}) { + if (!current?.meta) return; + if (remember && !history.state?.ytpNowPlaying) { + history.replaceState({ ...history.state, ytpBrowseView: view, ytpHomeOver: els.playerPane.classList.contains('home-over') }, '', location.href); + history.pushState({ ...history.state, ytpNowPlaying: true, ytpSettingsSection: null }, '', location.href); + } + setHomeOverPlayer(false); + els.playerPane.classList.remove('empty'); + els.placeholder.classList.add('hidden'); + const playlistId = queueSource.startsWith('playlist:') ? queueSource.slice(9) : null; + view = playlistId && data.playlists.some(p => p.id === playlistId) + ? { type: 'playlist', id: playlistId } + : { type: 'nowPlaying' }; + _lastViewKey = [view.type, view.id, view.smartType].join('|'); + render(); + renderUpNext(); +} + function renderUpNext() { updateServiceUpNext(); Remote.changed(); @@ -8664,6 +8686,7 @@ function updateHomeGridScrollState() { } function currentList() { + if (view.type === 'nowPlaying') return queue; if (view.type === 'search') return searchResults; if (view.type === 'history') return data.history; if (view.type === 'queue') return data.queue; @@ -8758,7 +8781,7 @@ function renderList() { if (view.type === 'library') { renderLibrary(); return; } // Filter bar — show for filterable views - const filterableViews = ['history', 'playlist', 'queue', 'channel', 'smart']; + const filterableViews = ['history', 'playlist', 'queue', 'channel', 'smart', 'nowPlaying']; const showFilter = filterableViews.includes(view.type); $('listFilterBar').classList.toggle('hidden', !showFilter); $('batchBar').classList.toggle('hidden', !selectMode); @@ -8782,7 +8805,9 @@ function renderList() { Recommendations.inList(); } - if (view.type === 'search') { + if (view.type === 'nowPlaying') { + els.listTitle.textContent = 'Now playing'; + } else if (view.type === 'search') { els.listTitle.textContent = 'Search results'; } else if (view.type === 'history') { els.listTitle.textContent = 'History'; @@ -10096,7 +10121,7 @@ function renderCard(v, index, list) { card.addEventListener('click', (e) => { if (e.target.closest('.card-menu') || e.target.closest('.card-del') || e.target.closest('.card-note-btn') || e.target.closest('.card-channel.link')) return; if (selectMode) { toggleSelectCard(v.id); return; } - playFromList(list, index, view.type === 'playlist' ? 'playlist:' + view.id : view.type); + playFromList(list, index, view.type === 'playlist' ? 'playlist:' + view.id : view.type === 'nowPlaying' ? queueSource : view.type); }); card.querySelector('.card-menu').addEventListener('click', (e) => { e.stopPropagation(); @@ -10175,8 +10200,28 @@ function markPlayingCard() { }); } +// Only entries created by returning to the player participate here; leave +// unrelated history (including Settings section navigation) to its owner. +window.addEventListener('popstate', event => { + const state = event.state; + if (state?.ytpSettingsSection) return; + if (state?.ytpNowPlaying) { + restoreNowPlayingList({ remember: false }); + if (isStackedPlayerLayout()) document.querySelector('.body')?.scrollTo({ top: 0 }); + } + else if (state?.ytpBrowseView) { + view = state.ytpBrowseView; + setHomeOverPlayer(!!state.ytpHomeOver); + render(); + } +}); + let _lastViewKey = null; function render() { + const key = [view.type, view.id, view.smartType].join('|'); + if (history.state?.ytpNowPlaying && _lastViewKey !== null && key !== _lastViewKey) { + history.pushState({ ...history.state, ytpNowPlaying: false, ytpBrowseView: view, ytpHomeOver: els.playerPane.classList.contains('home-over'), ytpSettingsSection: null }, '', location.href); + } listFilter = ''; const fi = $('listFilterInput'); if (fi) fi.value = ''; @@ -10845,6 +10890,7 @@ function applyPortraitPwaClass() { // In portrait mode, .body is the single scroll container (player pane on top, // list pane below). Scrolling it to 0 brings the player fully into view. function scrollPlayerIntoViewPortrait() { + restoreNowPlayingList(); if (!isStackedPlayerLayout()) return; const body = document.querySelector('.body'); if (body) { @@ -11484,14 +11530,16 @@ document.querySelectorAll('.chip').forEach((c) => { $('updateIconBtn').addEventListener('click', updateFromIcon); $('chBadge').addEventListener('click', (e) => { e.stopPropagation(); Notes.openChapterPicker(); }); $('miniBar').addEventListener('click', () => { - setHomeOverPlayer(false); hideMiniBar(); // Scroll the player into view if needed. In portrait PWA, scroll only the // .body pane (the designated scroll container) — scrollIntoView() also // scrolls overflow:hidden ancestors up to on iOS, leaving the // document offset and fixed-element hit testing broken. if (isStackedPlayerLayout()) scrollPlayerIntoViewPortrait(); - else els.playerPane.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + else { + restoreNowPlayingList(); + els.playerPane.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } }); // Opens the set-list view for whatever is already playing. No playlist arg, // so enterServiceMode leaves the live queue and position untouched. @@ -11500,7 +11548,7 @@ document.querySelectorAll('.chip').forEach((c) => { enterServiceMode(); }); // On the home screen the mini bar is the way back, so ✕ returns to the player. - $('miniCloseBtn').addEventListener('click', (e) => { e.stopPropagation(); setHomeOverPlayer(false); hideMiniBar(); }); + $('miniCloseBtn').addEventListener('click', (e) => { e.stopPropagation(); restoreNowPlayingList(); hideMiniBar(); }); // Drag-to-reorder: prevent default on cards container els.cards.addEventListener('dragover', (e) => {