Skip to content

Commit

Permalink
Allow playback regardless buffered duration when loading fails
Browse files Browse the repository at this point in the history
It is possible for playback to be stuck when there is failure in loading further data, while the player is required to load more due to the buffered duration being under `DefaultLoadControl.bufferForPlayback`. Therefore, we check if there is any loading error in `isLoadingPossible`, so that the player will allow the playback of the existing data rather than waiting forever for the data that can never be loaded.

Issue: #1571
PiperOrigin-RevId: 665801674
(cherry picked from commit 351593a)
  • Loading branch information
tianyif committed Aug 21, 2024
1 parent 9b39e35 commit 88b6401
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* ExoPlayer:
* Handle preload callbacks asynchronously in `PreloadMediaSource`
([#1568](https://github.com/androidx/media/issues/1568)).
* Allow playback regardless of buffered duration when loading fails
([#1571](https://github.com/androidx/media/issues/1571)).
* Transformer:
* Track Selection:
* Extractors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,9 @@ private boolean isLoadingPossible() {
if (loadingPeriodHolder == null) {
return false;
}
if (loadingPeriodHolder.hasLoadingError()) {
return false;
}
long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
import androidx.media3.exoplayer.upstream.Allocator;
import java.io.IOException;

/** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */
/* package */ final class MediaPeriodHolder {
Expand Down Expand Up @@ -394,6 +395,27 @@ public void updateClipping() {
}
}

/**
* Returns whether the media period has encountered an error that prevents it from being prepared
* or reading data.
*/
public boolean hasLoadingError() {
try {
if (!prepared) {
mediaPeriod.maybeThrowPrepareError();
} else {
for (SampleStream sampleStream : sampleStreams) {
if (sampleStream != null) {
sampleStream.maybeThrowError();
}
}
}
} catch (IOException e) {
return true;
}
return false;
}

private void enableTrackSelectionsInResult() {
if (!isLoadingMediaPeriod()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10794,6 +10794,62 @@ public void maybeThrowPrepareError() throws IOException {
player.release();
}

@Test
public void
mediaPeriodMaybeThrowPrepareError_bufferedDurationUnderMinimumBufferForPlayback_keepPlayingUntilBufferedDataExhausts()
throws Exception {
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
// Define a timeline that has a short duration of 1 second for the first item, which is smaller
// than the default buffer duration for playback in DefaultLoadControl (2.5 seconds).
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 1 * C.MICROS_PER_SECOND));
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
player.addMediaSource(
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected MediaPeriod createMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
allocator,
/* singleSampleTimeUs= */ 0,
mediaSourceEventDispatcher,
DrmSessionManager.DRM_UNSUPPORTED,
drmEventDispatcher,
/* deferOnPrepared= */ true) {
@Override
public void maybeThrowPrepareError() throws IOException {
throw new IOException();
}
};
}
});

player.prepare();
player.play();
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);

Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);

player.release();
}

@Test
public void sampleStreamMaybeThrowError_isNotThrownUntilPlaybackReachedFailingItem()
throws Exception {
Expand Down Expand Up @@ -10859,6 +10915,79 @@ public void maybeThrowError() throws IOException {
player.release();
}

@Test
public void
sampleStreamMaybeThrowError_bufferedDurationUnderMinimumBufferForPlayback_keepPlayingUntilBufferedDataExhausts()
throws Exception {
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
// Define a timeline that has a short duration of 1 second for the first item, which is smaller
// than the default buffer duration for playback in DefaultLoadControl (2.5 seconds).
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 1 * C.MICROS_PER_SECOND));
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
player.addMediaSource(
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected MediaPeriod createMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
allocator,
/* trackDataFactory= */ (format, mediaPeriodId) -> ImmutableList.of(),
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
/* deferOnPrepared= */ false) {
@Override
protected FakeSampleStream createSampleStream(
Allocator allocator,
@Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
Format initialFormat,
List<FakeSampleStreamItem> fakeSampleStreamItems) {
return new FakeSampleStream(
allocator,
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
initialFormat,
fakeSampleStreamItems) {
@Override
public void maybeThrowError() throws IOException {
throw new IOException();
}
};
}
};
}
});

player.prepare();
player.play();
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);

Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);

player.release();
}

@Test
public void rendererError_isReportedWithReadingMediaPeriodId() throws Exception {
FakeMediaSource source0 =
Expand Down

0 comments on commit 88b6401

Please sign in to comment.