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()