Skip to content

Commit

Permalink
Disable live speed adjustment where it has no benefit
Browse files Browse the repository at this point in the history
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: #9329
PiperOrigin-RevId: 421514283
  • Loading branch information
tonihei authored and icbaker committed Jan 14, 2022
1 parent b77204e commit b09b8dc
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 47 deletions.
5 changes: 4 additions & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* Sleep and retry when creating a `MediaCodec` instance fails. This works
around an issue that occurs on some devices when switching a surface
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
from `MediaCodec`.
([#9766](https://github.com/google/ExoPlayer/issues/9766)).
Expand All @@ -28,6 +28,9 @@
((#8353)[https://github.com/google/ExoPlayer/issues/8353]).
* 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.
* 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:
* Upgrade the Cast extension to depend on
`com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ public void setLiveConfiguration(LiveConfiguration liveConfiguration) {
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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public void getTargetLiveOffsetUs_afterSetLiveConfiguration_returnsMediaLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(42_000);
Expand All @@ -65,8 +65,8 @@ public void getTargetLiveOffsetUs_afterSetLiveConfiguration_returnsMediaLiveOffs
.setTargetOffsetMs(4321)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(400_000);
Expand All @@ -82,8 +82,8 @@ public void getTargetLiveOffsetUs_afterSetLiveConfiguration_returnsMediaLiveOffs
.setTargetOffsetMs(3)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(5_000);
Expand All @@ -100,8 +100,8 @@ public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUs_returnsOver
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
Expand All @@ -121,8 +121,8 @@ public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUs_returnsOver
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
Expand All @@ -142,8 +142,8 @@ public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUs_returnsOver
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
Expand All @@ -163,6 +163,22 @@ public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUs_returnsOver
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() {
Expand All @@ -174,8 +190,8 @@ public void getTargetLiveOffsetUs_afterSetTargetLiveOffsetOverrideUs_returnsOver
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());
defaultLivePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(C.TIME_UNSET);

Expand All @@ -195,8 +211,8 @@ public void getTargetLiveOffsetUs_afterNotifyRebuffer_returnsIncreasedTargetOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
Expand All @@ -218,8 +234,8 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

List<Long> targetOffsetsUs = new ArrayList<>();
Expand All @@ -244,8 +260,8 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

defaultLivePlaybackSpeedControl.notifyRebuffer();
Expand All @@ -266,8 +282,8 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

defaultLivePlaybackSpeedControl.notifyRebuffer();
Expand All @@ -289,8 +305,8 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
Expand Down Expand Up @@ -321,8 +337,8 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs
.setTargetOffsetMs(42)
.setMinOffsetMs(5)
.setMaxOffsetMs(400)
.setMinPlaybackSpeed(1f)
.setMaxPlaybackSpeed(1f)
.setMinPlaybackSpeed(0.95f)
.setMaxPlaybackSpeed(1.05f)
.build());

defaultLivePlaybackSpeedControl.notifyRebuffer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ private void processManifest(boolean scheduleRefresh) {
nowUnixTimeUs
- Util.msToUs(manifest.availabilityStartTimeMs)
- windowStartTimeInManifestUs;
updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs);
updateLiveConfiguration(nowInWindowUs, windowDurationUs);
windowStartUnixTimeMs =
manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs);
windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs);
Expand Down Expand Up @@ -859,7 +859,7 @@ private void processManifest(boolean scheduleRefresh) {
}
}

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;
Expand Down Expand Up @@ -934,6 +934,16 @@ private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDur
} 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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.LiveConfiguration;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Window;
Expand Down Expand Up @@ -140,7 +141,7 @@ public void factorySetFallbackTargetLiveOffsetMs_doesNotChangeMediaItem() {
}

@Test
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesDefaultFallback()
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveConfiguration_usesUnitSpeed()
throws InterruptedException {
DashMediaSource mediaSource =
new DashMediaSource.Factory(
Expand All @@ -154,26 +155,79 @@ public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_uses
.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_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_withoutMediaItemLiveProperties_usesFallback()
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;

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);
}

Expand Down Expand Up @@ -213,7 +267,12 @@ public void prepare_withSuggestedPresentationDelayAndMinBufferTime_usesManifestV
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;
Expand All @@ -222,7 +281,7 @@ public void prepare_withSuggestedPresentationDelayAndMinBufferTime_usesManifestV
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
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.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
Expand Down Expand Up @@ -474,7 +475,7 @@ private SinglePeriodTimeline createTimelineForLive(
targetLiveOffsetUs =
Util.constrainValue(
targetLiveOffsetUs, liveEdgeOffsetUs, playlist.durationUs + liveEdgeOffsetUs);
maybeUpdateLiveConfiguration(targetLiveOffsetUs);
updateLiveConfiguration(playlist, targetLiveOffsetUs);
long windowDefaultStartPositionUs =
getLiveWindowDefaultStartPositionUs(playlist, liveEdgeOffsetUs);
boolean suppressPositionProjection =
Expand Down Expand Up @@ -564,12 +565,18 @@ private long getLiveWindowDefaultStartPositionUs(
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();
}

/**
Expand Down
Loading

0 comments on commit b09b8dc

Please sign in to comment.