seekBy always jumps to position 0 when the duration is unknown #5

Closed
opened 2026-08-26 01:53:19 +02:00 by benjamin · 1 comment
Owner

Problem

PlayerController.seekBy() (PlayerController.kt:160-163) clamps the target position:

val target = (player.currentPosition + deltaSeconds * 1000)
    .coerceIn(0, player.duration.coerceAtLeast(0))

Player.getDuration() returns C.TIME_UNSET — a large negative Long — whenever the duration is not (yet) known. TIME_UNSET.coerceAtLeast(0) is 0, so the range collapses to 0..0 and every seek resolves to seekTo(0), regardless of direction or current position.

Failure scenario

Playback of anything whose duration has not resolved yet (right after prepare(), or a live source that never reports one) while isCurrentMediaItemSeekable is true: the user presses right to skip forward and the stream jumps to the very beginning instead. Pressing left does the same.

Suggested fix

Skip the upper clamp when the duration is unknown:

val duration = player.duration
val target = (player.currentPosition + deltaSeconds * 1000).coerceAtLeast(0)
    .let { if (duration > 0) it.coerceAtMost(duration) else it }

Found by multi-agent code review; verified against current PlayerController.kt.

## Problem `PlayerController.seekBy()` (`PlayerController.kt:160-163`) clamps the target position: ```kotlin val target = (player.currentPosition + deltaSeconds * 1000) .coerceIn(0, player.duration.coerceAtLeast(0)) ``` `Player.getDuration()` returns `C.TIME_UNSET` — a large negative `Long` — whenever the duration is not (yet) known. `TIME_UNSET.coerceAtLeast(0)` is `0`, so the range collapses to `0..0` and every seek resolves to `seekTo(0)`, regardless of direction or current position. ## Failure scenario Playback of anything whose duration has not resolved yet (right after `prepare()`, or a live source that never reports one) while `isCurrentMediaItemSeekable` is true: the user presses right to skip forward and the stream jumps to the very beginning instead. Pressing left does the same. ## Suggested fix Skip the upper clamp when the duration is unknown: ```kotlin val duration = player.duration val target = (player.currentPosition + deltaSeconds * 1000).coerceAtLeast(0) .let { if (duration > 0) it.coerceAtMost(duration) else it } ``` Found by multi-agent code review; verified against current `PlayerController.kt`.
Author
Owner

Fixed in dd612fc, shipped in v0.9.0.

seekBy() no longer clamps against an unknown duration:

val duration = player.duration
var target = (player.currentPosition + deltaSeconds * 1000).coerceAtLeast(0)
if (duration > 0) target = target.coerceAtMost(duration)
player.seekTo(target)

With C.TIME_UNSET the upper bound is simply not applied, so a forward seek moves forward instead of collapsing to position 0.

Fixed in dd612fc, shipped in v0.9.0. `seekBy()` no longer clamps against an unknown duration: ```kotlin val duration = player.duration var target = (player.currentPosition + deltaSeconds * 1000).coerceAtLeast(0) if (duration > 0) target = target.coerceAtMost(duration) player.seekTo(target) ``` With `C.TIME_UNSET` the upper bound is simply not applied, so a forward seek moves forward instead of collapsing to position 0.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: be-nj/castarr#5