mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
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
This commit is contained in:
parent
b03df4e8b5
commit
86ae7ebac4
@ -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 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*
|
||||
* <p>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 final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
private long minUpdateIntervalMs;
|
||||
private float proportionalControlFactorUs;
|
||||
private long targetLiveOffsetIncrementOnRebufferUs;
|
||||
private float minPossibleLiveOffsetSmoothingFactor;
|
||||
|
||||
/** Creates a builder. */
|
||||
public Builder() {
|
||||
@ -93,6 +107,7 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
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 final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the smoothing factor when smoothing the minimum possible live offset that can be
|
||||
* achieved during playback.
|
||||
*
|
||||
* <p>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 final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
fallbackMaxPlaybackSpeed,
|
||||
minUpdateIntervalMs,
|
||||
proportionalControlFactorUs,
|
||||
targetLiveOffsetIncrementOnRebufferUs);
|
||||
targetLiveOffsetIncrementOnRebufferUs,
|
||||
minPossibleLiveOffsetSmoothingFactor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +227,7 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
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 final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
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 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
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 final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
}
|
||||
|
||||
@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 @@ public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedC
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -872,7 +872,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
&& 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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -324,6 +324,172 @@ public class DefaultLivePlaybackSpeedControlTest {
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
|
||||
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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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 class DefaultLivePlaybackSpeedControlTest {
|
||||
/* 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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user