From c9f6ad03982db7282b84c36ebfa05d3343934c8e Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 21 Nov 2023 06:42:49 -0800 Subject: [PATCH] Avoid clipping live offset override to min/max offsets The live offset override is used to replace the media-defined live offset after user seeks to ensure the live adjustment adjusts to the new user-provided live offset and doesn't go back to the original one. However, the code currently clips the override to the min/max live offsets defined in LiveConfiguration. This is useful to clip the default value (in case of inconsistent values in the media), but the clipping shouldn't be applied to user overrides as the player will then adjust the position back to the min/max and doesn't stay at the desired user position. See https://github.com/androidx/media/commit/2416d9985718accfcc00ddc951afa217c261f7ae#r132871601 PiperOrigin-RevId: 584311004 (cherry picked from commit af0282b9db62a8c5de1dbcc49794872d0157c1ab) --- RELEASENOTES.md | 3 ++ .../DefaultLivePlaybackSpeedControl.java | 19 +++---- .../DefaultLivePlaybackSpeedControlTest.java | 8 +-- .../media3/exoplayer/ExoPlayerTest.java | 51 +++++++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index af3dab6a15..324def25a3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * Common Library: * ExoPlayer: + * Fix issue where manual seeks outside of the + `LiveConfiguration.min/maxOffset` range keep adjusting the offset back + to `min/maxOffset`. * Transformer: * Track Selection: * Extractors: 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 0b3a2b0b7e..3b8ca307d1 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -10594,6 +10594,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 {