fix: eliminate NP overlay stale-song on autoplay and cover-load delay

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>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-28 01:30:55 +02:00
1 parent 050aff6dbe
commit 0788c801fa
3 files changed
+45 -1

No files matched your search

+25
View File
@@ -16,6 +16,7 @@ let _lastPositionMs = 0; // ms — set by onPositionUpdate, used for prev() de
let _lastDurationMs = 0; // ms — set by onPositionUpdate, used for seek calculations
let _skipping = false; // true briefly after a skip — suppresses the transient isPlaying:false
let _skipTimer = null;
let _reconTick = 0; // counter for periodic track-reconciliation inside onPositionUpdate
// ─── SCROBBLE ─────────────────────────────────────────────────────────────
let _scrobbleTimer = null;
@@ -221,6 +222,24 @@ window.NSPlayer = {
_lastPositionMs = posMs;
_lastDurationMs = durMs;
if (S._scrubbing) return;
// Every 8 ticks (~2 s): verify JS track matches native snapshot.
// Catches the gap between auto-advance and onTrackChanged arriving in JS
// (evaluateJavascript is deferred while WebView is backgrounded/resuming).
if (HAS_BRIDGE && ++_reconTick >= 8) {
_reconTick = 0;
try {
const snap = JSON.parse(window.NSBridge.getState() || '{}');
const snapId = String(snap.currentId || '');
if (snapId && snapId !== String(S.queue[S.qIdx]?.id || '')) {
const found = S.queue.findIndex(s => String(s.id) === snapId);
if (found >= 0) {
S.qIdx = found;
const s = S.queue[S.qIdx];
if (s) { updatePlayerUI(s, true); syncNowPlaying(); onNpSongChange(s.id); renderQueue(); }
}
}
} catch {}
}
const dur = durMs / 1000;
const cur = posMs / 1000;
if (dur <= 0) return;
@@ -496,6 +515,12 @@ export function updatePlayerUI(s, _skipMediaSession = false) {
// Preload the hi-res NP cover now so opening the overlay is instant
const _npCv = coverUrl(s.coverArt, 1024);
if (_npCv && _npCv !== _npBgLastUrl) { const _pre = new Image(); _pre.src = _npCv; }
// Pre-warm the next song's 1024 cover so auto-advance NP swap is instant from cache
const _nextForPreload = S.qIdx + 1 < S.queue.length ? S.queue[S.qIdx + 1] : null;
if (_nextForPreload?.coverArt) {
const _nextCv = coverUrl(_nextForPreload.coverArt, 1024);
if (_nextCv) { const _preN = new Image(); _preN.src = _nextCv; }
}
}
// ── Solid cover-color background ────────────────────────────────────────────