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