diff --git a/src/player.js b/src/player.js
index 86f1689..28192ab 100644
--- a/src/player.js
+++ b/src/player.js
@@ -10,9 +10,10 @@ import { updateLyricsHighlight, onNpSongChange } from './nowplaying.js';
const HAS_BRIDGE = typeof window.NSBridge !== 'undefined';
// Track native playback state; driven by window.NSPlayer callbacks below.
-let _nativePos = 0; // ms
-let _nativePlaying = false;
-let _lastDurationMs = 0; // ms — set by onPositionUpdate, used for seek calculations
+let _nativePos = 0; // ms
+let _nativePlaying = false;
+let _lastPositionMs = 0; // ms — set by onPositionUpdate, used for prev() decision
+let _lastDurationMs = 0; // ms — set by onPositionUpdate, used for seek calculations
// Unified duration / position / seek helpers — use native state when available,
// fall back to HTMLAudioElement when not.
@@ -109,23 +110,24 @@ window.NSPlayer = {
// Native moved to a different track (auto-advance, seek, or queue replace).
onTrackChanged(payloadJson) {
try {
- const payload = JSON.parse(payloadJson);
- if (typeof payload.index === 'number' && payload.index !== S.qIdx) {
- S.qIdx = payload.index;
- const s = S.queue[S.qIdx];
- if (!s) return;
- updatePlayerUI(s, true);
- onNpSongChange(s.id);
- renderQueue();
- // Radio mode: top up the queue when running low; push new songs to native.
- if (S.radioMode && S.queue.length - S.qIdx < 5) {
- const prevLen = S.queue.length;
- fillRadioQueue().then(() => {
- S.queue.slice(prevLen).forEach(t =>
- _nsBridge('addToQueue', JSON.stringify(_trackToNative(t)), -1)
- );
- });
- }
+ const p = JSON.parse(payloadJson);
+ if (typeof p.index !== 'number') return;
+ if (p.index < 0 || p.index >= S.queue.length) return;
+ const indexChanged = (p.index !== S.qIdx);
+ S.qIdx = p.index;
+ const s = S.queue[S.qIdx];
+ if (!s) return;
+ updatePlayerUI(s, true);
+ onNpSongChange(s.id);
+ if (indexChanged) renderQueue();
+ // Radio mode: top up the queue when running low; push new songs to native.
+ if (S.radioMode && S.queue.length - S.qIdx < 5) {
+ const prevLen = S.queue.length;
+ fillRadioQueue().then(() => {
+ S.queue.slice(prevLen).forEach(t =>
+ _nsBridge('addToQueue', JSON.stringify(_trackToNative(t)), -1)
+ );
+ });
}
} catch (e) {}
},
@@ -133,6 +135,7 @@ window.NSPlayer = {
// ~250 ms position tick while playing.
onPositionUpdate(posMs, durMs) {
_nativePos = posMs;
+ _lastPositionMs = posMs;
_lastDurationMs = durMs;
if (S._scrubbing) return;
const dur = durMs / 1000;
@@ -238,8 +241,12 @@ export function playQueue(songs, startIdx) {
_clearPreload();
if (HAS_BRIDGE) {
- window.NSBridge.setQueue(JSON.stringify(buildTrackList()), startIdx, true);
+ const s = S.queue[S.qIdx];
+ if (s) { updatePlayerUI(s, true); onNpSongChange(s.id); }
renderQueue();
+ try {
+ window.NSBridge.setQueue(JSON.stringify(buildTrackList()), startIdx, true);
+ } catch (e) { console.warn('NSBridge.setQueue failed', e); }
if (S.radioMode) fillRadioQueue();
return;
}
@@ -335,7 +342,7 @@ export function updatePlayerUI(s, _skipMediaSession = false) {
const art = document.getElementById('player-art');
const cv = coverUrl(s.coverArt, 256);
const existingImg = art.querySelector('img');
- if (cv && existingImg) existingImg.src = cv;
+ if (cv && existingImg) { if (existingImg.src !== cv) existingImg.src = cv; }
else art.innerHTML = cv ? `` : '