fix: player bar freezes on manual song start in bridge mode
- playQueue: update UI immediately before NSBridge.setQueue so the bar reflects the new song without waiting for onTrackChanged - onTrackChanged: remove index guard that blocked UI refresh when S.qIdx was already set; always call updatePlayerUI/onNpSongChange - nextSong/prevSong: optimistic UI update before calling native next/prev - _computeNextIdx: extracted helper for shuffle/repeat/linear logic - _lastPositionMs: new variable for prev() 3s threshold in bridge mode - updatePlayerUI/_syncNpDirect: guard img.src assignment to prevent redundant reloads and flicker on double update Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf2921c56f
commit
7084fc4118
1 file changed
+49
-11
+49
-11
@@ -12,6 +12,7 @@ const HAS_BRIDGE = typeof window.NSBridge !== 'undefined';
|
|||||||
// Track native playback state; driven by window.NSPlayer callbacks below.
|
// Track native playback state; driven by window.NSPlayer callbacks below.
|
||||||
let _nativePos = 0; // ms
|
let _nativePos = 0; // ms
|
||||||
let _nativePlaying = false;
|
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
|
let _lastDurationMs = 0; // ms — set by onPositionUpdate, used for seek calculations
|
||||||
|
|
||||||
// Unified duration / position / seek helpers — use native state when available,
|
// Unified duration / position / seek helpers — use native state when available,
|
||||||
@@ -109,14 +110,16 @@ window.NSPlayer = {
|
|||||||
// Native moved to a different track (auto-advance, seek, or queue replace).
|
// Native moved to a different track (auto-advance, seek, or queue replace).
|
||||||
onTrackChanged(payloadJson) {
|
onTrackChanged(payloadJson) {
|
||||||
try {
|
try {
|
||||||
const payload = JSON.parse(payloadJson);
|
const p = JSON.parse(payloadJson);
|
||||||
if (typeof payload.index === 'number' && payload.index !== S.qIdx) {
|
if (typeof p.index !== 'number') return;
|
||||||
S.qIdx = payload.index;
|
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];
|
const s = S.queue[S.qIdx];
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
updatePlayerUI(s, true);
|
updatePlayerUI(s, true);
|
||||||
onNpSongChange(s.id);
|
onNpSongChange(s.id);
|
||||||
renderQueue();
|
if (indexChanged) renderQueue();
|
||||||
// Radio mode: top up the queue when running low; push new songs to native.
|
// Radio mode: top up the queue when running low; push new songs to native.
|
||||||
if (S.radioMode && S.queue.length - S.qIdx < 5) {
|
if (S.radioMode && S.queue.length - S.qIdx < 5) {
|
||||||
const prevLen = S.queue.length;
|
const prevLen = S.queue.length;
|
||||||
@@ -126,13 +129,13 @@ window.NSPlayer = {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ~250 ms position tick while playing.
|
// ~250 ms position tick while playing.
|
||||||
onPositionUpdate(posMs, durMs) {
|
onPositionUpdate(posMs, durMs) {
|
||||||
_nativePos = posMs;
|
_nativePos = posMs;
|
||||||
|
_lastPositionMs = posMs;
|
||||||
_lastDurationMs = durMs;
|
_lastDurationMs = durMs;
|
||||||
if (S._scrubbing) return;
|
if (S._scrubbing) return;
|
||||||
const dur = durMs / 1000;
|
const dur = durMs / 1000;
|
||||||
@@ -238,8 +241,12 @@ export function playQueue(songs, startIdx) {
|
|||||||
_clearPreload();
|
_clearPreload();
|
||||||
|
|
||||||
if (HAS_BRIDGE) {
|
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();
|
renderQueue();
|
||||||
|
try {
|
||||||
|
window.NSBridge.setQueue(JSON.stringify(buildTrackList()), startIdx, true);
|
||||||
|
} catch (e) { console.warn('NSBridge.setQueue failed', e); }
|
||||||
if (S.radioMode) fillRadioQueue();
|
if (S.radioMode) fillRadioQueue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -335,7 +342,7 @@ export function updatePlayerUI(s, _skipMediaSession = false) {
|
|||||||
const art = document.getElementById('player-art');
|
const art = document.getElementById('player-art');
|
||||||
const cv = coverUrl(s.coverArt, 256);
|
const cv = coverUrl(s.coverArt, 256);
|
||||||
const existingImg = art.querySelector('img');
|
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 ? `<img src="${cv}" style="width:100%;height:100%;object-fit:cover">` : '<div class="no-art">♪</div>';
|
else art.innerHTML = cv ? `<img src="${cv}" style="width:100%;height:100%;object-fit:cover">` : '<div class="no-art">♪</div>';
|
||||||
updateStarBtn();
|
updateStarBtn();
|
||||||
document.title = `${s.title} — NovaSonic`;
|
document.title = `${s.title} — NovaSonic`;
|
||||||
@@ -384,8 +391,8 @@ export function _syncNpDirect(s) {
|
|||||||
const cover = document.getElementById('np-cover');
|
const cover = document.getElementById('np-cover');
|
||||||
if (cover) {
|
if (cover) {
|
||||||
const existingImg = cover.querySelector('img');
|
const existingImg = cover.querySelector('img');
|
||||||
if (cv && existingImg) existingImg.src = cv;
|
if (cv && existingImg) { if (existingImg.src !== cv) existingImg.src = cv; }
|
||||||
else if (cover) cover.innerHTML = cv ? `<img src="${cv}" style="width:100%;height:100%;object-fit:cover">` : '<div class="no-art">♪</div>';
|
else cover.innerHTML = cv ? `<img src="${cv}" style="width:100%;height:100%;object-fit:cover">` : '<div class="no-art">♪</div>';
|
||||||
}
|
}
|
||||||
_crossfadeNpBg(cv);
|
_crossfadeNpBg(cv);
|
||||||
const t = document.getElementById('np-title'); if(t) t.textContent = s.title || '—';
|
const t = document.getElementById('np-title'); if(t) t.textContent = s.title || '—';
|
||||||
@@ -424,16 +431,47 @@ export function setPlayIcon(playing) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _computeNextIdx() {
|
||||||
|
if (S.repeat === 'one') return S.qIdx;
|
||||||
|
if (S.shuffle && S.queue.length > 1) {
|
||||||
|
let n = S.qIdx;
|
||||||
|
while (n === S.qIdx) n = Math.floor(Math.random() * S.queue.length);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
if (S.qIdx + 1 < S.queue.length) return S.qIdx + 1;
|
||||||
|
if (S.repeat === 'all') return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
export function prevSong() {
|
export function prevSong() {
|
||||||
_clearPreload();
|
_clearPreload();
|
||||||
if (HAS_BRIDGE) { _nsBridge('prev'); return; }
|
if (HAS_BRIDGE) {
|
||||||
|
if (_lastPositionMs > 3000) { _nsBridge('prev'); return; }
|
||||||
|
if (S.qIdx > 0) {
|
||||||
|
S.qIdx--;
|
||||||
|
const s = S.queue[S.qIdx];
|
||||||
|
if (s) { updatePlayerUI(s, true); onNpSongChange(s.id); renderQueue(); }
|
||||||
|
}
|
||||||
|
_nsBridge('prev');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(S.audio.currentTime > 3) { S.audio.currentTime = 0; return; }
|
if(S.audio.currentTime > 3) { S.audio.currentTime = 0; return; }
|
||||||
if(S.qIdx > 0) { S.qIdx--; playCurrent(); }
|
if(S.qIdx > 0) { S.qIdx--; playCurrent(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nextSong() {
|
export function nextSong() {
|
||||||
_clearPreload();
|
_clearPreload();
|
||||||
if (HAS_BRIDGE) { _nsBridge('next'); return; }
|
if (HAS_BRIDGE) {
|
||||||
|
const ni = _computeNextIdx();
|
||||||
|
if (ni >= 0 && ni < S.queue.length) {
|
||||||
|
S.qIdx = ni;
|
||||||
|
const s = S.queue[S.qIdx];
|
||||||
|
if (s) { updatePlayerUI(s, true); onNpSongChange(s.id); renderQueue(); }
|
||||||
|
}
|
||||||
|
_nsBridge('next');
|
||||||
|
if (S.radioMode && S.queue.length - S.qIdx < 5) fillRadioQueue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(S.shuffle) S.qIdx = Math.floor(Math.random() * S.queue.length);
|
if(S.shuffle) S.qIdx = Math.floor(Math.random() * S.queue.length);
|
||||||
else if(S.qIdx < S.queue.length - 1) S.qIdx++;
|
else if(S.qIdx < S.queue.length - 1) S.qIdx++;
|
||||||
else if(S.repeat) S.qIdx = 0;
|
else if(S.repeat) S.qIdx = 0;
|
||||||
|
|||||||
Reference in new issue
Block a user