NekoX/TMessagesProj/src/main/java/com/google/android/exoplayer2/DefaultLivePlaybackSpeedCon...

459 lines
19 KiB
Java

/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import com.google.android.exoplayer2.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/**
* A {@link LivePlaybackSpeedControl} that adjusts the playback speed using a proportional
* controller.
*
* <p>The control mechanism calculates the adjusted speed as {@code 1.0 + proportionalControlFactor
* x (currentLiveOffsetSec - targetLiveOffsetSec)}. Unit speed (1.0f) is used, if the {@code
* currentLiveOffsetSec} is closer to {@code targetLiveOffsetSec} than the value set with {@link
* Builder#setMaxLiveOffsetErrorMsForUnitSpeed(long)}.
*
* <p>The resulting speed is clamped to a minimum and maximum speed defined by the media, the
* fallback values set with {@link Builder#setFallbackMinPlaybackSpeed(float)} and {@link
* Builder#setFallbackMaxPlaybackSpeed(float)} or the {@link #DEFAULT_FALLBACK_MIN_PLAYBACK_SPEED
* minimum} and {@link #DEFAULT_FALLBACK_MAX_PLAYBACK_SPEED maximum} fallback default values.
*
* <p>When the player rebuffers, the target live offset {@link
* Builder#setTargetLiveOffsetIncrementOnRebufferMs(long) is increased} to adjust to the reduced
* 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 {
/**
* The default minimum factor by which playback can be sped up that should be used if no minimum
* playback speed is defined by the media.
*/
public static final float DEFAULT_FALLBACK_MIN_PLAYBACK_SPEED = 0.97f;
/**
* The default maximum factor by which playback can be sped up that should be used if no maximum
* playback speed is defined by the media.
*/
public static final float DEFAULT_FALLBACK_MAX_PLAYBACK_SPEED = 1.03f;
/**
* The default {@link Builder#setMinUpdateIntervalMs(long) minimum interval} between playback
* speed changes, in milliseconds.
*/
public static final long DEFAULT_MIN_UPDATE_INTERVAL_MS = 1_000;
/**
* The default {@link Builder#setProportionalControlFactor(float) proportional control factor}
* used to adjust the playback speed.
*/
public static final float DEFAULT_PROPORTIONAL_CONTROL_FACTOR = 0.1f;
/**
* The default increment applied to the target live offset each time the player is rebuffering, in
* milliseconds
*/
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 default maximum difference between the current live offset and the target live offset, in
* milliseconds, for which unit speed (1.0f) is used.
*/
public static final long DEFAULT_MAX_LIVE_OFFSET_ERROR_MS_FOR_UNIT_SPEED = 20;
/** Builder for a {@link DefaultLivePlaybackSpeedControl}. */
public static final class Builder {
private float fallbackMinPlaybackSpeed;
private float fallbackMaxPlaybackSpeed;
private long minUpdateIntervalMs;
private float proportionalControlFactorUs;
private long maxLiveOffsetErrorUsForUnitSpeed;
private long targetLiveOffsetIncrementOnRebufferUs;
private float minPossibleLiveOffsetSmoothingFactor;
/** Creates a builder. */
public Builder() {
fallbackMinPlaybackSpeed = DEFAULT_FALLBACK_MIN_PLAYBACK_SPEED;
fallbackMaxPlaybackSpeed = DEFAULT_FALLBACK_MAX_PLAYBACK_SPEED;
minUpdateIntervalMs = DEFAULT_MIN_UPDATE_INTERVAL_MS;
proportionalControlFactorUs = DEFAULT_PROPORTIONAL_CONTROL_FACTOR / C.MICROS_PER_SECOND;
maxLiveOffsetErrorUsForUnitSpeed =
Util.msToUs(DEFAULT_MAX_LIVE_OFFSET_ERROR_MS_FOR_UNIT_SPEED);
targetLiveOffsetIncrementOnRebufferUs =
Util.msToUs(DEFAULT_TARGET_LIVE_OFFSET_INCREMENT_ON_REBUFFER_MS);
minPossibleLiveOffsetSmoothingFactor = DEFAULT_MIN_POSSIBLE_LIVE_OFFSET_SMOOTHING_FACTOR;
}
/**
* Sets the minimum playback speed that should be used if no minimum playback speed is defined
* by the media.
*
* <p>The default is {@link #DEFAULT_FALLBACK_MIN_PLAYBACK_SPEED}.
*
* @param fallbackMinPlaybackSpeed The fallback minimum factor by which playback can be sped up.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setFallbackMinPlaybackSpeed(float fallbackMinPlaybackSpeed) {
Assertions.checkArgument(0 < fallbackMinPlaybackSpeed && fallbackMinPlaybackSpeed <= 1f);
this.fallbackMinPlaybackSpeed = fallbackMinPlaybackSpeed;
return this;
}
/**
* Sets the maximum playback speed that should be used if no maximum playback speed is defined
* by the media.
*
* <p>The default is {@link #DEFAULT_FALLBACK_MAX_PLAYBACK_SPEED}.
*
* @param fallbackMaxPlaybackSpeed The fallback maximum factor by which playback can be sped up.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setFallbackMaxPlaybackSpeed(float fallbackMaxPlaybackSpeed) {
Assertions.checkArgument(fallbackMaxPlaybackSpeed >= 1f);
this.fallbackMaxPlaybackSpeed = fallbackMaxPlaybackSpeed;
return this;
}
/**
* Sets the minimum interval between playback speed changes, in milliseconds.
*
* <p>The default is {@link #DEFAULT_MIN_UPDATE_INTERVAL_MS}.
*
* @param minUpdateIntervalMs The minimum interval between playback speed changes, in
* milliseconds.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setMinUpdateIntervalMs(long minUpdateIntervalMs) {
Assertions.checkArgument(minUpdateIntervalMs > 0);
this.minUpdateIntervalMs = minUpdateIntervalMs;
return this;
}
/**
* Sets the proportional control factor used to adjust the playback speed.
*
* <p>The factor by which playback will be sped up is calculated as {@code 1.0 +
* proportionalControlFactor x (currentLiveOffsetSec - targetLiveOffsetSec)}.
*
* <p>The default is {@link #DEFAULT_PROPORTIONAL_CONTROL_FACTOR}.
*
* @param proportionalControlFactor The proportional control factor used to adjust the playback
* speed.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setProportionalControlFactor(float proportionalControlFactor) {
Assertions.checkArgument(proportionalControlFactor > 0);
this.proportionalControlFactorUs = proportionalControlFactor / C.MICROS_PER_SECOND;
return this;
}
/**
* Sets the maximum difference between the current live offset and the target live offset, in
* milliseconds, for which unit speed (1.0f) is used.
*
* <p>The default is {@link #DEFAULT_MAX_LIVE_OFFSET_ERROR_MS_FOR_UNIT_SPEED}.
*
* @param maxLiveOffsetErrorMsForUnitSpeed The maximum live offset error for which unit speed is
* used, in milliseconds.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setMaxLiveOffsetErrorMsForUnitSpeed(long maxLiveOffsetErrorMsForUnitSpeed) {
Assertions.checkArgument(maxLiveOffsetErrorMsForUnitSpeed > 0);
this.maxLiveOffsetErrorUsForUnitSpeed = Util.msToUs(maxLiveOffsetErrorMsForUnitSpeed);
return this;
}
/**
* Sets the increment applied to the target live offset each time the player is rebuffering, in
* milliseconds.
*
* @param targetLiveOffsetIncrementOnRebufferMs The increment applied to the target live offset
* when the player is rebuffering, in milliseconds
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setTargetLiveOffsetIncrementOnRebufferMs(
long targetLiveOffsetIncrementOnRebufferMs) {
Assertions.checkArgument(targetLiveOffsetIncrementOnRebufferMs >= 0);
this.targetLiveOffsetIncrementOnRebufferUs =
Util.msToUs(targetLiveOffsetIncrementOnRebufferMs);
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 &ge; 0 and &lt; 1.
* @return This builder, for convenience.
*/
@CanIgnoreReturnValue
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(
fallbackMinPlaybackSpeed,
fallbackMaxPlaybackSpeed,
minUpdateIntervalMs,
proportionalControlFactorUs,
maxLiveOffsetErrorUsForUnitSpeed,
targetLiveOffsetIncrementOnRebufferUs,
minPossibleLiveOffsetSmoothingFactor);
}
}
private final float fallbackMinPlaybackSpeed;
private final float fallbackMaxPlaybackSpeed;
private final long minUpdateIntervalMs;
private final float proportionalControlFactor;
private final long maxLiveOffsetErrorUsForUnitSpeed;
private final long targetLiveOffsetRebufferDeltaUs;
private final float minPossibleLiveOffsetSmoothingFactor;
private long mediaConfigurationTargetLiveOffsetUs;
private long targetLiveOffsetOverrideUs;
private long idealTargetLiveOffsetUs;
private long minTargetLiveOffsetUs;
private long maxTargetLiveOffsetUs;
private long currentTargetLiveOffsetUs;
private float maxPlaybackSpeed;
private float minPlaybackSpeed;
private float adjustedPlaybackSpeed;
private long lastPlaybackSpeedUpdateMs;
private long smoothedMinPossibleLiveOffsetUs;
private long smoothedMinPossibleLiveOffsetDeviationUs;
private DefaultLivePlaybackSpeedControl(
float fallbackMinPlaybackSpeed,
float fallbackMaxPlaybackSpeed,
long minUpdateIntervalMs,
float proportionalControlFactor,
long maxLiveOffsetErrorUsForUnitSpeed,
long targetLiveOffsetRebufferDeltaUs,
float minPossibleLiveOffsetSmoothingFactor) {
this.fallbackMinPlaybackSpeed = fallbackMinPlaybackSpeed;
this.fallbackMaxPlaybackSpeed = fallbackMaxPlaybackSpeed;
this.minUpdateIntervalMs = minUpdateIntervalMs;
this.proportionalControlFactor = proportionalControlFactor;
this.maxLiveOffsetErrorUsForUnitSpeed = maxLiveOffsetErrorUsForUnitSpeed;
this.targetLiveOffsetRebufferDeltaUs = targetLiveOffsetRebufferDeltaUs;
this.minPossibleLiveOffsetSmoothingFactor = minPossibleLiveOffsetSmoothingFactor;
mediaConfigurationTargetLiveOffsetUs = C.TIME_UNSET;
targetLiveOffsetOverrideUs = C.TIME_UNSET;
minTargetLiveOffsetUs = C.TIME_UNSET;
maxTargetLiveOffsetUs = C.TIME_UNSET;
minPlaybackSpeed = fallbackMinPlaybackSpeed;
maxPlaybackSpeed = fallbackMaxPlaybackSpeed;
adjustedPlaybackSpeed = 1.0f;
lastPlaybackSpeedUpdateMs = C.TIME_UNSET;
idealTargetLiveOffsetUs = C.TIME_UNSET;
currentTargetLiveOffsetUs = C.TIME_UNSET;
smoothedMinPossibleLiveOffsetUs = C.TIME_UNSET;
smoothedMinPossibleLiveOffsetDeviationUs = C.TIME_UNSET;
}
@Override
public void setLiveConfiguration(LiveConfiguration liveConfiguration) {
mediaConfigurationTargetLiveOffsetUs = Util.msToUs(liveConfiguration.targetOffsetMs);
minTargetLiveOffsetUs = Util.msToUs(liveConfiguration.minOffsetMs);
maxTargetLiveOffsetUs = Util.msToUs(liveConfiguration.maxOffsetMs);
minPlaybackSpeed =
liveConfiguration.minPlaybackSpeed != C.RATE_UNSET
? liveConfiguration.minPlaybackSpeed
: fallbackMinPlaybackSpeed;
maxPlaybackSpeed =
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();
}
@Override
public void setTargetLiveOffsetOverrideUs(long liveOffsetUs) {
targetLiveOffsetOverrideUs = liveOffsetUs;
maybeResetTargetLiveOffsetUs();
}
@Override
public void notifyRebuffer() {
if (currentTargetLiveOffsetUs == C.TIME_UNSET) {
return;
}
currentTargetLiveOffsetUs += targetLiveOffsetRebufferDeltaUs;
if (maxTargetLiveOffsetUs != C.TIME_UNSET
&& currentTargetLiveOffsetUs > maxTargetLiveOffsetUs) {
currentTargetLiveOffsetUs = maxTargetLiveOffsetUs;
}
lastPlaybackSpeedUpdateMs = C.TIME_UNSET;
}
@Override
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) < maxLiveOffsetErrorUsForUnitSpeed) {
adjustedPlaybackSpeed = 1f;
} else {
float calculatedSpeed = 1f + proportionalControlFactor * liveOffsetErrorUs;
adjustedPlaybackSpeed =
Util.constrainValue(calculatedSpeed, minPlaybackSpeed, maxPlaybackSpeed);
}
return adjustedPlaybackSpeed;
}
@Override
public long getTargetLiveOffsetUs() {
return currentTargetLiveOffsetUs;
}
private void maybeResetTargetLiveOffsetUs() {
long idealOffsetUs = C.TIME_UNSET;
if (mediaConfigurationTargetLiveOffsetUs != C.TIME_UNSET) {
idealOffsetUs =
targetLiveOffsetOverrideUs != C.TIME_UNSET
? targetLiveOffsetOverrideUs
: mediaConfigurationTargetLiveOffsetUs;
if (minTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs < minTargetLiveOffsetUs) {
idealOffsetUs = minTargetLiveOffsetUs;
}
if (maxTargetLiveOffsetUs != C.TIME_UNSET && idealOffsetUs > maxTargetLiveOffsetUs) {
idealOffsetUs = maxTargetLiveOffsetUs;
}
}
if (idealTargetLiveOffsetUs == idealOffsetUs) {
return;
}
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 = Util.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);
}
}