From 9d81cdde31b47731b8e7d71701601fee62510882 Mon Sep 17 00:00:00 2001 From: Honey Date: Thu, 28 May 2026 00:28:06 +0200 Subject: [PATCH] feat: dynamic NP cover color, taller settings modal, fix Android song sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Now Playing: add "Dynamische Hintergrundfarbe" toggle in Theme settings; when on, np-bg uses higher saturation/brightness so the cover color really shows through the blurred background - Settings modal: extend to bottom nav on mobile (was leaving ~74px dead space for the player bar which the overlay already covers at z-index 200) - Android: fix onTrackChanged always staying on the first song — the snapshot from MediaController can lag behind ExoPlayer on auto-advance, causing the stale currentIndex to overwrite the correct p.index; now only advance to a higher live index (for rapid-skip catch-up) and use p.trackId directly instead of the potentially-stale snapshot id for the queue-mismatch guard Co-Authored-By: Claude Sonnet 4.6 --- index.html | 7 +++++++ src/main.js | 3 ++- src/player.js | 23 ++++++++++++++--------- src/ui.js | 11 +++++++---- styles.css | 6 +++++- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index 8eeb9bf..b80583f 100644 --- a/index.html +++ b/index.html @@ -663,6 +663,13 @@ Theme
+
+
+ Dynamische Hintergrundfarbe +
Jetzt-Läuft-Hintergrund nach Cover-Hauptfarbe
+
+
+
diff --git a/src/main.js b/src/main.js index 58515d4..57da774 100644 --- a/src/main.js +++ b/src/main.js @@ -9,7 +9,7 @@ import { initProgressScrub, updateProgress, updatePreviewProgress, stopPreview, toggleRadioMode, starCurrent, downloadCurrent, renderQueue, playPreview, toggleStar, dlSongLocal, _applyRadioBtn, _applyShuffleRepeat, _restoreLastPlayed, setPlayIcon, - npToggleStar, updateMediaSession, + npToggleStar, updateMediaSession, updateNpColorMode, } from './player.js'; import { navigate, _navGoBack, _navLoad, onHeaderSearch, closeHeaderSearch, @@ -125,6 +125,7 @@ window.addEventListener('load', () => { S.cfg = JSON.parse(saved); if(!S.cfg.backendUrl && window.NS_BACKEND_URL?.trim()) S.cfg.backendUrl = window.NS_BACKEND_URL.trim().replace(/\/$/,''); startApp(); + updateNpColorMode(); } if(window.NS_SERVER_URL?.trim()) { const el=document.getElementById('srv-url'); if(el&&!el.value) el.value=window.NS_SERVER_URL.trim(); } diff --git a/src/player.js b/src/player.js index 7b501eb..7e20340 100644 --- a/src/player.js +++ b/src/player.js @@ -184,20 +184,20 @@ window.NSPlayer = { try { const p = JSON.parse(payloadJson); if (typeof p.index !== 'number') return; - // Reconcile with the live native state so stale out-of-order callbacks - // (e.g. from rapid skips) don't overwrite the already-correct index. + // p.index comes directly from ExoPlayer (always accurate). + // Only advance to a higher live index to handle rapid forward skips where + // the callback can arrive before we processed the previous one. + // Never let a stale MediaController snapshot pull us BACK to an old index. let idx = p.index; - let liveId = ''; try { const live = JSON.parse(window.NSBridge.getState() || '{}'); - if (typeof live.currentIndex === 'number') idx = live.currentIndex; - liveId = live.currentId || ''; + if (typeof live.currentIndex === 'number' && live.currentIndex > idx) idx = live.currentIndex; } catch {} if (idx < 0 || idx >= S.queue.length) return; - // Reject stale callbacks from the old queue: if the song ID at this index - // doesn't match what native is actually playing, the queues are mismatched. - // Allow through if reason='queue' (we triggered a setQueue) or if no IDs to compare. - if (liveId && S.queue[idx]?.id && String(liveId) !== String(S.queue[idx].id) && p.reason !== 'queue') return; + // Verify using the trackId from the callback payload (ExoPlayer-direct, not the + // potentially-stale snapshot) so auto-advance is never rejected by a lagged snapshot. + const trackId = p.trackId || ''; + if (trackId && S.queue[idx]?.id && String(trackId) !== String(S.queue[idx].id) && p.reason !== 'queue') return; const indexChanged = (idx !== S.qIdx); S.qIdx = idx; const s = S.queue[S.qIdx]; @@ -484,6 +484,11 @@ export function updatePlayerUI(s, _skipMediaSession = false) { if (_npCv && _npCv !== _npBgLastUrl) { const _pre = new Image(); _pre.src = _npCv; } } +// Apply/remove dynamic-color mode based on current setting. +export function updateNpColorMode() { + document.getElementById('np-overlay')?.classList.toggle('dynamic-color', !!S.cfg?.coverColorBg); +} + let _npBgCurr = 'a'; let _npBgLastUrl = ''; function _crossfadeNpBg(cv) { diff --git a/src/ui.js b/src/ui.js index e430ad0..265df4d 100644 --- a/src/ui.js +++ b/src/ui.js @@ -3,7 +3,7 @@ import { esc, formatDur, toast } from './utils.js'; import { api, coverUrl, ndAuth, fetchRomajiIndex, romajiConvert, romajiLookup, invalidateRomajiIndex, invalidateCoverCache } from './api.js'; import { THEMES, applyTheme, getCurrentThemeId } from './themes.js'; // Circular imports — safe (only used inside function bodies) -import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt, refreshDisplayMode } from './player.js'; +import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt, refreshDisplayMode, updateNpColorMode } from './player.js'; import { navigate, loadPlaylists } from './views.js'; // ─── SIDEBAR ─── @@ -49,6 +49,7 @@ export function openSettings() { document.getElementById('s-backend').value = S.cfg.backendUrl || ''; document.getElementById('s-show-playcount').classList.toggle('on', !!S.cfg.showPlayCount); document.getElementById('s-show-romaji').classList.toggle('on', !!S.cfg.showRomaji); + document.getElementById('s-cover-color-bg').classList.toggle('on', !!S.cfg.coverColorBg); _renderThemePicker(); document.getElementById('settings-modal').classList.add('open'); checkBackend(); @@ -83,9 +84,11 @@ export function saveSettings() { if(credChanged) invalidateCoverCache(); S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim(); S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,''); - S.cfg.showPlayCount = document.getElementById('s-show-playcount').classList.contains('on'); - const romajiChanged = S.cfg.showRomaji !== document.getElementById('s-show-romaji').classList.contains('on'); - S.cfg.showRomaji = document.getElementById('s-show-romaji').classList.contains('on'); + S.cfg.showPlayCount = document.getElementById('s-show-playcount').classList.contains('on'); + const romajiChanged = S.cfg.showRomaji !== document.getElementById('s-show-romaji').classList.contains('on'); + S.cfg.showRomaji = document.getElementById('s-show-romaji').classList.contains('on'); + S.cfg.coverColorBg = document.getElementById('s-cover-color-bg').classList.contains('on'); + updateNpColorMode(); localStorage.setItem('ns_cfg', JSON.stringify(S.cfg)); closeSettings(); toast('✅ Gespeichert'); if (romajiChanged) { diff --git a/styles.css b/styles.css index f83fa62..f807cc1 100644 --- a/styles.css +++ b/styles.css @@ -563,7 +563,7 @@ button:focus{outline:none} .toast-container{bottom:calc(var(--player-h) + 80px + 16px)} .jobs-panel{bottom:calc(var(--player-h) + 80px + 16px)} .bnav-item{padding:6px 2px;box-sizing:border-box} - #settings-modal{align-items:flex-start;padding-top:max(env(safe-area-inset-top),16px);padding-bottom:calc(var(--player-h) + 80px + env(safe-area-inset-bottom) + 8px)} + #settings-modal{align-items:flex-start;padding-top:max(env(safe-area-inset-top),16px);padding-bottom:calc(80px + env(safe-area-inset-bottom))} #settings-modal .modal{max-height:100%!important;overflow:hidden} } @@ -590,6 +590,10 @@ button:focus{outline:none} will-change:opacity; z-index:0; } +/* Dynamic cover color mode: more vivid/saturated background */ +.np-overlay.dynamic-color .np-bg{ + filter:blur(60px) brightness(.5) saturate(3); +} .np-inner{ position:relative;z-index:1;