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
This commit is contained in:
tonihei 2022-01-13 10:38:43 +00:00 committed by Ian Baker
parent b77204eb4d
commit b09b8dc2ab
7 changed files with 225 additions and 47 deletions

View File

@ -17,7 +17,7 @@
* Sleep and retry when creating a `MediaCodec` instance fails. This works * Sleep and retry when creating a `MediaCodec` instance fails. This works
around an issue that occurs on some devices when switching a surface around an issue that occurs on some devices when switching a surface
from a secure codec to another codec from a secure codec to another codec
(#8696)[https://github.com/google/ExoPlayer/issues/8696]. ((#8696)[https://github.com/google/ExoPlayer/issues/8696]).
* Add `MediaCodecAdapter.getMetrics()` to allow users obtain metrics data * Add `MediaCodecAdapter.getMetrics()` to allow users obtain metrics data
from `MediaCodec`. from `MediaCodec`.
([#9766](https://github.com/google/ExoPlayer/issues/9766)). ([#9766](https://github.com/google/ExoPlayer/issues/9766)).
@ -28,6 +28,9 @@
((#8353)[https://github.com/google/ExoPlayer/issues/8353]). ((#8353)[https://github.com/google/ExoPlayer/issues/8353]).
* Fix decoder fallback logic for Dolby Atmos (E-AC3-JOC) and Dolby Vision * Fix decoder fallback logic for Dolby Atmos (E-AC3-JOC) and Dolby Vision
to use a compatible base decoder (E-AC3 or H264/H265) if needed. to use a compatible base decoder (E-AC3 or H264/H265) if needed.
* Disable automatic speed adjustment for live streams that neither have
low-latency features nor a user request setting the speed
((#9329)[https://github.com/google/ExoPlayer/issues/9329]).
* Android 12 compatibility: * Android 12 compatibility:
* Upgrade the Cast extension to depend on * Upgrade the Cast extension to depend on
`com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier `com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier

View File

@ -308,6 +308,10 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET
? liveConfiguration.maxPlaybackSpeed ? liveConfiguration.maxPlaybackSpeed
: fallbackMaxPlaybackSpeed; : 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(); maybeResetTargetLiveOffsetUs();
} }

View File

@ -48,8 +48,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(42_000); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(42_000);
@ -65,8 +65,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(4321) .setTargetOffsetMs(4321)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(400_000); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(400_000);
@ -82,8 +82,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(3) .setTargetOffsetMs(3)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(5_000); assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(5_000);
@ -100,8 +100,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
@ -121,8 +121,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
@ -142,8 +142,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
@ -163,6 +163,22 @@ public class DefaultLivePlaybackSpeedControlTest {
assertThat(targetLiveOffsetUs).isEqualTo(C.TIME_UNSET); 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 @Test
public void public void
getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideWithTimeUnset_returnsMediaLiveOffset() { getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideWithTimeUnset_returnsMediaLiveOffset() {
@ -174,8 +190,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
defaultLivePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(C.TIME_UNSET); defaultLivePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(C.TIME_UNSET);
@ -195,8 +211,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
@ -218,8 +234,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
List<Long> targetOffsetsUs = new ArrayList<>(); List<Long> targetOffsetsUs = new ArrayList<>();
@ -244,8 +260,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
defaultLivePlaybackSpeedControl.notifyRebuffer(); defaultLivePlaybackSpeedControl.notifyRebuffer();
@ -266,8 +282,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
defaultLivePlaybackSpeedControl.notifyRebuffer(); defaultLivePlaybackSpeedControl.notifyRebuffer();
@ -289,8 +305,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
@ -321,8 +337,8 @@ public class DefaultLivePlaybackSpeedControlTest {
.setTargetOffsetMs(42) .setTargetOffsetMs(42)
.setMinOffsetMs(5) .setMinOffsetMs(5)
.setMaxOffsetMs(400) .setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f) .setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1f) .setMaxPlaybackSpeed(1.05f)
.build()); .build());
defaultLivePlaybackSpeedControl.notifyRebuffer(); defaultLivePlaybackSpeedControl.notifyRebuffer();

View File

@ -800,7 +800,7 @@ public final class DashMediaSource extends BaseMediaSource {
nowUnixTimeUs nowUnixTimeUs
- Util.msToUs(manifest.availabilityStartTimeMs) - Util.msToUs(manifest.availabilityStartTimeMs)
- windowStartTimeInManifestUs; - windowStartTimeInManifestUs;
updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs); updateLiveConfiguration(nowInWindowUs, windowDurationUs);
windowStartUnixTimeMs = windowStartUnixTimeMs =
manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs); manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs);
windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs); windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs);
@ -859,7 +859,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. // Default maximum offset: start of window.
long maxPossibleLiveOffsetMs = usToMs(nowInWindowUs); long maxPossibleLiveOffsetMs = usToMs(nowInWindowUs);
long maxLiveOffsetMs = maxPossibleLiveOffsetMs; long maxLiveOffsetMs = maxPossibleLiveOffsetMs;
@ -934,6 +934,16 @@ public final class DashMediaSource extends BaseMediaSource {
} else if (manifest.serviceDescription != null) { } else if (manifest.serviceDescription != null) {
maxPlaybackSpeed = manifest.serviceDescription.maxPlaybackSpeed; 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 = liveConfiguration =
new MediaItem.LiveConfiguration.Builder() new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(targetOffsetMs) .setTargetOffsetMs(targetOffsetMs)

View File

@ -24,6 +24,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaItem.LiveConfiguration;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.Timeline.Window;
@ -140,7 +141,7 @@ public final class DashMediaSourceTest {
} }
@Test @Test
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesDefaultFallback() public void prepare_withoutLiveConfiguration_withoutMediaItemLiveConfiguration_usesUnitSpeed()
throws InterruptedException { throws InterruptedException {
DashMediaSource mediaSource = DashMediaSource mediaSource =
new DashMediaSource.Factory( new DashMediaSource.Factory(
@ -154,18 +155,71 @@ public final class DashMediaSourceTest {
.isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS); .isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS);
assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L);
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(1f);
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1f);
} }
@Test @Test
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesFallback() public void prepare_withoutLiveConfiguration_withOnlyMediaItemTargetOffset_usesUnitSpeed()
throws InterruptedException { 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 = DashMediaSource mediaSource =
new DashMediaSource.Factory( new DashMediaSource.Factory(
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION)) () -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
.setFallbackTargetLiveOffsetMs(1234L) .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 = MediaItem.LiveConfiguration liveConfiguration =
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
@ -173,7 +227,7 @@ public final class DashMediaSourceTest {
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(1234L); assertThat(liveConfiguration.targetOffsetMs).isEqualTo(1234L);
assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L); assertThat(liveConfiguration.minOffsetMs).isEqualTo(0L);
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); 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); assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
} }
@ -213,7 +267,12 @@ public final class DashMediaSourceTest {
createSampleMpdDataSource( createSampleMpdDataSource(
SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS)) SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS))
.setFallbackTargetLiveOffsetMs(1234L) .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 = MediaItem.LiveConfiguration liveConfiguration =
prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration;
@ -222,7 +281,7 @@ public final class DashMediaSourceTest {
assertThat(liveConfiguration.minOffsetMs).isEqualTo(500L); assertThat(liveConfiguration.minOffsetMs).isEqualTo(500L);
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L);
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET); assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(1.05f);
} }
@Test @Test

