Skip to content

Commit

Permalink
Add targetLiveOffsetUs parameter to LoadControl.shouldStartPlayback
Browse files Browse the repository at this point in the history
This allows a LoadControl to start playback earlier if the target
live offset is very low.

Issue: #4904
PiperOrigin-RevId: 336863824
  • Loading branch information
christosts authored and kim-vde committed Oct 13, 2020
1 parent 76b7f76 commit 8fdadad
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 36 deletions.
16 changes: 9 additions & 7 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
### dev-v2 (not yet released)

* Core library:
* `LoadControl`:
* Add a `targetLiveOffsetUs` parameter to `shouldStartPlayback`.
* Fix bug where streams with highly uneven durations may get stuck in a
buffering state
([#7943](https://github.com/google/ExoPlayer/issues/7943)).
Expand All @@ -12,8 +14,8 @@
([#4463](https://github.com/google/ExoPlayer/issues/4463)).
* Add a getter and callback for static metadata to the player
([#7266](https://github.com/google/ExoPlayer/issues/7266)).
* Time out on release to prevent ANRs if the underlying platform call
is stuck ([#4352](https://github.com/google/ExoPlayer/issues/4352)).
* Time out on release to prevent ANRs if the underlying platform call is
stuck ([#4352](https://github.com/google/ExoPlayer/issues/4352)).
* Time out when detaching a surface to prevent ANRs if the underlying
platform call is stuck
([#5887](https://github.com/google/ExoPlayer/issues/5887)).
Expand Down Expand Up @@ -48,8 +50,8 @@
([#7949](https://github.com/google/ExoPlayer/issues/7949)).
* Fix regression for Ogg files with packets that span multiple pages
([#7992](https://github.com/google/ExoPlayer/issues/7992)).
* Add TS extractor parameter to configure the number of bytes in which
to search for a timestamp to determine the duration and to seek.
* Add TS extractor parameter to configure the number of bytes in which to
search for a timestamp to determine the duration and to seek.
([#7988](https://github.com/google/ExoPlayer/issues/7988)).
* Ignore negative payload size in PES packets
([#8005](https://github.com/google/ExoPlayer/issues/8005)).
Expand All @@ -64,9 +66,9 @@
* Allow apps to specify a `VideoAdPlayerCallback`
([#7944](https://github.com/google/ExoPlayer/issues/7944)).
* Accept ad tags via the `AdsMediaSource` constructor and deprecate
passing them via the `ImaAdsLoader` constructor/builders. Passing the
ad tag via media item playback properties continues to be supported.
This is in preparation for supporting ads in playlists
passing them via the `ImaAdsLoader` constructor/builders. Passing the ad
tag via media item playback properties continues to be supported. This
is in preparation for supporting ads in playlists
([#3750](https://github.com/google/ExoPlayer/issues/3750)).

* UI:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;

import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.Math.max;
import static java.lang.Math.min;

Expand Down Expand Up @@ -129,7 +130,7 @@ public Builder() {
* @throws IllegalStateException If {@link #build()} has already been called.
*/
public Builder setAllocator(DefaultAllocator allocator) {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
this.allocator = allocator;
return this;
}
Expand All @@ -154,7 +155,7 @@ public Builder setBufferDurationsMs(
int maxBufferMs,
int bufferForPlaybackMs,
int bufferForPlaybackAfterRebufferMs) {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0");
assertGreaterOrEqual(
bufferForPlaybackAfterRebufferMs, 0, "bufferForPlaybackAfterRebufferMs", "0");
Expand All @@ -181,7 +182,7 @@ public Builder setBufferDurationsMs(
* @throws IllegalStateException If {@link #build()} has already been called.
*/
public Builder setTargetBufferBytes(int targetBufferBytes) {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
this.targetBufferBytes = targetBufferBytes;
return this;
}
Expand All @@ -196,7 +197,7 @@ public Builder setTargetBufferBytes(int targetBufferBytes) {
* @throws IllegalStateException If {@link #build()} has already been called.
*/
public Builder setPrioritizeTimeOverSizeThresholds(boolean prioritizeTimeOverSizeThresholds) {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds;
return this;
}
Expand All @@ -212,7 +213,7 @@ public Builder setPrioritizeTimeOverSizeThresholds(boolean prioritizeTimeOverSiz
* @throws IllegalStateException If {@link #build()} has already been called.
*/
public Builder setBackBuffer(int backBufferDurationMs, boolean retainBackBufferFromKeyframe) {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0");
this.backBufferDurationMs = backBufferDurationMs;
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe;
Expand All @@ -227,7 +228,7 @@ public DefaultLoadControl createDefaultLoadControl() {

/** Creates a {@link DefaultLoadControl}. */
public DefaultLoadControl build() {
Assertions.checkState(!buildCalled);
checkState(!buildCalled);
buildCalled = true;
if (allocator == null) {
allocator = new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
Expand Down Expand Up @@ -257,7 +258,7 @@ public DefaultLoadControl build() {
private final boolean retainBackBufferFromKeyframe;

private int targetBufferBytes;
private boolean isBuffering;
private boolean isLoading;

/** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */
@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -394,23 +395,26 @@ public boolean shouldContinueLoading(
// Prevent playback from getting stuck if minBufferUs is too small.
minBufferUs = max(minBufferUs, 500_000);
if (bufferedDurationUs < minBufferUs) {
isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
if (!isBuffering && bufferedDurationUs < 500_000) {
isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
if (!isLoading && bufferedDurationUs < 500_000) {
Log.w(
"DefaultLoadControl",
"Target buffer size reached with less than 500ms of buffered media data.");
}
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
isBuffering = false;
} // Else don't change the buffering state
return isBuffering;
isLoading = false;
} // Else don't change the loading state.
return isLoading;
}

@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs) {
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
if (targetLiveOffsetUs != C.TIME_UNSET) {
minBufferDurationUs = min(targetLiveOffsetUs / 2, minBufferDurationUs);
}
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs
|| (!prioritizeTimeOverSizeThresholds
Expand Down Expand Up @@ -441,7 +445,7 @@ private void reset(boolean resetAllocator) {
targetBufferBytesOverwrite == C.LENGTH_UNSET
? DEFAULT_MIN_BUFFER_SIZE
: targetBufferBytesOverwrite;
isBuffering = false;
isLoading = false;
if (resetAllocator) {
allocator.reset();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1647,10 +1647,20 @@ private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
int windowIndex =
playbackInfo.timeline.getPeriodByUid(queue.getPlayingPeriod().uid, period).windowIndex;
playbackInfo.timeline.getWindow(windowIndex, window);
long targetLiveOffsetUs =
window.isLive && window.isDynamic
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
|| loadControl.shouldStartPlayback(
getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed,
rebuffering,
targetLiveOffsetUs);
}

private boolean isTimelineReady() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
*/
public interface LoadControl {

/**
* Called by the player when prepared with a new source.
*/
/** Called by the player when prepared with a new source. */
void onPrepared();

/**
Expand Down Expand Up @@ -113,7 +111,11 @@ boolean shouldContinueLoading(
* @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by
* buffer depletion rather than a user action. Hence this parameter is false during initial
* buffering and when buffering as a result of a seek operation.
* @param targetLiveOffsetUs The desired playback position offset to the live edge in
* microseconds, or {@link C#TIME_UNSET} if the media is not a live stream or no offset is
* configured.
* @return Whether playback should be allowed to start or resume.
*/
boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering);
boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ public void shouldContinueLoadingWithMinBufferReached_inFastPlayback() {
.isTrue();
}

@Test
public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() {
loadControl = builder.build();
loadControl.onTracksSelected(new Renderer[0], TrackGroupArray.EMPTY, new TrackSelectionArray());

assertThat(
loadControl.shouldContinueLoading(
/* playbackPositionUs= */ 0, /* bufferedDurationUs= */ 0, /* playbackSpeed= */ 1f))
.isTrue();
}

@Test
public void shouldNotContinueLoadingWithMaxBufferReached_inFastPlayback() {
build();
Expand All @@ -185,21 +196,117 @@ public void shouldNotContinueLoadingWithMaxBufferReached_inFastPlayback() {
}

@Test
public void startsPlayback_whenMinBufferSizeReached() {
public void shouldStartPlayback_whenMinBufferSizeReached_returnsTrue() {
build();

assertThat(loadControl.shouldStartPlayback(MIN_BUFFER_US, SPEED, /* rebuffering= */ false))
assertThat(
loadControl.shouldStartPlayback(
MIN_BUFFER_US,
SPEED,
/* rebuffering= */ false,
/* targetLiveOffsetUs= */ C.TIME_UNSET))
.isTrue();
}

@Test
public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() {
loadControl = builder.build();
loadControl.onTracksSelected(new Renderer[0], TrackGroupArray.EMPTY, new TrackSelectionArray());
public void
shouldStartPlayback_withoutTargetLiveOffset_returnsTrueWhenBufferForPlaybackReached() {
builder.setBufferDurationsMs(
/* minBufferMs= */ 5_000,
/* maxBufferMs= */ 20_000,
/* bufferForPlaybackMs= */ 3_000,
/* bufferForPlaybackAfterRebufferMs= */ 4_000);
build();

assertThat(
loadControl.shouldContinueLoading(
/* playbackPositionUs= */ 0, /* bufferedDurationUs= */ 0, /* playbackSpeed= */ 1f))
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 2_999_999,
SPEED,
/* rebuffering= */ false,
/* targetLiveOffsetUs= */ C.TIME_UNSET))
.isFalse();
assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 3_000_000,
SPEED,
/* rebuffering= */ false,
/* targetLiveOffsetUs= */ C.TIME_UNSET))
.isTrue();
}

@Test
public void shouldStartPlayback_withTargetLiveOffset_returnsTrueWhenHalfLiveOffsetReached() {
builder.setBufferDurationsMs(
/* minBufferMs= */ 5_000,
/* maxBufferMs= */ 20_000,
/* bufferForPlaybackMs= */ 3_000,
/* bufferForPlaybackAfterRebufferMs= */ 4_000);
build();

assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 499_999,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ 1_000_000))
.isFalse();
assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 500_000,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ 1_000_000))
.isTrue();
}

@Test
public void
shouldStartPlayback_afterRebuffer_withoutTargetLiveOffset_whenBufferForPlaybackAfterRebufferReached() {
builder.setBufferDurationsMs(
/* minBufferMs= */ 5_000,
/* maxBufferMs= */ 20_000,
/* bufferForPlaybackMs= */ 3_000,
/* bufferForPlaybackAfterRebufferMs= */ 4_000);
build();

assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 3_999_999,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ C.TIME_UNSET))
.isFalse();
assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 4_000_000,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ C.TIME_UNSET))
.isTrue();
}

@Test
public void shouldStartPlayback_afterRebuffer_withTargetLiveOffset_whenHalfLiveOffsetReached() {
builder.setBufferDurationsMs(
/* minBufferMs= */ 5_000,
/* maxBufferMs= */ 20_000,
/* bufferForPlaybackMs= */ 3_000,
/* bufferForPlaybackAfterRebufferMs= */ 4_000);
build();

assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 499_999,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ 1_000_000))
.isFalse();
assertThat(
loadControl.shouldStartPlayback(
/* bufferedDurationUs= */ 500_000,
SPEED,
/* rebuffering= */ true,
/* targetLiveOffsetUs= */ 1_000_000))
.isTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4605,7 +4605,10 @@ public boolean shouldContinueLoading(

@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
long bufferedDurationUs,
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
return true;
}
};
Expand Down Expand Up @@ -4649,7 +4652,10 @@ public boolean shouldContinueLoading(

@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
long bufferedDurationUs,
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
return true;
}
};
Expand Down Expand Up @@ -4724,7 +4730,10 @@ public boolean shouldContinueLoading(

@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
long bufferedDurationUs,
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
return false;
}
};
Expand Down

0 comments on commit 8fdadad

Please sign in to comment.