Detect SampleStream error after PreloadMediaPeriod has prepared
Per the javadoc, the method `MediaPeriod.maybeThrowPrepareError` is only allowed to be called before the period has completed preparation. For later errors in loading the streams, `SampleStream.maybeThrowError` will be called instead. PiperOrigin-RevId: 665831430
This commit is contained in:
parent
059ad62377
commit
410b26fba1
@ -35,8 +35,8 @@ import java.util.Objects;
|
||||
|
||||
public final MediaPeriod mediaPeriod;
|
||||
|
||||
public boolean prepared;
|
||||
private boolean prepareInternalCalled;
|
||||
private boolean prepared;
|
||||
@Nullable private Callback callback;
|
||||
@Nullable private PreloadTrackSelectionHolder preloadTrackSelectionHolder;
|
||||
|
||||
@ -49,7 +49,7 @@ import java.util.Objects;
|
||||
this.mediaPeriod = mediaPeriod;
|
||||
}
|
||||
|
||||
/* package */ void preload(Callback callback, long positionUs) {
|
||||
public void preload(Callback callback, long positionUs) {
|
||||
this.callback = callback;
|
||||
if (prepared) {
|
||||
callback.onPrepared(PreloadMediaPeriod.this);
|
||||
@ -217,7 +217,7 @@ import java.util.Objects;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */ long selectTracksForPreloading(
|
||||
public long selectTracksForPreloading(
|
||||
@NullableType ExoTrackSelection[] selections, long positionUs) {
|
||||
@NullableType SampleStream[] preloadedSampleStreams = new SampleStream[selections.length];
|
||||
boolean[] preloadedStreamResetFlags = new boolean[selections.length];
|
||||
@ -284,6 +284,17 @@ import java.util.Objects;
|
||||
mediaPeriod.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
public void maybeThrowStreamError() throws IOException {
|
||||
checkState(prepared);
|
||||
if (preloadTrackSelectionHolder != null) {
|
||||
for (SampleStream stream : preloadTrackSelectionHolder.streams) {
|
||||
if (stream != null) {
|
||||
stream.maybeThrowError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PreloadTrackSelectionHolder {
|
||||
public final @NullableType ExoTrackSelection[] selections;
|
||||
public final boolean[] mayRetainStreamFlags;
|
||||
|
@ -424,7 +424,12 @@ public final class PreloadMediaSource extends WrappingMediaSource {
|
||||
try {
|
||||
maybeThrowSourceInfoRefreshError();
|
||||
if (preloadingMediaPeriodAndKey != null) {
|
||||
preloadingMediaPeriodAndKey.first.maybeThrowPrepareError();
|
||||
PreloadMediaPeriod preloadingMediaPeriod = preloadingMediaPeriodAndKey.first;
|
||||
if (!preloadingMediaPeriod.prepared) {
|
||||
preloadingMediaPeriod.maybeThrowPrepareError();
|
||||
} else {
|
||||
preloadingMediaPeriod.maybeThrowStreamError();
|
||||
}
|
||||
}
|
||||
preloadHandler.postDelayed(this::checkForPreloadError, CHECK_FOR_PRELOAD_ERROR_INTERVAL_MS);
|
||||
} catch (IOException e) {
|
||||
|
@ -15,7 +15,9 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.source.preload;
|
||||
|
||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@ -29,6 +31,7 @@ import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
@ -43,11 +46,15 @@ import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.FixedTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||
import androidx.media3.test.utils.FakeMediaPeriod;
|
||||
import androidx.media3.test.utils.FakeSampleStream;
|
||||
import androidx.media3.test.utils.FakeTimeline;
|
||||
import androidx.media3.test.utils.FakeTrackSelection;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Test;
|
||||
@ -1594,4 +1601,68 @@ public final class PreloadMediaPeriodTest {
|
||||
.selectTracks(eq(trackSelections), any(), any(), any(), /* positionUs= */ eq(0L));
|
||||
assertThat(trackSelectionStartPositionUs).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeThrowStreamError_preloadedStreamHasError_errorThrows() throws Exception {
|
||||
Format videoFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
.setAverageBitrate(800_000)
|
||||
.setWidth(1280)
|
||||
.setHeight(720)
|
||||
.build();
|
||||
MediaSource.MediaPeriodId mediaPeriodId =
|
||||
new MediaSource.MediaPeriodId(/* periodUid= */ new Object());
|
||||
FakeMediaPeriod wrappedMediaPeriod =
|
||||
new FakeMediaPeriod(
|
||||
new TrackGroupArray(new TrackGroup(videoFormat)),
|
||||
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||
FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
new MediaSourceEventListener.EventDispatcher()
|
||||
.withParameters(/* windowIndex= */ 0, mediaPeriodId)) {
|
||||
@Override
|
||||
protected FakeSampleStream createSampleStream(
|
||||
Allocator allocator,
|
||||
@Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
|
||||
DrmSessionManager drmSessionManager,
|
||||
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
|
||||
Format initialFormat,
|
||||
List<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems) {
|
||||
return new FakeSampleStream(
|
||||
allocator,
|
||||
mediaSourceEventDispatcher,
|
||||
drmSessionManager,
|
||||
drmEventDispatcher,
|
||||
initialFormat,
|
||||
fakeSampleStreamItems) {
|
||||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
PreloadMediaPeriod preloadMediaPeriod = new PreloadMediaPeriod(wrappedMediaPeriod);
|
||||
AtomicBoolean onPreparedOfPreloadCallbackCalled = new AtomicBoolean();
|
||||
MediaPeriod.Callback preloadCallback =
|
||||
new MediaPeriod.Callback() {
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
onPreparedOfPreloadCallbackCalled.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {}
|
||||
};
|
||||
preloadMediaPeriod.preload(preloadCallback, /* positionUs= */ 0L);
|
||||
runMainLooperUntil(onPreparedOfPreloadCallbackCalled::get);
|
||||
ExoTrackSelection[] preloadTrackSelections =
|
||||
new ExoTrackSelection[] {
|
||||
new FixedTrackSelection(new TrackGroup(videoFormat), /* track= */ 0)
|
||||
};
|
||||
// PreloadMediaPeriod.selectTracksForPreloading keeps the preloaded stream.
|
||||
preloadMediaPeriod.selectTracksForPreloading(preloadTrackSelections, /* positionUs= */ 0L);
|
||||
|
||||
assertThrows(IOException.class, preloadMediaPeriod::maybeThrowStreamError);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.SystemClock;
|
||||
@ -68,12 +70,15 @@ import androidx.media3.test.utils.FakeAudioRenderer;
|
||||
import androidx.media3.test.utils.FakeMediaPeriod;
|
||||
import androidx.media3.test.utils.FakeMediaSource;
|
||||
import androidx.media3.test.utils.FakeMediaSourceFactory;
|
||||
import androidx.media3.test.utils.FakeSampleStream;
|
||||
import androidx.media3.test.utils.FakeTimeline;
|
||||
import androidx.media3.test.utils.FakeTrackSelector;
|
||||
import androidx.media3.test.utils.FakeVideoRenderer;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@ -334,15 +339,12 @@ public final class PreloadMediaSourceTest {
|
||||
@Test
|
||||
public void preload_sourceInfoRefreshErrorThrows_onPreloadErrorCalled() throws TimeoutException {
|
||||
AtomicReference<PreloadException> preloadExceptionReference = new AtomicReference<>();
|
||||
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
|
||||
IOException causeException = new IOException("Failed to refresh source info");
|
||||
TestPreloadControl preloadControl =
|
||||
new TestPreloadControl() {
|
||||
@Override
|
||||
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {
|
||||
super.onPreloadError(error, mediaSource);
|
||||
preloadExceptionReference.set(error);
|
||||
preloadMediaSourceReference.set(mediaSource);
|
||||
}
|
||||
};
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
@ -393,9 +395,8 @@ public final class PreloadMediaSourceTest {
|
||||
.build());
|
||||
|
||||
preloadMediaSource.preload(/* startPositionUs= */ 0L);
|
||||
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
|
||||
runMainLooperUntil(() -> preloadExceptionReference.get() != null);
|
||||
|
||||
assertThat(preloadControl.onPreloadErrorCalled).isTrue();
|
||||
assertThat(preloadExceptionReference.get()).hasCauseThat().isEqualTo(causeException);
|
||||
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(0);
|
||||
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
|
||||
@ -406,15 +407,12 @@ public final class PreloadMediaSourceTest {
|
||||
@Test
|
||||
public void preload_periodPrepareErrorThrows_onPreloadErrorCalled() throws TimeoutException {
|
||||
AtomicReference<PreloadException> preloadExceptionReference = new AtomicReference<>();
|
||||
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
|
||||
IOException causeException = new IOException("Failed to prepare the period");
|
||||
TestPreloadControl preloadControl =
|
||||
new TestPreloadControl() {
|
||||
@Override
|
||||
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {
|
||||
super.onPreloadError(error, mediaSource);
|
||||
preloadExceptionReference.set(error);
|
||||
preloadMediaSourceReference.set(mediaSource);
|
||||
}
|
||||
};
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
@ -481,9 +479,8 @@ public final class PreloadMediaSourceTest {
|
||||
.build());
|
||||
|
||||
preloadMediaSource.preload(/* startPositionUs= */ 0L);
|
||||
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
|
||||
runMainLooperUntil(() -> preloadExceptionReference.get() != null);
|
||||
|
||||
assertThat(preloadControl.onPreloadErrorCalled).isTrue();
|
||||
assertThat(preloadExceptionReference.get()).hasCauseThat().isEqualTo(causeException);
|
||||
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
|
||||
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
|
||||
@ -491,6 +488,117 @@ public final class PreloadMediaSourceTest {
|
||||
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preload_sampleStreamErrorThrows_onPreloadErrorCalled() throws TimeoutException {
|
||||
AtomicReference<PreloadException> preloadExceptionReference = new AtomicReference<>();
|
||||
IOException causeException = new IOException("Failed to read the data");
|
||||
TestPreloadControl preloadControl =
|
||||
new TestPreloadControl() {
|
||||
@Override
|
||||
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {
|
||||
preloadExceptionReference.set(error);
|
||||
}
|
||||
};
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new MediaSource.Factory() {
|
||||
@Override
|
||||
public MediaSource.Factory setDrmSessionManagerProvider(
|
||||
DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource.Factory setLoadErrorHandlingPolicy(
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @C.ContentType int[] getSupportedTypes() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource createMediaSource(MediaItem mediaItem) {
|
||||
Format videoFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
.setAverageBitrate(800_000)
|
||||
.setWidth(1280)
|
||||
.setHeight(720)
|
||||
.build();
|
||||
return new FakeMediaSource(new FakeTimeline(), videoFormat) {
|
||||
@Override
|
||||
public 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<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems) {
|
||||
return new FakeSampleStream(
|
||||
allocator,
|
||||
mediaSourceEventDispatcher,
|
||||
drmSessionManager,
|
||||
drmEventDispatcher,
|
||||
initialFormat,
|
||||
fakeSampleStreamItems) {
|
||||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
throw causeException;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
TrackSelector trackSelector =
|
||||
new DefaultTrackSelector(ApplicationProvider.getApplicationContext());
|
||||
trackSelector.init(() -> {}, bandwidthMeter);
|
||||
PreloadMediaSource.Factory preloadMediaSourceFactory =
|
||||
new PreloadMediaSource.Factory(
|
||||
mediaSourceFactory,
|
||||
preloadControl,
|
||||
trackSelector,
|
||||
bandwidthMeter,
|
||||
getRendererCapabilities(renderersFactory),
|
||||
allocator,
|
||||
Util.getCurrentOrMainLooper());
|
||||
PreloadMediaSource preloadMediaSource =
|
||||
preloadMediaSourceFactory.createMediaSource(
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||
.build());
|
||||
|
||||
preloadMediaSource.preload(/* startPositionUs= */ 0L);
|
||||
runMainLooperUntil(() -> preloadExceptionReference.get() != null);
|
||||
|
||||
assertThat(preloadExceptionReference.get()).hasCauseThat().isEqualTo(causeException);
|
||||
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
|
||||
assertThat(preloadControl.onTrackSelectedCalled).isTrue();
|
||||
assertThat(preloadControl.onContinueLoadingRequestedCalled).isFalse();
|
||||
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
prepareSource_beforeSourceInfoRefreshedForPreloading_onlyInvokeExternalCallerOnSourceInfoRefreshed() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user