View File

@ -26,6 +26,7 @@ import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaItem.LiveConfiguration;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider; import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
@ -474,7 +475,7 @@ public final class HlsMediaSource extends BaseMediaSource
targetLiveOffsetUs = targetLiveOffsetUs =
Util.constrainValue( Util.constrainValue(
targetLiveOffsetUs, liveEdgeOffsetUs, playlist.durationUs + liveEdgeOffsetUs); targetLiveOffsetUs, liveEdgeOffsetUs, playlist.durationUs + liveEdgeOffsetUs);
maybeUpdateLiveConfiguration(targetLiveOffsetUs); updateLiveConfiguration(playlist, targetLiveOffsetUs);
long windowDefaultStartPositionUs = long windowDefaultStartPositionUs =
getLiveWindowDefaultStartPositionUs(playlist, liveEdgeOffsetUs); getLiveWindowDefaultStartPositionUs(playlist, liveEdgeOffsetUs);
boolean suppressPositionProjection = boolean suppressPositionProjection =
@ -564,12 +565,18 @@ public final class HlsMediaSource extends BaseMediaSource
return segment.relativeStartTimeUs; return segment.relativeStartTimeUs;
} }
private void maybeUpdateLiveConfiguration(long targetLiveOffsetUs) { private void updateLiveConfiguration(HlsMediaPlaylist playlist, long targetLiveOffsetUs) {
long targetLiveOffsetMs = Util.usToMs(targetLiveOffsetUs); boolean disableSpeedAdjustment =
if (targetLiveOffsetMs != liveConfiguration.targetOffsetMs) { mediaItem.liveConfiguration.minPlaybackSpeed == C.RATE_UNSET
liveConfiguration = && mediaItem.liveConfiguration.maxPlaybackSpeed == C.RATE_UNSET
liveConfiguration.buildUpon().setTargetOffsetMs(targetLiveOffsetMs).build(); && 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();
} }
/** /**

View File

@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.net.Uri; import android.net.Uri;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
@ -77,6 +78,8 @@ public class HlsMediaSourceTest {
// The target live offset is picked from target duration (3 * 4 = 12 seconds) and then expressed // 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). // in relation to the live edge (12 + 1 seconds).
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000);
assertThat(window.liveConfiguration.minPlaybackSpeed).isEqualTo(1f);
assertThat(window.liveConfiguration.maxPlaybackSpeed).isEqualTo(1f);
assertThat(window.defaultPositionUs).isEqualTo(4000000); assertThat(window.defaultPositionUs).isEqualTo(4000000);
} }
@ -100,7 +103,7 @@ public class HlsMediaSourceTest {
+ "#EXTINF:4.00000,\n" + "#EXTINF:4.00000,\n"
+ "fileSequence3.ts\n" + "fileSequence3.ts\n"
+ "#EXT-X-SERVER-CONTROL:HOLD-BACK=12"; + "#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. // offset of 1 second.
SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00")); SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00"));
HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); 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 // The target live offset is picked from hold back and then expressed in relation to the live
// edge (+1 seconds). // edge (+1 seconds).
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); 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); assertThat(window.defaultPositionUs).isEqualTo(4000000);
} }
@ -139,7 +144,7 @@ public class HlsMediaSourceTest {
+ "#EXTINF:4.00000,\n" + "#EXTINF:4.00000,\n"
+ "fileSequence3.ts\n" + "fileSequence3.ts\n"
+ "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3"; + "#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")); SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00"));
HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist);
MediaItem mediaItem = MediaItem.fromUri(playlistUri); 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 // The target live offset is picked from hold back and then expressed in relation to the live
// edge (+1 seconds). // edge (+1 seconds).
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(13000); 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); 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 // The target live offset is picked from part hold back and then expressed in relation to the
// live edge (+1 seconds). // live edge (+1 seconds).
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(4000); 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); assertThat(window.defaultPositionUs).isEqualTo(0);
} }
@ -395,7 +404,7 @@ public class HlsMediaSourceTest {
+ "#EXTINF:4.00000,\n" + "#EXTINF:4.00000,\n"
+ "fileSequence0.ts\n" + "fileSequence0.ts\n"
+ "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3"; + "#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. // live offset set in the media item.
SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:05.0+00:00")); SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:05.0+00:00"));
HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist); HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist);
@ -415,6 +424,76 @@ public class HlsMediaSourceTest {
assertThat(window.defaultPositionUs).isEqualTo(0); 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 @Test
public void public void
loadLivePlaylist_targetLiveOffsetLargerThanLiveWindow_targetLiveOffsetIsWithinLiveWindow() loadLivePlaylist_targetLiveOffsetLargerThanLiveWindow_targetLiveOffsetIsWithinLiveWindow()