diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a00c337946..99aa703ab5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,9 @@ by default with null `ImageOutput` and `ImageDecoder.Factory.DEFAULT`. * Emit `Player.Listener.onPositionDiscontinuity` event when silence is skipped ([#765](https://github.com/androidx/media/issues/765)). + * Fix issue where manual seeks outside of the + `LiveConfiguration.min/maxOffset` range keep adjusting the offset back + to `min/maxOffset`. * Transformer: * Add support for flattening H.265/HEVC SEF slow motion videos. * Increase transmuxing speed, especially for 'remove video' edits. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java index 8eeceb08c6..130a518e53 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java @@ -379,15 +379,16 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC private void maybeResetTargetLiveOffsetUs() { long idealOffsetUs = C.TIME_UNSET; if (mediaConfigurationTargetLiveOffsetUs != C.TIME_UNSET) { - idealOffsetUs = - targetLiveOffsetOverrideUs != C.TIME_UNSET - ? targetLiveOffsetOverrideUs - : mediaConfigurationTargetLiveOffsetUs; - if (minTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs < minTargetLiveOffsetUs) { - idealOffsetUs = minTargetLiveOffsetUs; - } - if (maxTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs > maxTargetLiveOffsetUs) { - idealOffsetUs = maxTargetLiveOffsetUs; + if (targetLiveOffsetOverrideUs != C.TIME_UNSET) { + idealOffsetUs = targetLiveOffsetOverrideUs; + } else { + idealOffsetUs = mediaConfigurationTargetLiveOffsetUs; + if (minTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs < minTargetLiveOffsetUs) { + idealOffsetUs = minTargetLiveOffsetUs; + } + if (maxTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs > maxTargetLiveOffsetUs) { + idealOffsetUs = maxTargetLiveOffsetUs; + } } } if (idealTargetLiveOffsetUs == idealOffsetUs) { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java index 7ee65951a6..d8d44467c8 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java @@ -112,7 +112,7 @@ public class DefaultLivePlaybackSpeedControlTest { @Test public void - getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUsGreaterThanMax_returnsMaxLiveOffset() { + getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUsGreaterThanMax_returnsOverride() { DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build(); @@ -128,12 +128,12 @@ public class DefaultLivePlaybackSpeedControlTest { long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); - assertThat(targetLiveOffsetUs).isEqualTo(400_000); + assertThat(targetLiveOffsetUs).isEqualTo(123_456_789); } @Test public void - getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUsLessThanMin_returnsMinLiveOffset() { + getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUsLessThanMin_returnsOverride() { DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build(); @@ -149,7 +149,7 @@ public class DefaultLivePlaybackSpeedControlTest { long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); - assertThat(targetLiveOffsetUs).isEqualTo(5_000); + assertThat(targetLiveOffsetUs).isEqualTo(3_141); } @Test diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index daa9b92239..e75a0d6ad9 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -10598,6 +10598,57 @@ public final class ExoPlayerTest { assertThat(liveOffsetAtEnd).isIn(Range.closed(1_900L, 2_100L)); } + @Test + public void targetLiveOffsetInMedia_withUserSeekOutsideMaxLivOffset_adjustsLiveOffsetToSeek() + throws Exception { + long windowStartUnixTimeMs = 987_654_321_000L; + long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; + ExoPlayer player = + new TestExoPlayerBuilder(context) + .setClock( + new FakeClock(/* initialTimeMs= */ nowUnixTimeMs, /* isAutoAdvancing= */ true)) + .build(); + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ true, + /* isLive= */ true, + /* isPlaceholder= */ false, + /* durationUs= */ 1000 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND, + /* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs), + ImmutableList.of(AdPlaybackState.NONE), + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder() + .setTargetOffsetMs(9_000) + .setMaxOffsetMs(10_000) + .build()) + .build())); + player.pause(); + player.setMediaSource(new FakeMediaSource(timeline)); + player.prepare(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); + long liveOffsetAtStart = player.getCurrentLiveOffset(); + // Verify test setup (now = 20 seconds in live window, default start position = 8 seconds). + assertThat(liveOffsetAtStart).isIn(Range.closed(11_900L, 12_100L)); + + // Seek to a live offset of 15 seconds (outside of declared max offset of 10 seconds). + player.seekTo(5_000); + // Play until close to the end of the available live window. + TestPlayerRunHelper.playUntilPosition( + player, /* mediaItemIndex= */ 0, /* positionMs= */ 999_000); + long liveOffsetAtEnd = player.getCurrentLiveOffset(); + player.release(); + + // Assert the live offset adjustment was permanent. + assertThat(liveOffsetAtEnd).isIn(Range.closed(14_100L, 15_900L)); + } + @Test public void targetLiveOffsetInMedia_withTimelineUpdate_adjustsLiveOffsetToLatestTimeline() throws Exception {