Three improvements:
- openNowPlaying(): reads NSBridge.getState() (always-fresh @Volatile snapshot)
via ID search to reconcile S.qIdx before render; fixes NP showing old song
when deferred evaluateJavascript callbacks haven't fired yet after backgrounding
- onPositionUpdate(): periodic check (~every 2s) detects track mismatch via
snapshot ID; catches missed/delayed onTrackChanged when NP is open
- updatePlayerUI(): pre-warms next song's 1024px cover so auto-advance NP
swap is instant from browser cache instead of a 1-second load delay
- styles: add backface-visibility:hidden + isolation:isolate to .np-overlay
to reduce GPU composite flicker on Android WebView on manual skip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes:
1. Remove snapshot read from openNowPlaying() — the MediaController snapshot
can show the old song at the old index (both still valid entries in the
queue), so the ID check does not detect staleness; after the onTrackChanged
fix S.qIdx is always authoritative so there is no need to read the snapshot
2. Call syncNowPlaying() explicitly in onTrackChanged so the NP overlay is
refreshed immediately when the song changes while it is already open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openNowPlaying() was blindly trusting the MediaController snapshot index,
which can lag one track behind ExoPlayer on auto-advance. This overwrote
the already-correct S.qIdx that onTrackChanged had just set, causing the
NP overlay to render the previous song while the mini-player showed the
right one. Now only accept the snapshot index when the song ID at that
position matches — stale snapshots are silently ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- initProgressScrub: pass noTouch=true for the bottom mini-bar so touch
events are never registered on it; only mouse events remain (desktop-only).
The now-playing scrubber is unchanged and keeps full touch support.
- Export seekToMs(ms) from player.js; use it in the lyrics click handler
instead of S.audio.currentTime which has no effect when HAS_BRIDGE
(native ExoPlayer handles playback, not the HTML audio element).
- styles.css: add will-change:transform to .player and .np-overlay to keep
them on GPU compositor layers during the swipe gesture; tweak NP open
transition to cubic-bezier(.4,0,.2,1) for a snappier feel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Android, onTrackChanged fires asynchronously so opening the overlay
quickly after a track change could show the previous song. Query
NSBridge.getState() synchronously at open time to get the real index.
Same flash can also occur on web — openNowPlaying now always renders
from current S.qIdx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
player.js
- Replace `_native()` function with `const HAS_BRIDGE` (evaluated once at
module load so bridge detection is rock-solid)
- Add `buildTrackList()` — maps S.queue to NSBridge Track array with 1024px
artworkUrl so lock-screen cover is crisp on high-DPR displays
- Add `_pushQueueIfBridge()` — called by `fillRadioQueue()` so radio top-ups
are pushed to ExoPlayer in real time (fixes autoplay gap after radio refill)
- `playQueue`: route through `NSBridge.setQueue` when bridge present; old
`S.audio` path retained for browser/PWA fallback
- `playCurrent`: `NSBridge.seekToIndex + play` in bridge mode
- `nextSong / prevSong / togglePlay / onEnded`: delegate to bridge, skip local
S.qIdx manipulation (native sends onTrackChanged callback instead)
- `seekTo / _initBarScrub`: use `_lastDurationMs` for ms-accurate seeks
- `onTrackChanged`: guard with `p.index !== S.qIdx` to avoid redundant redraws
- `updateMediaSession`: artwork sizes 96/300/512 → 256/512/1024
- Player-bar cover: 100 → 256px; NP cover/bg: 400 → 1024px;
queue items: 60 → 128px; preload hint sizes: 96/300/512 → 256/512/1024
main.js
- Wrap watchdog setInterval in `typeof window.NSBridge === 'undefined'` guard;
the foreground service keeps ExoPlayer alive — JS watchdog not needed there
views.js
- Album detail / playlist detail covers: 300 → 512px
- Playlist grid cards: 200 → 512px
- Search result song cards: 200 → 256px
- Artist top-songs / all-songs thumbnails: 60 → 128px
- Partial album art in _renderArtistAlbums: 300 → 512px
components.js
- makeAlbumCard: 200 → 512px
- makeSongItem: 60 → 128px
nowplaying.js
- NP queue items: 60 → 128px
- NP action sheet thumb: 80 → 256px
sw.js
- Bump CACHE to ns-v41 so stale small-cover entries are evicted on next load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nowplaying: add cancellation token to _loadNpLyrics to prevent stale async
calls overwriting newer results (synced→plain race condition)
- nowplaying: replace scrollIntoView with explicit container.scrollTo to prevent
page-level scroll on desktop (topbar shifting / gap at edge)
- backend: remove results[0] fallback in lookupItunesTrackMeta — only use
exact title+artist match to avoid embedding wrong artwork
- components/views: makeSongItem accepts noArtistLink/noAlbumLink opts;
album-detail disables both, playlist-detail disables artist link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Retry LRCLIB without album_name when exact match fails (404) —
album name mismatch was the most common cause of falling back to plain
- Capture plainLyrics from LRCLIB and prefer it over Navidrome plain
(LRCLIB plain text is usually higher quality)
- Use only first artist for LRCLIB lookup (multi-artist strings like
'Artist A / Artist B' cause 404)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>