From 86ae7ebac4eab3be1d88a7f26d367f1806f1bf90 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 9 Nov 2020 14:53:50 +0000 Subject: [PATCH] Decrease target live offset if safely possible. To check what is safely possible we keep track of the live offset corresponding to the buffered duration and only deecrease the target offset to a safe margin from the buffered duration. Also, while still possible (i.e. while the actual offset is larger than the safe margin), we increase the target offset to the safe margin to avoid rebuffers to start with. Issue: #4904 PiperOrigin-RevId: 341396492 --- .../DefaultLivePlaybackSpeedControl.java | 119 +++++++++- .../exoplayer2/ExoPlayerImplInternal.java | 3 +- .../exoplayer2/LivePlaybackSpeedControl.java | 3 +- .../DefaultLivePlaybackSpeedControlTest.java | 222 ++++++++++++++++-- 4 files changed, 321 insertions(+), 26 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java index 6ea6f083217..9bb3d1cd38f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControl.java @@ -15,6 +15,10 @@ */ package com.google.android.exoplayer2; +import static com.google.common.primitives.Longs.max; +import static java.lang.Math.abs; +import static java.lang.Math.max; + import android.os.SystemClock; import com.google.android.exoplayer2.MediaItem.LiveConfiguration; import com.google.android.exoplayer2.util.Assertions; @@ -36,7 +40,10 @@ * *

