diff --git a/android/app/src/main/kotlin/com/novasonic/app/MusicService.kt b/android/app/src/main/kotlin/com/novasonic/app/MusicService.kt index 9869c99..069e59e 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/MusicService.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/MusicService.kt @@ -155,6 +155,10 @@ class MusicService : MediaSessionService() { private inner class PlayerListener : Player.Listener { + // Debounce JS-initiated transitions (seek / queue-replace) so rapid skips + // produce one onTrackChanged notification with the final settled state. + private var _pendingTrackChanged: Runnable? = null + override fun onIsPlayingChanged(isPlaying: Boolean) { val state = player.playbackState stateListener?.onPlaybackStateChanged( @@ -184,14 +188,31 @@ class MusicService : MediaSessionService() { Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED -> "queue" else -> "seek" } - stateListener?.onTrackChanged( - player.currentMediaItemIndex, - mediaItem?.mediaId ?: "", - reasonStr - ) - // Pre-warm artwork for current + next tracks so notification - // never shows a blank cover on auto-advance. + // Always warm artwork immediately so the notification never shows a blank cover. warmArtworkCache() + + // Cancel any pending debounced dispatch from a previous transition. + _pendingTrackChanged?.let { handler.removeCallbacks(it) } + _pendingTrackChanged = null + + val dispatch = Runnable { + _pendingTrackChanged = null + stateListener?.onTrackChanged( + player.currentMediaItemIndex, + player.currentMediaItem?.mediaId ?: "", + reasonStr + ) + } + + if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) { + // Native auto-advance: JS didn't initiate this, notify immediately. + dispatch.run() + } else { + // JS-initiated (seek or setQueue): debounce 120 ms so rapid skips + // coalesce into one callback with the final settled index. + _pendingTrackChanged = dispatch + handler.postDelayed(dispatch, 120) + } } override fun onTimelineChanged(timeline: Timeline, reason: Int) { diff --git a/src/player.js b/src/player.js index 74f5e0d..00c066a 100644 --- a/src/player.js +++ b/src/player.js @@ -143,9 +143,16 @@ window.NSPlayer = { try { 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; + // Reconcile with the live native state so stale out-of-order callbacks + // (e.g. from rapid skips) don't overwrite the already-correct index. + let idx = p.index; + try { + const live = JSON.parse(window.NSBridge.getState() || '{}'); + if (typeof live.currentIndex === 'number') idx = live.currentIndex; + } catch {} + if (idx < 0 || idx >= S.queue.length) return; + const indexChanged = (idx !== S.qIdx); + S.qIdx = idx; const s = S.queue[S.qIdx]; if (!s) return; updatePlayerUI(s, true);