fix(android player): seek broke after seamless track transitions (durationMs stuck at 0)
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
803331e807
commit
87ee8cd097
2 files changed
+17
-2
No files matched your search
@@ -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).
|
- **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.
|
- **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.
|
- 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** — Playlist drag-and-drop / sort order
|
||||||
- **Android** — Artist page iTunes fallback (for artists not in Navidrome)
|
- **Android** — Artist page iTunes fallback (for artists not in Navidrome)
|
||||||
- **Android** — Romaji / Japanese sort support
|
- **Android** — Romaji / Japanese sort support
|
||||||
|
|||||||
@@ -227,11 +227,25 @@ class PlayerController(private val context: Context, private val repo: MusicRepo
|
|||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(500)
|
delay(500)
|
||||||
val ctrl = controller ?: break
|
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() {
|
private fun stopTicker() {
|
||||||
tickerJob?.cancel()
|
tickerJob?.cancel()
|
||||||
tickerJob = null
|
tickerJob = null
|
||||||
@@ -271,7 +285,7 @@ class PlayerController(private val context: Context, private val repo: MusicRepo
|
|||||||
currentSong = queue.getOrNull(idx),
|
currentSong = queue.getOrNull(idx),
|
||||||
currentIndex = idx,
|
currentIndex = idx,
|
||||||
positionMs = ctrl.currentPosition,
|
positionMs = ctrl.currentPosition,
|
||||||
durationMs = ctrl.duration.coerceAtLeast(0),
|
durationMs = resolveDurationMs(ctrl),
|
||||||
queue = queue,
|
queue = queue,
|
||||||
repeatMode = ctrl.repeatMode,
|
repeatMode = ctrl.repeatMode,
|
||||||
shuffled = isManuallyShuffled,
|
shuffled = isManuallyShuffled,
|
||||||
|
|||||||
Reference in new issue
Block a user