From af0282b9db62a8c5de1dbcc49794872d0157c1ab 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 #minor-release PiperOrigin-RevId: 584311004 --- 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 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 {