When the player rebuffers, the target live offset {@link * Builder#setTargetLiveOffsetIncrementOnRebufferMs(long) is increased} to adjust to the reduced - * network capabilities. + * network capabilities. The live playback speed control also {@link + * Builder#setMinPossibleLiveOffsetSmoothingFactor(float) keeps track} of the minimum possible live + * offset to decrease the target live offset again if conditions improve. The minimum possible live + * offset is derived from the current offset and the duration of buffered media. */ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedControl { @@ -70,6 +77,12 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC */ public static final long DEFAULT_TARGET_LIVE_OFFSET_INCREMENT_ON_REBUFFER_MS = 500; + /** + * The default smoothing factor when smoothing the minimum possible live offset that can be + * achieved during playback. + */ + public static final float DEFAULT_MIN_POSSIBLE_LIVE_OFFSET_SMOOTHING_FACTOR = 0.999f; + /** * The maximum difference between the current live offset and the target live offset for which * unit speed (1.0f) is used. @@ -84,6 +97,7 @@ public static final class Builder { private long minUpdateIntervalMs; private float proportionalControlFactorUs; private long targetLiveOffsetIncrementOnRebufferUs; + private float minPossibleLiveOffsetSmoothingFactor; /** Creates a builder. */ public Builder() { @@ -93,6 +107,7 @@ public Builder() { proportionalControlFactorUs = DEFAULT_PROPORTIONAL_CONTROL_FACTOR / C.MICROS_PER_SECOND; targetLiveOffsetIncrementOnRebufferUs = C.msToUs(DEFAULT_TARGET_LIVE_OFFSET_INCREMENT_ON_REBUFFER_MS); + minPossibleLiveOffsetSmoothingFactor = DEFAULT_MIN_POSSIBLE_LIVE_OFFSET_SMOOTHING_FACTOR; } /** @@ -173,6 +188,28 @@ public Builder setTargetLiveOffsetIncrementOnRebufferMs( return this; } + /** + * Sets the smoothing factor when smoothing the minimum possible live offset that can be + * achieved during playback. + * + *

The live playback speed control keeps track of the minimum possible live offset achievable + * during playback to know whether it can reduce the current target live offset. The minimum + * possible live offset is defined as {@code currentLiveOffset - bufferedDuration}. As the + * minimum possible live offset is constantly changing, it is smoothed over recent samples by + * applying exponential smoothing: {@code smoothedMinPossibleOffset = smoothingFactor x + * smoothedMinPossibleOffset + (1-smoothingFactor) x currentMinPossibleOffset}. + * + * @param minPossibleLiveOffsetSmoothingFactor The smoothing factor. Must be ≥ 0 and < 1. + * @return This builder, for convenience. + */ + public Builder setMinPossibleLiveOffsetSmoothingFactor( + float minPossibleLiveOffsetSmoothingFactor) { + Assertions.checkArgument( + minPossibleLiveOffsetSmoothingFactor >= 0 && minPossibleLiveOffsetSmoothingFactor < 1f); + this.minPossibleLiveOffsetSmoothingFactor = minPossibleLiveOffsetSmoothingFactor; + return this; + } + /** Builds an instance. */ public DefaultLivePlaybackSpeedControl build() { return new DefaultLivePlaybackSpeedControl( @@ -180,7 +217,8 @@ public DefaultLivePlaybackSpeedControl build() { fallbackMaxPlaybackSpeed, minUpdateIntervalMs, proportionalControlFactorUs, - targetLiveOffsetIncrementOnRebufferUs); + targetLiveOffsetIncrementOnRebufferUs, + minPossibleLiveOffsetSmoothingFactor); } } @@ -189,6 +227,7 @@ public DefaultLivePlaybackSpeedControl build() { private final long minUpdateIntervalMs; private final float proportionalControlFactor; private final long targetLiveOffsetRebufferDeltaUs; + private final float minPossibleLiveOffsetSmoothingFactor; private long mediaConfigurationTargetLiveOffsetUs; private long targetLiveOffsetOverrideUs; @@ -202,17 +241,22 @@ public DefaultLivePlaybackSpeedControl build() { private float adjustedPlaybackSpeed; private long lastPlaybackSpeedUpdateMs; + private long smoothedMinPossibleLiveOffsetUs; + private long smoothedMinPossibleLiveOffsetDeviationUs; + private DefaultLivePlaybackSpeedControl( float fallbackMinPlaybackSpeed, float fallbackMaxPlaybackSpeed, long minUpdateIntervalMs, float proportionalControlFactor, - long targetLiveOffsetRebufferDeltaUs) { + long targetLiveOffsetRebufferDeltaUs, + float minPossibleLiveOffsetSmoothingFactor) { this.fallbackMinPlaybackSpeed = fallbackMinPlaybackSpeed; this.fallbackMaxPlaybackSpeed = fallbackMaxPlaybackSpeed; this.minUpdateIntervalMs = minUpdateIntervalMs; this.proportionalControlFactor = proportionalControlFactor; this.targetLiveOffsetRebufferDeltaUs = targetLiveOffsetRebufferDeltaUs; + this.minPossibleLiveOffsetSmoothingFactor = minPossibleLiveOffsetSmoothingFactor; mediaConfigurationTargetLiveOffsetUs = C.TIME_UNSET; targetLiveOffsetOverrideUs = C.TIME_UNSET; minTargetLiveOffsetUs = C.TIME_UNSET; @@ -223,6 +267,8 @@ private DefaultLivePlaybackSpeedControl( lastPlaybackSpeedUpdateMs = C.TIME_UNSET; idealTargetLiveOffsetUs = C.TIME_UNSET; currentTargetLiveOffsetUs = C.TIME_UNSET; + smoothedMinPossibleLiveOffsetUs = C.TIME_UNSET; + smoothedMinPossibleLiveOffsetDeviationUs = C.TIME_UNSET; } @Override @@ -261,16 +307,20 @@ public void notifyRebuffer() { } @Override - public float getAdjustedPlaybackSpeed(long liveOffsetUs) { + public float getAdjustedPlaybackSpeed(long liveOffsetUs, long bufferedDurationUs) { if (mediaConfigurationTargetLiveOffsetUs == C.TIME_UNSET) { return 1f; } + + updateSmoothedMinPossibleLiveOffsetUs(liveOffsetUs, bufferedDurationUs); + if (lastPlaybackSpeedUpdateMs != C.TIME_UNSET && SystemClock.elapsedRealtime() - lastPlaybackSpeedUpdateMs < minUpdateIntervalMs) { return adjustedPlaybackSpeed; } lastPlaybackSpeedUpdateMs = SystemClock.elapsedRealtime(); + adjustTargetLiveOffsetUs(liveOffsetUs); long liveOffsetErrorUs = liveOffsetUs - currentTargetLiveOffsetUs; if (Math.abs(liveOffsetErrorUs) < MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED) { adjustedPlaybackSpeed = 1f; @@ -306,6 +356,67 @@ private void maybeResetTargetLiveOffsetUs() { } idealTargetLiveOffsetUs = idealOffsetUs; currentTargetLiveOffsetUs = idealOffsetUs; + smoothedMinPossibleLiveOffsetUs = C.TIME_UNSET; + smoothedMinPossibleLiveOffsetDeviationUs = C.TIME_UNSET; lastPlaybackSpeedUpdateMs = C.TIME_UNSET; } + + private void updateSmoothedMinPossibleLiveOffsetUs(long liveOffsetUs, long bufferedDurationUs) { + long minPossibleLiveOffsetUs = liveOffsetUs - bufferedDurationUs; + if (smoothedMinPossibleLiveOffsetUs == C.TIME_UNSET) { + smoothedMinPossibleLiveOffsetUs = minPossibleLiveOffsetUs; + smoothedMinPossibleLiveOffsetDeviationUs = 0; + } else { + // Use the maximum here to ensure we keep track of the upper bound of what is safely possible, + // not the average. + smoothedMinPossibleLiveOffsetUs = + max( + minPossibleLiveOffsetUs, + smooth( + smoothedMinPossibleLiveOffsetUs, + minPossibleLiveOffsetUs, + minPossibleLiveOffsetSmoothingFactor)); + long minPossibleLiveOffsetDeviationUs = + abs(minPossibleLiveOffsetUs - smoothedMinPossibleLiveOffsetUs); + smoothedMinPossibleLiveOffsetDeviationUs = + smooth( + smoothedMinPossibleLiveOffsetDeviationUs, + minPossibleLiveOffsetDeviationUs, + minPossibleLiveOffsetSmoothingFactor); + } + } + + private void adjustTargetLiveOffsetUs(long liveOffsetUs) { + // Stay in a safe distance (3 standard deviations = >99%) to the minimum possible live offset. + long safeOffsetUs = + smoothedMinPossibleLiveOffsetUs + 3 * smoothedMinPossibleLiveOffsetDeviationUs; + if (currentTargetLiveOffsetUs > safeOffsetUs) { + // There is room for decreasing the target offset towards the ideal or safe offset (whichever + // is larger). We want to limit the decrease so that the playback speed delta we achieve is + // the same as the maximum delta when slowing down towards the target. + long minUpdateIntervalUs = C.msToUs(minUpdateIntervalMs); + long decrementToOffsetCurrentSpeedUs = + (long) ((adjustedPlaybackSpeed - 1f) * minUpdateIntervalUs); + long decrementToIncreaseSpeedUs = (long) ((maxPlaybackSpeed - 1f) * minUpdateIntervalUs); + long maxDecrementUs = decrementToOffsetCurrentSpeedUs + decrementToIncreaseSpeedUs; + currentTargetLiveOffsetUs = + max(safeOffsetUs, idealTargetLiveOffsetUs, currentTargetLiveOffsetUs - maxDecrementUs); + } else { + // We'd like to reach a stable condition where the current live offset stays just below the + // safe offset. But don't increase the target offset to more than what would allow us to slow + // down gradually from the current offset. + long offsetWhenSlowingDownNowUs = + liveOffsetUs - (long) (max(0f, adjustedPlaybackSpeed - 1f) / proportionalControlFactor); + currentTargetLiveOffsetUs = + Util.constrainValue(offsetWhenSlowingDownNowUs, currentTargetLiveOffsetUs, safeOffsetUs); + if (maxTargetLiveOffsetUs != C.TIME_UNSET + && currentTargetLiveOffsetUs > maxTargetLiveOffsetUs) { + currentTargetLiveOffsetUs = maxTargetLiveOffsetUs; + } + } + } + + private static long smooth(long smoothedValue, long newValue, float smoothingFactor) { + return (long) (smoothingFactor * smoothedValue + (1f - smoothingFactor) * newValue); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 20a5201e0d5..f5cdcdc8c8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -872,7 +872,8 @@ private void updatePlaybackPositions() throws ExoPlaybackException { && isCurrentPeriodInMovingLiveWindow() && playbackInfo.playbackParameters.speed == 1f) { float adjustedSpeed = - livePlaybackSpeedControl.getAdjustedPlaybackSpeed(getCurrentLiveOffsetUs()); + livePlaybackSpeedControl.getAdjustedPlaybackSpeed( + getCurrentLiveOffsetUs(), getTotalBufferedDurationUs()); if (mediaClock.getPlaybackParameters().speed != adjustedSpeed) { mediaClock.setPlaybackParameters(playbackInfo.playbackParameters.withSpeed(adjustedSpeed)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/LivePlaybackSpeedControl.java b/library/core/src/main/java/com/google/android/exoplayer2/LivePlaybackSpeedControl.java index 8844c629087..6796185edd3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/LivePlaybackSpeedControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/LivePlaybackSpeedControl.java @@ -53,9 +53,10 @@ public interface LivePlaybackSpeedControl { * #getTargetLiveOffsetUs() target live offset}. * * @param liveOffsetUs The current live offset, in microseconds. + * @param bufferedDurationUs The duration of media that's currently buffered, in microseconds. * @return The adjusted playback speed. */ - float getAdjustedPlaybackSpeed(long liveOffsetUs); + float getAdjustedPlaybackSpeed(long liveOffsetUs, long bufferedDurationUs); /** * Returns the current target live offset, in microseconds, or {@link C#TIME_UNSET} if no target diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControlTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControlTest.java index 9c08b9999be..2e2731b0652 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControlTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedControlTest.java @@ -324,6 +324,172 @@ public void getTargetLiveOffsetUs_afterRepeatedNotifyRebuffer_returnsMaxLiveOffs assertThat(targetLiveOffsetUs).isEqualTo(39_000); } + @Test + public void + getTargetLiveOffsetUs_afterNotifyRebufferAndAdjustPlaybackSpeedWithLargeBufferedDuration_returnsDecreasedOffsetToIdealTarget() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder() + .setTargetLiveOffsetIncrementOnRebufferMs(3_000) + .setMinUpdateIntervalMs(100) + .build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration( + /* targetLiveOffsetMs= */ 42_000, + /* minLiveOffsetMs= */ 5_000, + /* maxLiveOffsetMs= */ 400_000, + /* minPlaybackSpeed= */ 0.9f, + /* maxPlaybackSpeed= */ 1.1f)); + + defaultLivePlaybackSpeedControl.notifyRebuffer(); + long targetLiveOffsetAfterRebufferUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 45_000_000, /* bufferedDurationUs= */ 9_000_000); + long targetLiveOffsetAfterOneAdjustmentUs = + defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + for (int i = 0; i < 500; i++) { + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 45_000_000, /* bufferedDurationUs= */ 9_000_000); + } + long targetLiveOffsetAfterManyAdjustmentsUs = + defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetAfterOneAdjustmentUs).isLessThan(targetLiveOffsetAfterRebufferUs); + assertThat(targetLiveOffsetAfterManyAdjustmentsUs) + .isLessThan(targetLiveOffsetAfterOneAdjustmentUs); + assertThat(targetLiveOffsetAfterManyAdjustmentsUs).isEqualTo(42_000_000); + } + + @Test + public void + getTargetLiveOffsetUs_afterNotifyRebufferAndAdjustPlaybackSpeedWithSmallBufferedDuration_returnsDecreasedOffsetToSafeTarget() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder() + .setTargetLiveOffsetIncrementOnRebufferMs(3_000) + .setMinUpdateIntervalMs(100) + .build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration( + /* targetLiveOffsetMs= */ 42_000, + /* minLiveOffsetMs= */ 5_000, + /* maxLiveOffsetMs= */ 400_000, + /* minPlaybackSpeed= */ 0.9f, + /* maxPlaybackSpeed= */ 1.1f)); + + defaultLivePlaybackSpeedControl.notifyRebuffer(); + long targetLiveOffsetAfterRebufferUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 45_000_000, /* bufferedDurationUs= */ 1_000_000); + long targetLiveOffsetAfterOneAdjustmentUs = + defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + for (int i = 0; i < 500; i++) { + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + long noiseUs = ((i % 10) - 5) * 1_000; + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 45_000_000, /* bufferedDurationUs= */ 1_000_000 + noiseUs); + } + long targetLiveOffsetAfterManyAdjustmentsUs = + defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetAfterOneAdjustmentUs).isLessThan(targetLiveOffsetAfterRebufferUs); + assertThat(targetLiveOffsetAfterManyAdjustmentsUs) + .isLessThan(targetLiveOffsetAfterOneAdjustmentUs); + // Should be at least be at the minimum buffered position. + assertThat(targetLiveOffsetAfterManyAdjustmentsUs).isGreaterThan(44_005_000); + } + + @Test + public void + getTargetLiveOffsetUs_afterAdjustPlaybackSpeedWithLiveOffsetAroundCurrentTarget_returnsSafeTarget() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder().build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration( + /* targetLiveOffsetMs= */ 42_000, + /* minLiveOffsetMs= */ 5_000, + /* maxLiveOffsetMs= */ 400_000, + /* minPlaybackSpeed= */ 0.9f, + /* maxPlaybackSpeed= */ 1.1f)); + + long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + // Pretend to have a buffered duration at around the target duration with some artificial noise. + for (int i = 0; i < 500; i++) { + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + long noiseUs = ((i % 10) - 5) * 1_000; + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 49_000_000, /* bufferedDurationUs= */ 7_000_000 + noiseUs); + } + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 49_000_000, /* bufferedDurationUs= */ 7_000_000); + long targetLiveOffsetAfterUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetBeforeUs).isEqualTo(42_000_000); + assertThat(targetLiveOffsetAfterUs).isGreaterThan(42_005_000); + } + + @Test + public void + getTargetLiveOffsetUs_afterAdjustPlaybackSpeedAndSmoothingFactorOfZero_ignoresSafeTargetAndReturnsCurrentTarget() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder() + .setMinPossibleLiveOffsetSmoothingFactor(0f) + .build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration( + /* targetLiveOffsetMs= */ 42_000, + /* minLiveOffsetMs= */ 5_000, + /* maxLiveOffsetMs= */ 400_000, + /* minPlaybackSpeed= */ 0.9f, + /* maxPlaybackSpeed= */ 1.1f)); + + long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + // Pretend to have a buffered duration at around the target duration with some artificial noise. + for (int i = 0; i < 500; i++) { + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + long noiseUs = ((i % 10) - 5) * 1_000; + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 49_000_000, /* bufferedDurationUs= */ 7_000_000 + noiseUs); + } + ShadowSystemClock.advanceBy(Duration.ofMillis(100)); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 49_000_000, /* bufferedDurationUs= */ 7_000_000); + long targetLiveOffsetAfterUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetBeforeUs).isEqualTo(42_000_000); + // Despite the noise indicating it's unsafe here, we still return the target offset. + assertThat(targetLiveOffsetAfterUs).isEqualTo(42_000_000); + } + + @Test + public void + getTargetLiveOffsetUs_afterAdjustPlaybackSpeedWithLiveOffsetLessThanCurrentTarget_returnsCurrentTarget() { + DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = + new DefaultLivePlaybackSpeedControl.Builder() + .setTargetLiveOffsetIncrementOnRebufferMs(3_000) + .setMinUpdateIntervalMs(100) + .build(); + defaultLivePlaybackSpeedControl.setLiveConfiguration( + new LiveConfiguration( + /* targetLiveOffsetMs= */ 42_000, + /* minLiveOffsetMs= */ 5_000, + /* maxLiveOffsetMs= */ 400_000, + /* minPlaybackSpeed= */ 0.9f, + /* maxPlaybackSpeed= */ 1.1f)); + + long targetLiveOffsetBeforeUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 39_000_000, /* bufferedDurationUs= */ 1_000_000); + long targetLiveOffsetAfterUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs(); + + assertThat(targetLiveOffsetBeforeUs).isEqualTo(42_000_000); + assertThat(targetLiveOffsetAfterUs).isEqualTo(42_000_000); + } + @Test public void adjustPlaybackSpeed_liveOffsetMatchesTargetOffset_returnsUnitSpeed() { DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl = @@ -337,7 +503,8 @@ public void adjustPlaybackSpeed_liveOffsetMatchesTargetOffset_returnsUnitSpeed() /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_000_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_000_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed).isEqualTo(1f); } @@ -358,12 +525,14 @@ public void adjustPlaybackSpeed_liveOffsetWithinAcceptableErrorMargin_returnsUni defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( /* liveOffsetUs= */ 2_000_000 - DefaultLivePlaybackSpeedControl.MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED - + 1); + + 1, + /* bufferedDurationUs= */ 1_000_000); float adjustedSpeedJustBelowUpperErrorMargin = defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( /* liveOffsetUs= */ 2_000_000 + DefaultLivePlaybackSpeedControl.MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED - - 1); + - 1, + /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeedJustAboveLowerErrorMargin).isEqualTo(1f); assertThat(adjustedSpeedJustBelowUpperErrorMargin).isEqualTo(1f); @@ -382,7 +551,8 @@ public void adjustPlaybackSpeed_withLiveOffsetGreaterThanTargetOffset_returnsAdj /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); float expectedSpeedAccordingToDocumentation = 1f + 0.01f * (2.5f - 2f); assertThat(adjustedSpeed).isEqualTo(expectedSpeedAccordingToDocumentation); @@ -403,7 +573,8 @@ public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjus /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); float expectedSpeedAccordingToDocumentation = 1f + 0.01f * (1.5f - 2f); assertThat(adjustedSpeed).isEqualTo(expectedSpeedAccordingToDocumentation); @@ -425,7 +596,7 @@ public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjus float adjustedSpeed = defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( - /* liveOffsetUs= */ 999_999_999_999L); + /* liveOffsetUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L); assertThat(adjustedSpeed).isEqualTo(1.5f); } @@ -445,7 +616,7 @@ public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjus float adjustedSpeed = defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( - /* liveOffsetUs= */ -999_999_999_999L); + /* liveOffsetUs= */ -999_999_999_999L, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed).isEqualTo(0.5f); } @@ -465,7 +636,7 @@ public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjus float adjustedSpeed = defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( - /* liveOffsetUs= */ 999_999_999_999L); + /* liveOffsetUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L); assertThat(adjustedSpeed).isEqualTo(2f); } @@ -485,7 +656,7 @@ public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjus float adjustedSpeed = defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( - /* liveOffsetUs= */ -999_999_999_999L); + /* liveOffsetUs= */ -999_999_999_999L, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed).isEqualTo(0.2f); } @@ -503,13 +674,16 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed1 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); ShadowSystemClock.advanceBy(Duration.ofMillis(122)); float adjustedSpeed2 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); ShadowSystemClock.advanceBy(Duration.ofMillis(2)); float adjustedSpeed3 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed1).isEqualTo(adjustedSpeed2); assertThat(adjustedSpeed3).isNotEqualTo(adjustedSpeed2); @@ -529,7 +703,8 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed1 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); defaultLivePlaybackSpeedControl.setLiveConfiguration( new LiveConfiguration( /* targetLiveOffsetMs= */ 2_000, @@ -538,7 +713,8 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* minPlaybackSpeed= */ C.RATE_UNSET, /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed2 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed1).isEqualTo(adjustedSpeed2); } @@ -557,7 +733,8 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed1 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); defaultLivePlaybackSpeedControl.setLiveConfiguration( new LiveConfiguration( /* targetLiveOffsetMs= */ 1_000, @@ -566,7 +743,8 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* minPlaybackSpeed= */ C.RATE_UNSET, /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed2 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed1).isNotEqualTo(adjustedSpeed2); } @@ -585,10 +763,12 @@ public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameA /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed1 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); defaultLivePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(2_000_001); float adjustedSpeed2 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed1).isNotEqualTo(adjustedSpeed2); } @@ -606,10 +786,12 @@ public void adjustPlaybackSpeed_repeatedCallAfterNotifyRebuffer_updatesSpeedAgai /* maxPlaybackSpeed= */ C.RATE_UNSET)); float adjustedSpeed1 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 1_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 1_500_000, /* bufferedDurationUs= */ 1_000_000); defaultLivePlaybackSpeedControl.notifyRebuffer(); float adjustedSpeed2 = - defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed(/* liveOffsetUs= */ 2_500_000); + defaultLivePlaybackSpeedControl.getAdjustedPlaybackSpeed( + /* liveOffsetUs= */ 2_500_000, /* bufferedDurationUs= */ 1_000_000); assertThat(adjustedSpeed1).isNotEqualTo(adjustedSpeed2); }