Add LivePlaybackSpeedControl interface and default implementation.

Issue: #4904
PiperOrigin-RevId: 336839908
This commit is contained in:
christosts 2020-10-13 10:50:49 +01:00 committed by kim-vde
parent 13f1f3b3eb
commit 867d27fa72
3 changed files with 596 additions and 0 deletions

View File

@ -0,0 +1,237 @@
/*
* 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 android.os.SystemClock;
import com.google.android.exoplayer2.MediaItem.LiveConfiguration;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/**
* 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 marginally close to {@code targetLiveOffsetSec}, i.e. {@code
* |currentLiveOffsetSec - targetLiveOffsetSec| <= MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED}.
*
* <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.
*/
public final class DefaultLivePlaybackSpeedControl implements LivePlaybackSpeedControl {
/**
* The default minimum playback speed 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 playback speed 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 = 500;
/**
* The default {@link Builder#setProportionalControlFactor(float) proportional control factor}
* used to adjust the playback speed.
*/
public static final float DEFAULT_PROPORTIONAL_CONTROL_FACTOR = 0.05f;
/**
* The maximum difference between the current live offset and the target live offset for which
* unit speed (1.0f) is used.
*/
public static final long MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED = 5_000;
/** Builder for a {@link DefaultLivePlaybackSpeedControl}. */
public static final class Builder {
private float fallbackMinPlaybackSpeed;
private float fallbackMaxPlaybackSpeed;
private long minUpdateIntervalMs;
private float proportionalControlFactorUs;
/** 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;
}
/**
* 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 playback speed.
* @return This builder, for convenience.
*/
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 playback speed.
* @return This builder, for convenience.
*/
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.
*/
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 adjusted playback speed 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.
*/
public Builder setProportionalControlFactor(float proportionalControlFactor) {
Assertions.checkArgument(proportionalControlFactor > 0);
this.proportionalControlFactorUs = proportionalControlFactor / C.MICROS_PER_SECOND;
return this;
}
/** Builds an instance. */
public DefaultLivePlaybackSpeedControl build() {
return new DefaultLivePlaybackSpeedControl(
fallbackMinPlaybackSpeed,
fallbackMaxPlaybackSpeed,
minUpdateIntervalMs,
proportionalControlFactorUs);
}
}
private final float fallbackMinPlaybackSpeed;
private final float fallbackMaxPlaybackSpeed;
private final long minUpdateIntervalMs;
private final float proportionalControlFactor;
private LiveConfiguration mediaConfiguration;
private long targetLiveOffsetOverrideUs;
private float adjustedPlaybackSpeed;
private long lastPlaybackSpeedUpdateMs;
private DefaultLivePlaybackSpeedControl(
float fallbackMinPlaybackSpeed,
float fallbackMaxPlaybackSpeed,
long minUpdateIntervalMs,
float proportionalControlFactor) {
this.fallbackMinPlaybackSpeed = fallbackMinPlaybackSpeed;
this.fallbackMaxPlaybackSpeed = fallbackMaxPlaybackSpeed;
this.minUpdateIntervalMs = minUpdateIntervalMs;
this.proportionalControlFactor = proportionalControlFactor;
mediaConfiguration = LiveConfiguration.UNSET;
targetLiveOffsetOverrideUs = C.TIME_UNSET;
adjustedPlaybackSpeed = 1.0f;
lastPlaybackSpeedUpdateMs = C.TIME_UNSET;
}
@Override
public void updateLiveConfiguration(LiveConfiguration liveConfiguration) {
this.mediaConfiguration = liveConfiguration;
lastPlaybackSpeedUpdateMs = C.TIME_UNSET;
}
@Override
public void overrideTargetLiveOffsetUs(long liveOffsetUs) {
this.targetLiveOffsetOverrideUs = liveOffsetUs;
lastPlaybackSpeedUpdateMs = C.TIME_UNSET;
}
@Override
public float adjustPlaybackSpeed(long liveOffsetUs) {
long targetLiveOffsetUs = getTargetLiveOffsetUs();
if (targetLiveOffsetUs == C.TIME_UNSET) {
return 1f;
}
if (lastPlaybackSpeedUpdateMs != C.TIME_UNSET
&& SystemClock.elapsedRealtime() - lastPlaybackSpeedUpdateMs < minUpdateIntervalMs) {
return adjustedPlaybackSpeed;
}
lastPlaybackSpeedUpdateMs = SystemClock.elapsedRealtime();
long liveOffsetErrorUs = liveOffsetUs - targetLiveOffsetUs;
if (Math.abs(liveOffsetErrorUs) < MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED) {
adjustedPlaybackSpeed = 1f;
} else {
float calculatedSpeed = 1f + proportionalControlFactor * liveOffsetErrorUs;
adjustedPlaybackSpeed =
Util.constrainValue(calculatedSpeed, getMinPlaybackSpeed(), getMaxPlaybackSpeed());
}
return adjustedPlaybackSpeed;
}
@Override
public long getTargetLiveOffsetUs() {
return targetLiveOffsetOverrideUs != C.TIME_UNSET
&& mediaConfiguration.targetLiveOffsetMs != C.TIME_UNSET
? targetLiveOffsetOverrideUs
: C.msToUs(mediaConfiguration.targetLiveOffsetMs);
}
private float getMinPlaybackSpeed() {
return mediaConfiguration.minPlaybackSpeed != C.RATE_UNSET
? mediaConfiguration.minPlaybackSpeed
: fallbackMinPlaybackSpeed;
}
private float getMaxPlaybackSpeed() {
return mediaConfiguration.maxPlaybackSpeed != C.RATE_UNSET
? mediaConfiguration.maxPlaybackSpeed
: fallbackMaxPlaybackSpeed;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 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 com.google.android.exoplayer2.MediaItem.LiveConfiguration;
/**
* Controls the playback speed while playing live content in order to maintain a steady target live
* offset.
*/
public interface LivePlaybackSpeedControl {
/**
* Updates the live configuration defined by the media.
*
* @param liveConfiguration The {@link LiveConfiguration} as defined by the media.
*/
void updateLiveConfiguration(LiveConfiguration liveConfiguration);
/**
* Overrides the {@link #updateLiveConfiguration configured} target live offset in microseconds,
* or {@code C.TIME_UNSET} to delete a previous override.
*
* <p>If no target live offset is configured by {@link #updateLiveConfiguration}, this override
* has no effect.
*/
void overrideTargetLiveOffsetUs(long liveOffsetUs);
/**
* Returns the adjusted playback speed in order get closer towards the {@link
* #getTargetLiveOffsetUs() target live offset}.
*
* @param liveOffsetUs The current live offset, in microseconds.
* @return The adjusted playback speed.
*/
float adjustPlaybackSpeed(long liveOffsetUs);
/**
* Returns the current target live offset, in microseconds, or {@link C#TIME_UNSET} if no target
* live offset is defined for the current media.
*/
long getTargetLiveOffsetUs();
}

View File

@ -0,0 +1,303 @@
/*
* 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.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.MediaItem.LiveConfiguration;
import java.time.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.ShadowSystemClock;
/** Unit test for {@link DefaultLivePlaybackSpeedControl}. */
@RunWith(AndroidJUnit4.class)
public class DefaultLivePlaybackSpeedControlTest {
@Test
public void getTargetLiveOffsetUs_returnsUnset() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(C.TIME_UNSET);
}
@Test
public void getTargetLiveOffsetUs_afterUpdateLiveConfiguration_usesMediaLiveOffset() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 42, /* minPlaybackSpeed= */ 1f, /* maxPlaybackSpeed= */ 1f));
assertThat(defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs()).isEqualTo(42_000);
}
@Test
public void getTargetLiveOffsetUs_withOverrideTargetLiveOffsetUs_usesOverride() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(123_456_789);
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 42, /* minPlaybackSpeed= */ 1f, /* maxPlaybackSpeed= */ 1f));
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
assertThat(targetLiveOffsetUs).isEqualTo(123_456_789);
}
@Test
public void
getTargetLiveOffsetUs_afterOverrideTargetLiveOffset_withoutMediaConfiguration_returnsUnset() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(123_456_789);
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
assertThat(targetLiveOffsetUs).isEqualTo(C.TIME_UNSET);
}
@Test
public void
getTargetLiveOffsetUs_afterOverrideTargetLiveOffsetUsWithTimeUnset_usesMediaLiveOffset() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(123_456_789);
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 42, /* minPlaybackSpeed= */ 1f, /* maxPlaybackSpeed= */ 1f));
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(C.TIME_UNSET);
long targetLiveOffsetUs = defaultLivePlaybackSpeedControl.getTargetLiveOffsetUs();
assertThat(targetLiveOffsetUs).isEqualTo(42_000);
}
@Test
public void adjustPlaybackSpeed_liveOffsetMatchesTargetOffset_returnsUnitSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_000_000);
assertThat(adjustedSpeed).isEqualTo(1f);
}
@Test
public void adjustPlaybackSpeed_liveOffsetWithinAcceptableErrorMargin_returnsUnitSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeedJustAboveLowerErrorMargin =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(
/* liveOffsetUs= */ 2_000_000
- DefaultLivePlaybackSpeedControl.MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED
+ 1);
float adjustedSpeedJustBelowUpperErrorMargin =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(
/* liveOffsetUs= */ 2_000_000
+ DefaultLivePlaybackSpeedControl.MAXIMUM_LIVE_OFFSET_ERROR_US_FOR_UNIT_SPEED
- 1);
assertThat(adjustedSpeedJustAboveLowerErrorMargin).isEqualTo(1f);
assertThat(adjustedSpeedJustBelowUpperErrorMargin).isEqualTo(1f);
}
@Test
public void adjustPlaybackSpeed_withLiveOffsetGreaterThanTargetOffset_returnsAdjustedSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setProportionalControlFactor(0.01f).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_500_000);
float expectedSpeedAccordingToDocumentation = 1f + 0.01f * (2.5f - 2f);
assertThat(adjustedSpeed).isEqualTo(expectedSpeedAccordingToDocumentation);
assertThat(adjustedSpeed).isGreaterThan(1f);
}
@Test
public void adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_returnsAdjustedSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setProportionalControlFactor(0.01f).build();
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(2_000_000);
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 1_500_000);
float expectedSpeedAccordingToDocumentation = 1f + 0.01f * (1.5f - 2f);
assertThat(adjustedSpeed).isEqualTo(expectedSpeedAccordingToDocumentation);
assertThat(adjustedSpeed).isLessThan(1f);
}
@Test
public void
adjustPlaybackSpeed_withLiveOffsetGreaterThanTargetOffset_clampedToFallbackMaximumSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setFallbackMaxPlaybackSpeed(1.5f).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 999_999_999_999L);
assertThat(adjustedSpeed).isEqualTo(1.5f);
}
@Test
public void
adjustPlaybackSpeed_withLiveOffsetLowerThanTargetOffset_clampedToFallbackMinimumSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setFallbackMinPlaybackSpeed(0.5f).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ -999_999_999_999L);
assertThat(adjustedSpeed).isEqualTo(0.5f);
}
@Test
public void
adjustPlaybackSpeed_andMediaProvidedMaxSpeedWithLiveOffsetGreaterThanTargetOffset_clampedToMediaMaxSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setFallbackMaxPlaybackSpeed(1.5f).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ 2f));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 999_999_999_999L);
assertThat(adjustedSpeed).isEqualTo(2f);
}
@Test
public void
adjustPlaybackSpeed_andMediaProvidedMinSpeedWithLiveOffsetLowerThanTargetOffset_clampedToMediaMinSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setFallbackMinPlaybackSpeed(0.5f).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ 0.2f,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ -999_999_999_999L);
assertThat(adjustedSpeed).isEqualTo(0.2f);
}
@Test
public void adjustPlaybackSpeed_repeatedCallWithinMinUpdateInterval_returnsSameAdjustedSpeed() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setMinUpdateIntervalMs(123).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed1 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 1_500_000);
ShadowSystemClock.advanceBy(Duration.ofMillis(122));
float adjustedSpeed2 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_500_000);
ShadowSystemClock.advanceBy(Duration.ofMillis(2));
float adjustedSpeed3 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_500_000);
assertThat(adjustedSpeed1).isEqualTo(adjustedSpeed2);
assertThat(adjustedSpeed3).isNotEqualTo(adjustedSpeed2);
}
@Test
public void adjustPlaybackSpeed_repeatedCallAfterUpdateLiveConfiguration_updatesSpeedAgain() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setMinUpdateIntervalMs(123).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed1 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 1_500_000);
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed2 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_500_000);
assertThat(adjustedSpeed1).isNotEqualTo(adjustedSpeed2);
}
@Test
public void adjustPlaybackSpeed_repeatedCallAfterNewTargetLiveOffset_updatesSpeedAgain() {
DefaultLivePlaybackSpeedControl defaultLivePlaybackSpeedControl =
new DefaultLivePlaybackSpeedControl.Builder().setMinUpdateIntervalMs(123).build();
defaultLivePlaybackSpeedControl.updateLiveConfiguration(
new LiveConfiguration(
/* targetLiveOffsetMs= */ 2_000,
/* minPlaybackSpeed= */ C.RATE_UNSET,
/* maxPlaybackSpeed= */ C.RATE_UNSET));
float adjustedSpeed1 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 1_500_000);
defaultLivePlaybackSpeedControl.overrideTargetLiveOffsetUs(2_000_001);
float adjustedSpeed2 =
defaultLivePlaybackSpeedControl.adjustPlaybackSpeed(/* liveOffsetUs= */ 2_500_000);
assertThat(adjustedSpeed1).isNotEqualTo(adjustedSpeed2);
}
}