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: google/ExoPlayer#9329
PiperOrigin-RevId: 421514283
  • Loading branch information
tonihei authored and icbaker committed Jan 25, 2022
1 parent 6caaf58 commit b12918d
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,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 @@ -49,8 +49,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 @@ -66,8 +66,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 @@ -83,8 +83,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 @@ -101,8 +101,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 @@ -122,8 +122,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 @@ -143,8 +143,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 @@ -164,6 +164,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 @@ -175,8 +191,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 @@ -196,8 +212,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 @@ -219,8 +235,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 @@ -245,8 +261,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 @@ -267,8 +283,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 @@ -290,8 +306,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 @@ -322,8 +338,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 @@ -802,7 +802,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 @@ -861,7 +861,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 @@ -936,6 +936,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 @@ -22,6 +22,7 @@
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;
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 @@ -25,6 +25,7 @@
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;
Expand Down Expand Up @@ -476,7 +477,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 @@ -566,12 +567,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 b12918d

Please sign in to comment.