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,