fix: cover art on auto-advance + native back-button overlay routing

Issue 1 (MusicService): wrap DefaultHttpDataSource in CacheBitmapLoader so
Media3 notification artwork is fetched once and served from memory on every
subsequent auto-advance. warmArtworkCache() pre-loads current+next 3 tracks
on both onMediaItemTransition and onTimelineChanged (queue replace via setQueue).
Adds explicit media3-datasource dependency.

Issue 2 (back button): replace deprecated onKeyDown with OnBackPressedCallback.
WebBridge.setOverlayState(bool) tracks whether JS has an overlay open;
consumeBackPress() is called first — if true, MainActivity delegates to
window.NSPlayer.handleBack() which closes the topmost layer. Falls back to
webView.goBack() then moveTaskToBack(true) so the MusicService binding survives.
JS side: _syncOverlayState() + MutationObserver keep the native flag current;
handleBack covers NP overlay, queue panel, action sheet, all modals, sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-09 11:11:09 +02:00
1 parent 76ecfe2268
commit cf2921c56f
7 files changed
+178 -10

No files matched your search

+3 -1
View File
@@ -2,7 +2,7 @@ import { S } from './state.js';
import { esc } from './utils.js';
import { coverUrl } from './api.js';
// Circular imports — safe (only used inside function bodies)
import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar } from './player.js';
import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar, _syncOverlayState } from './player.js';
import { navigate } from './views.js';
import { openPlModal, showSongInfo } from './ui.js';
@@ -13,6 +13,7 @@ export function openNowPlaying() {
overlay.classList.add('open');
if(S.npQueueMode) switchNpTab('player');
syncNowPlaying();
_syncOverlayState();
if(!overlay._swipeAttached) {
overlay._swipeAttached = true;
let startY=0, startT=0, dragging=false;
@@ -42,6 +43,7 @@ export function closeNowPlaying() {
const overlay = document.getElementById('np-overlay');
overlay.style.transform = '';
overlay.classList.remove('open');
_syncOverlayState();
}
export function syncNowPlaying() {
+78
View File
@@ -710,6 +710,7 @@ export function toggleQueue() {
S.queueOpen = !S.queueOpen;
document.getElementById('queue-panel').classList.toggle('open', S.queueOpen);
if(S.queueOpen) renderQueue();
_syncOverlayState();
}
// ─── DOWNLOAD ─────────────────────────────────────────────────────────────
@@ -767,6 +768,83 @@ export function updatePreviewProgress() {
// ─── RESTORE LAST PLAYED ──────────────────────────────────────────────────
// ─── BACK-BUTTON / OVERLAY STATE SYNC ────────────────────────────────────
// Native back button: when JS has an overlay open, Android delegates the event
// to window.NSPlayer.handleBack() instead of minimising the app.
function _hasAnyOverlayOpen() {
if (S.npOpen || S.queueOpen) return true;
if (document.getElementById('np-sheet')?.classList.contains('open')) return true;
if (document.getElementById('ctx-menu')?.style.display === 'block') return true;
if (document.getElementById('sidebar')?.classList.contains('mobile-open')) return true;
return ['settings-modal','pl-modal','info-modal','dl-modal','edit-pl-modal','confirm-modal','notif-panel']
.some(id => document.getElementById(id)?.classList.contains('open'));
}
export function _syncOverlayState() {
if (!HAS_BRIDGE) return;
try { window.NSBridge.setOverlayState(_hasAnyOverlayOpen()); } catch {}
}
window.NSPlayer.handleBack = function() {
// Close overlays from topmost layer downward.
if (document.getElementById('np-sheet')?.classList.contains('open')) {
window.closeNpMenu?.(); _syncOverlayState(); return;
}
const ctxMenu = document.getElementById('ctx-menu');
if (ctxMenu?.style.display === 'block') {
ctxMenu.style.display = 'none'; ctxMenu.innerHTML = '';
_syncOverlayState(); return;
}
if (S.npOpen) {
window.closeNowPlaying?.(); _syncOverlayState(); return;
}
if (S.queueOpen) {
toggleQueue(); _syncOverlayState(); return;
}
// Generic modals
const modalMap = {
'settings-modal': 'closeSettings',
'pl-modal': 'closePlModal',
'info-modal': 'closeInfoModal',
'dl-modal': 'closeDlModal',
'edit-pl-modal': 'closeEditPlModal',
'confirm-modal': '_confirmCancel',
};
for (const [id, fn] of Object.entries(modalMap)) {
const el = document.getElementById(id);
if (el?.classList.contains('open')) {
if (window[fn]) window[fn](); else el.classList.remove('open');
_syncOverlayState(); return;
}
}
// Notif panel (generic fallback)
const notifPanel = document.getElementById('notif-panel');
if (notifPanel?.classList.contains('open')) {
notifPanel.classList.remove('open'); _syncOverlayState(); return;
}
// Sidebar
if (document.getElementById('sidebar')?.classList.contains('mobile-open')) {
window.closeSidebar?.(); _syncOverlayState(); return;
}
};
// MutationObserver keeps hasOpenOverlay in sync whenever the DOM changes class
// attributes (e.g. any overlay gains/loses the 'open' class).
if (HAS_BRIDGE) {
const _startOverlayObserver = () => {
if (!document.body) return;
new MutationObserver(_syncOverlayState).observe(document.body, {
attributes: true, attributeFilter: ['class'], subtree: true,
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', _startOverlayObserver);
} else {
_startOverlayObserver();
}
}
export function _restoreLastPlayed() {
try {
const saved = JSON.parse(localStorage.getItem('ns_last_played') || 'null');