Skip to content

Commit 88b6401

Browse files
committed
Allow playback regardless buffered duration when loading fails
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)
1 parent 9b39e35 commit 88b6401

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* ExoPlayer:
77
* Handle preload callbacks asynchronously in `PreloadMediaSource`
88
([#1568](https://github.com/androidx/media/issues/1568)).
9+
* Allow playback regardless of buffered duration when loading fails
10+
([#1571](https://github.com/androidx/media/issues/1571)).
911
* Transformer:
1012
* Track Selection:
1113
* Extractors:

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,9 @@ private boolean isLoadingPossible() {
26332633
if (loadingPeriodHolder == null) {
26342634
return false;
26352635
}
2636+
if (loadingPeriodHolder.hasLoadingError()) {
2637+
return false;
2638+
}
26362639
long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs();
26372640
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
26382641
return false;

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodHolder.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import androidx.media3.exoplayer.trackselection.TrackSelector;
3636
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
3737
import androidx.media3.exoplayer.upstream.Allocator;
38+
import java.io.IOException;
3839

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

398+
/**
399+
* Returns whether the media period has encountered an error that prevents it from being prepared
400+
* or reading data.
401+
*/
402+
public boolean hasLoadingError() {
403+
try {
404+
if (!prepared) {
405+
mediaPeriod.maybeThrowPrepareError();
406+
} else {
407+
for (SampleStream sampleStream : sampleStreams) {
408+
if (sampleStream != null) {
409+
sampleStream.maybeThrowError();
410+
}
411+
}
412+
}
413+
} catch (IOException e) {
414+
return true;
415+
}
416+
return false;
417+
}
418+
397419
private void enableTrackSelectionsInResult() {
398420
if (!isLoadingMediaPeriod()) {
399421
return;

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10794,6 +10794,62 @@ public void maybeThrowPrepareError() throws IOException {
1079410794
player.release();
1079510795
}
1079610796

10797+
@Test
10798+
public void
10799+
mediaPeriodMaybeThrowPrepareError_bufferedDurationUnderMinimumBufferForPlayback_keepPlayingUntilBufferedDataExhausts()
10800+
throws Exception {
10801+
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
10802+
// Define a timeline that has a short duration of 1 second for the first item, which is smaller
10803+
// than the default buffer duration for playback in DefaultLoadControl (2.5 seconds).
10804+
Timeline timeline =
10805+
new FakeTimeline(
10806+
new TimelineWindowDefinition(
10807+
/* isSeekable= */ true,
10808+
/* isDynamic= */ false,
10809+
/* durationUs= */ 1 * C.MICROS_PER_SECOND));
10810+
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
10811+
player.addMediaSource(
10812+
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
10813+
@Override
10814+
protected MediaPeriod createMediaPeriod(
10815+
MediaPeriodId id,
10816+
TrackGroupArray trackGroupArray,
10817+
Allocator allocator,
10818+
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
10819+
DrmSessionManager drmSessionManager,
10820+
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
10821+
@Nullable TransferListener transferListener) {
10822+
return new FakeMediaPeriod(
10823+
trackGroupArray,
10824+
allocator,
10825+
/* singleSampleTimeUs= */ 0,
10826+
mediaSourceEventDispatcher,
10827+
DrmSessionManager.DRM_UNSUPPORTED,
10828+
drmEventDispatcher,
10829+
/* deferOnPrepared= */ true) {
10830+
@Override
10831+
public void maybeThrowPrepareError() throws IOException {
10832+
throw new IOException();
10833+
}
10834+
};
10835+
}
10836+
});
10837+
10838+
player.prepare();
10839+
player.play();
10840+
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);
10841+
10842+
Object period1Uid =
10843+
player
10844+
.getCurrentTimeline()
10845+
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
10846+
.uid;
10847+
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
10848+
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
10849+
10850+
player.release();
10851+
}
10852+
1079710853
@Test
1079810854
public void sampleStreamMaybeThrowError_isNotThrownUntilPlaybackReachedFailingItem()
1079910855
throws Exception {
@@ -10859,6 +10915,79 @@ public void maybeThrowError() throws IOException {
1085910915
player.release();
1086010916
}
1086110917

10918+
@Test
10919+
public void
10920+
sampleStreamMaybeThrowError_bufferedDurationUnderMinimumBufferForPlayback_keepPlayingUntilBufferedDataExhausts()
10921+
throws Exception {
10922+
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();
10923+
// Define a timeline that has a short duration of 1 second for the first item, which is smaller
10924+
// than the default buffer duration for playback in DefaultLoadControl (2.5 seconds).
10925+
Timeline timeline =
10926+
new FakeTimeline(
10927+
new TimelineWindowDefinition(
10928+
/* isSeekable= */ true,
10929+
/* isDynamic= */ false,
10930+
/* durationUs= */ 1 * C.MICROS_PER_SECOND));
10931+
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
10932+
player.addMediaSource(
10933+
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
10934+
@Override
10935+
protected MediaPeriod createMediaPeriod(
10936+
MediaPeriodId id,
10937+
TrackGroupArray trackGroupArray,
10938+
Allocator allocator,
10939+
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
10940+
DrmSessionManager drmSessionManager,
10941+
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
10942+
@Nullable TransferListener transferListener) {
10943+
return new FakeMediaPeriod(
10944+
trackGroupArray,
10945+
allocator,
10946+
/* trackDataFactory= */ (format, mediaPeriodId) -> ImmutableList.of(),
10947+
mediaSourceEventDispatcher,
10948+
drmSessionManager,
10949+
drmEventDispatcher,
10950+
/* deferOnPrepared= */ false) {
10951+
@Override
10952+
protected FakeSampleStream createSampleStream(
10953+
Allocator allocator,
10954+
@Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
10955+
DrmSessionManager drmSessionManager,
10956+
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
10957+
Format initialFormat,
10958+
List<FakeSampleStreamItem> fakeSampleStreamItems) {
10959+
return new FakeSampleStream(
10960+
allocator,
10961+
mediaSourceEventDispatcher,
10962+
drmSessionManager,
10963+
drmEventDispatcher,
10964+
initialFormat,
10965+
fakeSampleStreamItems) {
10966+
@Override
10967+
public void maybeThrowError() throws IOException {
10968+
throw new IOException();
10969+
}
10970+
};
10971+
}
10972+
};
10973+
}
10974+
});
10975+
10976+
player.prepare();
10977+
player.play();
10978+
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);
10979+
10980+
Object period1Uid =
10981+
player
10982+
.getCurrentTimeline()
10983+
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
10984+
.uid;
10985+
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
10986+
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
10987+
10988+
player.release();
10989+
}
10990+
1086210991
@Test
1086310992
public void rendererError_isReportedWithReadingMediaPeriodId() throws Exception {
1086410993
FakeMediaSource source0 =

0 commit comments

Comments
 (0)