From 87ee8cd0977873865ea3ad0721f4c3e34f9d5a05 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 16 Aug 2026 21:47:58 +0200 Subject: [PATCH] fix(android player): seek broke after seamless track transitions (durationMs stuck at 0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After skipping through already-buffered tracks the player stays READY, so onPlaybackStateChanged never fires and syncState() isn't re-called; ctrl.duration was TIME_UNSET(→0) at the transition, leaving state.durationMs stuck at 0. The seek bar then computed fraction*0 = 0 and snapped playback to the start every time (or to a stale value → "only 10s"). Restarting the app re-read the real duration, which is why it recovered. Fix: durationMs now self-heals — the 500ms ticker refreshes it too, and resolveDurationMs() falls back to the current song's known duration while ctrl.duration is still unset. Also record the Erkunden swipe-between-tabs idea. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + .../novasonic/app/player/PlayerController.kt | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d78c9..28b57e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -453,6 +453,7 @@ Primary causes identified: - **New-release alerts:** periodic check for new albums/singles by followed artists (sources: iTunes lookup / MusicBrainz / Last.fm), surfaced in the existing in-app notification bell (and optionally push). - **Concert alerts:** reuse the existing **Ticketmaster** events integration to notify when a followed artist has nearby/upcoming shows. - Ties into the importer: a "download this new release" action straight from the notification. +- **Android** — **Swipe between Erkunden sub-tabs** — a left/right swipe gesture on the Erkunden tab to switch between "Bibliothek" and "Global" search (instead of only tapping the chips). A fun, natural pager-style gesture (e.g. HorizontalPager). - **Android** — Playlist drag-and-drop / sort order - **Android** — Artist page iTunes fallback (for artists not in Navidrome) - **Android** — Romaji / Japanese sort support diff --git a/android/app/src/main/kotlin/com/novasonic/app/player/PlayerController.kt b/android/app/src/main/kotlin/com/novasonic/app/player/PlayerController.kt index 6f1e5af..161c78a 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/player/PlayerController.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/player/PlayerController.kt @@ -227,11 +227,25 @@ class PlayerController(private val context: Context, private val repo: MusicRepo while (isActive) { delay(500) val ctrl = controller ?: break - _state.update { it.copy(positionMs = ctrl.currentPosition) } + // Refresh durationMs too — after a SEAMLESS track transition (skipping through + // already-buffered tracks) the player stays READY, so onPlaybackStateChanged never + // fires and syncState() isn't re-called; ctrl.duration was TIME_UNSET(→0) at the + // transition, leaving durationMs stuck at 0. Seek then computes fraction*0 = 0 and + // snaps to the start every time. Re-reading here lets it self-heal within 500ms. + _state.update { it.copy(positionMs = ctrl.currentPosition, durationMs = resolveDurationMs(ctrl)) } } } } + /** Player duration, falling back to the current song's known duration while ctrl.duration is + * still TIME_UNSET (right after a media transition). Prevents a 0-duration seek → snap-to-0. */ + private fun resolveDurationMs(ctrl: MediaController): Long { + val d = ctrl.duration + if (d > 0) return d + val songDurSecs = queue.getOrNull(ctrl.currentMediaItemIndex)?.duration?.toLong() ?: 0L + return songDurSecs * 1000L + } + private fun stopTicker() { tickerJob?.cancel() tickerJob = null @@ -271,7 +285,7 @@ class PlayerController(private val context: Context, private val repo: MusicRepo currentSong = queue.getOrNull(idx), currentIndex = idx, positionMs = ctrl.currentPosition, - durationMs = ctrl.duration.coerceAtLeast(0), + durationMs = resolveDurationMs(ctrl), queue = queue, repeatMode = ctrl.repeatMode, shuffled = isManuallyShuffled,