From b12918d1e6b92763c0a7b870e598d260a052361f Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 13 Jan 2022 10:38:43 +0000 Subject: [PATCH] Disable live speed adjustment where it has no benefit Live speed adjustment is used for all live playback at the moment, but has no user visible effect if the media is not played with low latency. To avoid unnecessary adjustment during playback without benefit, this change restricts the live speed adjustment to cases where either the user requested a speed value in the MediaItem or the media specifically defined a low-latency stream. Issue: google/ExoPlayer#9329 PiperOrigin-RevId: 421514283 --- .../DefaultLivePlaybackSpeedControl.java | 4 + .../DefaultLivePlaybackSpeedControlTest.java | 68 +++++++++------ .../exoplayer/dash/DashMediaSource.java | 14 ++- .../exoplayer/dash/DashMediaSourceTest.java | 75 ++++++++++++++-- .../media3/exoplayer/hls/HlsMediaSource.java | 21 +++-- .../exoplayer/hls/HlsMediaSourceTest.java | 85 ++++++++++++++++++- 6 files changed, 221 insertions(+), 46 deletions(-) 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 b8f84a7743..0de033fc9d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControl.java @@ -311,6 +311,10 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET ? liveConfiguration.maxPlaybackSpeed : fallbackMaxPlaybackSpeed; + if (minPlaybackSpeed == 1f && maxPlaybackSpeed == 1f) { + // Don't bother calculating adjustments if it's not possible to change the speed. + mediaConfigurationTargetLiveOffsetUs = C.TIME_UNSET; + } maybeResetTargetLiveOffsetUs(); } 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 930a9356a7..7ee65951a6 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLivePlaybackSpeedControlTest.java @@ -49,8 +49,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(42_000); @@ -66,8 +66,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(4321) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(400_000); @@ -83,8 +83,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(3) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(5_000); @@ -101,8 +101,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); @@ -122,8 +122,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); @@ -143,8 +143,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); @@ -164,6 +164,22 @@ public class DefaultLivePlaybackSpeedControlTest { assertThat(targetLiveOffsetUs).isEqualTo(C.TIME_UNSET); } + @Test + public void getTargetLiveOffsetUs_withUnitSpeed_returnsUnset() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder().build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration.Builder() + .setTargetOffsetMs(42) + .setMinPlaybackSpeed(1f) + .setMaxPlaybackSpeed(1f) + .build()); + + long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetUs).isEqualTo(C.TIME_UNSET); + } + @Test public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideWithTimeUnset_returnsMediaLiveOffset() { @@ -175,8 +191,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); defaultLivePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(C.TIME_UNSET); @@ -196,8 +212,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); @@ -219,8 +235,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); List targetOffsetsUs = new ArrayList<>(); @@ -245,8 +261,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); defaultLivePlaybackSpeedControl.notifyRebuffer(); @@ -267,8 +283,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); defaultLivePlaybackSpeedControl.notifyRebuffer(); @@ -290,8 +306,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); @@ -322,8 +338,8 @@ public class DefaultLivePlaybackSpeedControlTest { .setTargetOffsetMs(42) .setMinOffsetMs(5) .setMaxOffsetMs(400) - .setMinPlaybackSpeed(1f) - .setMaxPlaybackSpeed(1f) + .setMinPlaybackSpeed(0.95f) + .setMaxPlaybackSpeed(1.05f) .build()); defaultLivePlaybackSpeedControl.notifyRebuffer(); diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java index 3e151d60d5..038b4191ef 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java @@ -802,7 +802,7 @@ public final class DashMediaSource extends BaseMediaSource { nowUnixTimeUs - Util.msToUs(manifest.availabilityStartTimeMs) - windowStartTimeInManifestUs; - updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs); + updateLiveConfiguration(nowInWindowUs, windowDurationUs); windowStartUnixTimeMs = manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs); windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs); @@ -861,7 +861,7 @@ public final class DashMediaSource extends BaseMediaSource { } } - private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDurationUs) { + private void updateLiveConfiguration(long nowInWindowUs, long windowDurationUs) { // Default maximum offset: start of window. long maxPossibleLiveOffsetMs = usToMs(nowInWindowUs); long maxLiveOffsetMs = maxPossibleLiveOffsetMs; @@ -936,6 +936,16 @@ public final class DashMediaSource extends BaseMediaSource { } else if (manifest.serviceDescription != null) { maxPlaybackSpeed = manifest.serviceDescription.maxPlaybackSpeed; } + if (minPlaybackSpeed == C.RATE_UNSET + && maxPlaybackSpeed == C.RATE_UNSET + && (manifest.serviceDescription == null + || manifest.serviceDescription.targetOffsetMs == C.TIME_UNSET)) { + // Force unit speed (instead of automatic adjustment with fallback speeds) if there are no + // specific speed limits defined by the media item or the manifest, and the manifest contains + // no low-latency target offset either. + minPlaybackSpeed = 1f; + maxPlaybackSpeed = 1f; + } liveConfiguration = new MediaItem.LiveConfiguration.Builder() .setTargetOffsetMs(targetOffsetMs) diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java index 20ba0560ee..4cca2aee2e 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.fail; import android.net.Uri; import androidx.media3.common.C; import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaItem.LiveConfiguration; import androidx.media3.common.ParserException; import androidx.media3.common.Timeline; import androidx.media3.common.Timeline.Window; @@ -140,7 +141,7 @@ public final class DashMediaSourceTest { } @Test - public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesDefaultFallback() + public void prepare_withoutLiveConfiguration_withoutMediaItemLiveConfiguration_usesUnitSpeed() throws InterruptedException { DashMediaSource mediaSource = new DashMediaSource.Factory( @@ -154,18 +155,71 @@ public final class DashMediaSourceTest { .isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS); assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); - assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); - assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(1f); + assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1f); } @Test - public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesFallback() + public void prepare_withoutLiveConfiguration_withOnlyMediaItemTargetOffset_usesUnitSpeed() throws InterruptedException { + DashMediaSource mediaSource = + new DashMediaSource.Factory( + () -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION)) + .createMediaSource( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new LiveConfiguration.Builder().setTargetOffsetMs(10_000L).build()) + .build()); + + MediaItem.LiveConfiguration liveConfiguration = + prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; + + assertThat(liveConfiguration.targetOffsetMs).isEqualTo(10_000L); + assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); + assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); + assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(1f); + assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1f); + } + + @Test + public void prepare_withoutLiveConfiguration_withMediaItemSpeedLimits_usesDefaultFallbackValues() + throws InterruptedException { + DashMediaSource mediaSource = + new DashMediaSource.Factory( + () -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION)) + .createMediaSource( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new LiveConfiguration.Builder().setMinPlaybackSpeed(0.95f).build()) + .build()); + + MediaItem.LiveConfiguration liveConfiguration = + prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; + + assertThat(liveConfiguration.targetOffsetMs) + .isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS); + assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); + assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); + assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(0.95f); + assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); + } + + @Test + public void + prepare_withoutLiveConfiguration_withoutMediaItemTargetOffset_usesDefinedFallbackTargetOffset() + throws InterruptedException { DashMediaSource mediaSource = new DashMediaSource.Factory( () -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION)) .setFallbackTargetLiveOffsetMs(1234L) - .createMediaSource(MediaItem.fromUri(Uri.EMPTY)); + .createMediaSource( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new LiveConfiguration.Builder().setMinPlaybackSpeed(0.95f).build()) + .build()); MediaItem.LiveConfiguration liveConfiguration = prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; @@ -173,7 +227,7 @@ public final class DashMediaSourceTest { assertThat(liveConfiguration.targetOffsetMs).isEqualTo(1234L); assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); - assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(0.95f); assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); } @@ -213,7 +267,12 @@ public final class DashMediaSourceTest { createSampleMpdDataSource( SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS)) .setFallbackTargetLiveOffsetMs(1234L) - .createMediaSource(MediaItem.fromUri(Uri.EMPTY)); + .createMediaSource( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new LiveConfiguration.Builder().setMaxPlaybackSpeed(1.05f).build()) + .build()); MediaItem.LiveConfiguration liveConfiguration = prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; @@ -222,7 +281,7 @@ public final class DashMediaSourceTest { assertThat(liveConfiguration.minOffsetMs).isEqualTo(500L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); - assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1.05f); } @Test diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java index e0bd7029fb..7610aa8286 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java @@ -25,6 +25,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaItem.LiveConfiguration; import androidx.media3.common.MediaLibraryInfo; import androidx.media3.common.StreamKey; import androidx.media3.common.util.UnstableApi; @@ -476,7 +477,7 @@ public final class HlsMediaSource extends BaseMediaSource targetLiveOffsetUs = Util.constrainValue( targetLiveOffsetUs, liveEdgeOffsetUs, playlist.durationUs + liveEdgeOffsetUs); - maybeUpdateLiveConfiguration(targetLiveOffsetUs); + updateLiveConfiguration(playlist, targetLiveOffsetUs); long windowDefaultStartPositionUs = getLiveWindowDefaultStartPositionUs(playlist, liveEdgeOffsetUs); boolean suppressPositionProjection = @@ -566,12 +567,18 @@ public final class HlsMediaSource extends BaseMediaSource return segment.relativeStartTimeUs; } - private void maybeUpdateLiveConfiguration(long targetLiveOffsetUs) { - long targetLiveOffsetMs = Util.usToMs(targetLiveOffsetUs); - if (targetLiveOffsetMs != liveConfiguration.targetOffsetMs) { - liveConfiguration = - liveConfiguration.buildUpon().setTargetOffsetMs(targetLiveOffsetMs).build(); - } + private void updateLiveConfiguration(HlsMediaPlaylist playlist, long targetLiveOffsetUs) { + boolean disableSpeedAdjustment = + mediaItem.liveConfiguration.minPlaybackSpeed == C.RATE_UNSET + && mediaItem.liveConfiguration.maxPlaybackSpeed == C.RATE_UNSET + && playlist.serverControl.holdBackUs == C.TIME_UNSET + && playlist.serverControl.partHoldBackUs == C.TIME_UNSET; + liveConfiguration = + new LiveConfiguration.Builder() + .setTargetOffsetMs(Util.usToMs(targetLiveOffsetUs)) + .setMinPlaybackSpeed(disableSpeedAdjustment ? 1f : liveConfiguration.minPlaybackSpeed) + .setMaxPlaybackSpeed(disableSpeedAdjustment ? 1f : liveConfiguration.maxPlaybackSpeed) + .build(); } /** diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsMediaSourceTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsMediaSourceTest.java index 01e73a87f0..7e3f637fa3 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsMediaSourceTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/HlsMediaSourceTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import android.os.SystemClock; +import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.ParserException; import androidx.media3.common.Timeline; @@ -77,6 +78,8 @@ public class HlsMediaSourceTest { // The target live offset is picked from target duration (3 * 4 = 12 seconds) and then expressed // in relation to the live edge (12 + 1 seconds). assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); + assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(1f); + assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(1f); assertThat(window.defaultPositionUs).isEqualTo(4000000); } @@ -100,7 +103,7 @@ public class HlsMediaSourceTest { + "#EXTINF:4.00000,\n" + "fileSequence3.ts\n" + "#EXT-X-SERVER-CONTROL:HOLD-BACK=12"; - // The playlist finishes 1 second before the the current time, therefore there's a live edge + // The playlist finishes 1 second before the current time, therefore there's a live edge // offset of 1 second. SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00")); HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); @@ -113,6 +116,8 @@ public class HlsMediaSourceTest { // The target live offset is picked from hold back and then expressed in relation to the live // edge (+1 seconds). assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); + assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(window.defaultPositionUs).isEqualTo(4000000); } @@ -139,7 +144,7 @@ public class HlsMediaSourceTest { + "#EXTINF:4.00000,\n" + "fileSequence3.ts\n" + "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3"; - // The playlist finishes 1 second before the the current time. + // The playlist finishes 1 second before the current time. SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00")); HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); MediaItem mediaItem = MediaItem.fromUri(playlistUri); @@ -151,6 +156,8 @@ public class HlsMediaSourceTest { // The target live offset is picked from hold back and then expressed in relation to the live // edge (+1 seconds). assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); + assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(window.defaultPositionUs).isEqualTo(4000000); } @@ -182,6 +189,8 @@ public class HlsMediaSourceTest { // The target live offset is picked from part hold back and then expressed in relation to the // live edge (+1 seconds). assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(4000); + assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); + assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(window.defaultPositionUs).isEqualTo(0); } @@ -395,7 +404,7 @@ public class HlsMediaSourceTest { + "#EXTINF:4.00000,\n" + "fileSequence0.ts\n" + "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3"; - // The playlist finishes 1 second before the the current time. This should not affect the target + // The playlist finishes 1 second before the current time. This should not affect the target // live offset set in the media item. SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:05.0+00:00")); HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); @@ -415,6 +424,76 @@ public class HlsMediaSourceTest { assertThat(window.defaultPositionUs).isEqualTo(0); } + @Test + public void loadLivePlaylist_noHoldBackInPlaylistAndNoPlaybackSpeedInMediaItem_usesUnitSpeed() + throws TimeoutException, ParserException { + String playlistUri = "fake://foo.bar/media0/playlist.m3u8"; + // The playlist has no hold back defined. + String playlist = + "#EXTM3U\n" + + "#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n" + + "#EXT-X-TARGETDURATION:4\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXTINF:4.00000,\n" + + "fileSequence0.ts"; + // The playlist finishes 1 second before the current time. This should not affect the target + // live offset set in the media item. + SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:05.0+00:00")); + HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(playlistUri) + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder().setTargetOffsetMs(1000).build()) + .build(); + HlsMediaSource mediaSource = factory.createMediaSource(mediaItem); + + Timeline timeline = prepareAndWaitForTimeline(mediaSource); + + Timeline.Window window = timeline.getWindow(0, new Timeline.Window()); + assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(1f); + assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(1f); + assertThat(window.defaultPositionUs).isEqualTo(0); + } + + @Test + public void + loadLivePlaylist_noHoldBackInPlaylistAndPlaybackSpeedInMediaItem_usesMediaItemConfiguration() + throws TimeoutException, ParserException { + String playlistUri = "fake://foo.bar/media0/playlist.m3u8"; + // The playlist has no hold back defined. + String playlist = + "#EXTM3U\n" + + "#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n" + + "#EXT-X-TARGETDURATION:4\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXTINF:4.00000,\n" + + "fileSequence0.ts"; + // The playlist finishes 1 second before the current time. This should not affect the target + // live offset set in the media item. + SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:05.0+00:00")); + HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(playlistUri) + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder() + .setTargetOffsetMs(1000) + .setMinPlaybackSpeed(0.94f) + .setMaxPlaybackSpeed(1.02f) + .build()) + .build(); + HlsMediaSource mediaSource = factory.createMediaSource(mediaItem); + + Timeline timeline = prepareAndWaitForTimeline(mediaSource); + + Timeline.Window window = timeline.getWindow(0, new Timeline.Window()); + assertThat(window.liveConfiguration).isEqualTo(mediaItem.liveConfiguration); + assertThat(window.defaultPositionUs).isEqualTo(0); + } + @Test public void loadLivePlaylist_targetLiveOffsetLargerThanLiveWindow_targetLiveOffsetIsWithinLiveWindow()