Add onPreloadError method to PreloadMediaSource.PreloadControl

Upon the call of `PreloadMediaSource.preload`, the source will periodically check the source refresh or period loading error, and trigger `PreloadMediaSource.PreloadControl.onPreloadError`. For now, the `DefaultPreloadManager` will skip the problematic source and continue to preload the next source. The checking of the error will be terminated when the source stops preloading or releases.

PiperOrigin-RevId: 650195817
This commit is contained in:
tianyifeng 2024-07-08 04:03:44 -07:00 committed by Copybara-Service
parent b4722ef1ea
commit 58ff8fa3c2
5 changed files with 363 additions and 448 deletions

View file

@ -16,6 +16,9 @@
every media item. Previously it was not called for the first one. Use
`MediaCodecRenderer.experimentalEnableProcessedStreamChangedAtStart()`
to enable this.
* Add `PreloadMediaSource.PreloadControl.onPreloadError` to allow
`PreloadMediaSource.PreloadControl` implementations to take actions when
error occurs.
* Transformer:
* Track Selection:
* Extractors:

View file

@ -249,6 +249,11 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
onPreloadCompleted(mediaSource);
}
@Override
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {
onPreloadCompleted(mediaSource);
}
private boolean continueOrCompletePreloading(
PreloadMediaSource mediaSource,
Predicate<Status> continueLoadingPredicate,

View file

@ -16,6 +16,7 @@
package androidx.media3.exoplayer.source.preload;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.postOrRun;
import android.os.Handler;
import android.os.Looper;
@ -45,6 +46,7 @@ import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import java.io.IOException;
import java.util.Arrays;
/**
@ -105,6 +107,14 @@ public final class PreloadMediaSource extends WrappingMediaSource {
* @param mediaSource The {@link PreloadMediaSource} that has loaded to the end of source.
*/
default void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) {}
/**
* Called from {@link PreloadMediaSource} when an error occurs.
*
* @param error The {@linkplain PreloadException error}.
* @param mediaSource The {@link PreloadMediaSource} that has the error occur.
*/
void onPreloadError(PreloadException error, PreloadMediaSource mediaSource);
}
/** Factory for {@link PreloadMediaSource}. */
@ -203,6 +213,7 @@ public final class PreloadMediaSource extends WrappingMediaSource {
}
private static final String TAG = "PreloadMediaSource";
private static final long CHECK_FOR_PRELOAD_ERROR_INTERVAL_MS = 100;
private final PreloadControl preloadControl;
private final TrackSelector trackSelector;
@ -253,10 +264,11 @@ public final class PreloadMediaSource extends WrappingMediaSource {
this.startPositionUs = startPositionUs;
onSourcePreparedNotified = false;
if (isUsedByPlayer()) {
notifyOnUsedByPlayer();
onUsedByPlayer();
} else {
setPlayerId(PlayerId.UNSET); // Set to PlayerId.UNSET as there is no ongoing playback.
prepareSourceInternal(bandwidthMeter.getTransferListener());
checkForPreloadError();
}
});
}
@ -267,7 +279,8 @@ public final class PreloadMediaSource extends WrappingMediaSource {
* <p>Can be called from any thread.
*/
public void clear() {
preloadHandler.post(
postOrRun(
preloadHandler,
() -> {
if (preloadingMediaPeriodAndKey != null) {
mediaSource.releasePeriod(preloadingMediaPeriodAndKey.first.mediaPeriod);
@ -279,7 +292,7 @@ public final class PreloadMediaSource extends WrappingMediaSource {
@Override
protected void prepareSourceInternal() {
if (isUsedByPlayer() && !onUsedByPlayerNotified) {
notifyOnUsedByPlayer();
onUsedByPlayer();
}
if (timeline != null) {
onChildSourceInfoRefreshed(timeline);
@ -298,6 +311,7 @@ public final class PreloadMediaSource extends WrappingMediaSource {
}
onSourcePreparedNotified = true;
if (!preloadControl.onSourcePrepared(this)) {
stopPreloading();
return;
}
Pair<Object, Long> periodPosition =
@ -393,6 +407,42 @@ public final class PreloadMediaSource extends WrappingMediaSource {
});
}
private boolean isUsedByPlayer() {
return prepareSourceCalled();
}
private void onUsedByPlayer() {
preloadControl.onUsedByPlayer(this);
stopPreloading();
onUsedByPlayerNotified = true;
}
private void checkForPreloadError() {
try {
maybeThrowSourceInfoRefreshError();
if (preloadingMediaPeriodAndKey != null) {
preloadingMediaPeriodAndKey.first.maybeThrowPrepareError();
}
preloadHandler.postDelayed(this::checkForPreloadError, CHECK_FOR_PRELOAD_ERROR_INTERVAL_MS);
} catch (IOException e) {
preloadControl.onPreloadError(
new PreloadException(this.getMediaItem(), /* message= */ null, e), this);
stopPreloading();
}
}
private void stopPreloading() {
preloadHandler.removeCallbacksAndMessages(null);
}
private static boolean mediaPeriodIdEqualsWithoutWindowSequenceNumber(
MediaPeriodId firstPeriodId, MediaPeriodId secondPeriodId) {
return firstPeriodId.periodUid.equals(secondPeriodId.periodUid)
&& firstPeriodId.adGroupIndex == secondPeriodId.adGroupIndex
&& firstPeriodId.adIndexInAdGroup == secondPeriodId.adIndexInAdGroup
&& firstPeriodId.nextAdGroupIndex == secondPeriodId.nextAdGroupIndex;
}
private class PreloadMediaPeriodCallback implements MediaPeriod.Callback {
private final long periodStartPositionUs;
@ -419,14 +469,18 @@ public final class PreloadMediaSource extends WrappingMediaSource {
} catch (ExoPlaybackException e) {
Log.e(TAG, "Failed to select tracks", e);
}
if (trackSelectorResult != null) {
preloadMediaPeriod.selectTracksForPreloading(
trackSelectorResult.selections, periodStartPositionUs);
if (preloadControl.onTracksSelected(PreloadMediaSource.this)) {
preloadMediaPeriod.continueLoading(
new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build());
}
if (trackSelectorResult == null) {
stopPreloading();
return;
}
preloadMediaPeriod.selectTracksForPreloading(
trackSelectorResult.selections, periodStartPositionUs);
if (!preloadControl.onTracksSelected(PreloadMediaSource.this)) {
stopPreloading();
return;
}
preloadMediaPeriod.continueLoading(
new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build());
}
@Override
@ -437,32 +491,20 @@ public final class PreloadMediaSource extends WrappingMediaSource {
PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod;
if (prepared && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE) {
preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this);
} else if (!prepared
|| preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) {
preloadMediaPeriod.continueLoading(
new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build());
stopPreloading();
return;
}
if (prepared
&& !preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) {
stopPreloading();
return;
}
preloadMediaPeriod.continueLoading(
new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build());
}
}
private boolean isUsedByPlayer() {
return prepareSourceCalled();
}
private void notifyOnUsedByPlayer() {
preloadControl.onUsedByPlayer(this);
onUsedByPlayerNotified = true;
}
private static boolean mediaPeriodIdEqualsWithoutWindowSequenceNumber(
MediaPeriodId firstPeriodId, MediaPeriodId secondPeriodId) {
return firstPeriodId.periodUid.equals(secondPeriodId.periodUid)
&& firstPeriodId.adGroupIndex == secondPeriodId.adGroupIndex
&& firstPeriodId.adIndexInAdGroup == secondPeriodId.adIndexInAdGroup
&& firstPeriodId.nextAdGroupIndex == secondPeriodId.nextAdGroupIndex;
}
private static class MediaPeriodKey {
public final MediaSource.MediaPeriodId mediaPeriodId;

View file

@ -124,6 +124,9 @@ public class PreloadAndPlaybackCoordinationTest {
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
preloadControlOnUsedByPlayerCounter.addAndGet(1);
}
@Override
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {}
};
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(

View file

@ -46,6 +46,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider;
import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
@ -61,6 +62,7 @@ import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.test.utils.FakeAudioRenderer;
import androidx.media3.test.utils.FakeMediaPeriod;
@ -71,8 +73,9 @@ 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 java.io.IOException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
@ -108,40 +111,19 @@ public final class PreloadMediaSourceTest {
@Test
public void preload_loadPeriodToTargetPreloadPosition() throws Exception {
AtomicInteger onSourcePreparedCounter = new AtomicInteger();
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
AtomicBoolean onContinueLoadingStopped = new AtomicBoolean();
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCounter.addAndGet(1);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return true;
}
TestPreloadControl preloadControl =
new TestPreloadControl() {
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
preloadMediaSourceReference.set(mediaSource);
onContinueLoadingRequestedCalled = true;
if (bufferedPositionUs >= TARGET_PRELOAD_POSITION_US) {
onContinueLoadingStopped.set(true);
preloadMediaSourceReference.set(mediaSource);
return false;
}
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
ProgressiveMediaSource.Factory mediaSourceFactory =
new ProgressiveMediaSource.Factory(
@ -166,47 +148,27 @@ public final class PreloadMediaSourceTest {
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(onContinueLoadingStopped::get);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
assertThat(onSourcePreparedCounter.get()).isEqualTo(1);
assertThat(onTracksSelectedCalled.get()).isTrue();
assertThat(onUsedByPlayerCalled.get()).isFalse();
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(1);
assertThat(preloadControl.onTrackSelectedCalled).isTrue();
assertThat(preloadControl.onContinueLoadingRequestedCalled).isTrue();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
assertThat(preloadControl.onPreloadErrorCalled).isFalse();
assertThat(preloadMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
}
@Test
public void preload_stopWhenTracksSelectedByPreloadControl() throws Exception {
AtomicInteger onSourcePreparedCounter = new AtomicInteger();
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
AtomicBoolean onContinueLoadingRequestedCalled = new AtomicBoolean();
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCounter.addAndGet(1);
return true;
}
TestPreloadControl preloadControl =
new TestPreloadControl() {
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTrackSelectedCalled = true;
preloadMediaSourceReference.set(mediaSource);
onTracksSelectedCalled.set(true);
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
onContinueLoadingRequestedCalled.set(true);
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
ProgressiveMediaSource.Factory mediaSourceFactory =
new ProgressiveMediaSource.Factory(
@ -231,47 +193,27 @@ public final class PreloadMediaSourceTest {
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(onTracksSelectedCalled::get);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
assertThat(onSourcePreparedCounter.get()).isEqualTo(1);
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(1);
assertThat(preloadControl.onTrackSelectedCalled).isTrue();
assertThat(preloadMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
assertThat(onContinueLoadingRequestedCalled.get()).isFalse();
assertThat(onUsedByPlayerCalled.get()).isFalse();
assertThat(preloadControl.onContinueLoadingRequestedCalled).isFalse();
assertThat(preloadControl.onPreloadErrorCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
}
@Test
public void preload_stopWhenSourcePreparedByPreloadControl() throws Exception {
AtomicInteger onSourcePreparedCounter = new AtomicInteger();
public void preload_stopWhenSourcePreparedByPreloadControl() {
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
AtomicBoolean onContinueLoadingRequestedCalled = new AtomicBoolean();
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
TestPreloadControl preloadControl =
new TestPreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalledCount++;
preloadMediaSourceReference.set(mediaSource);
onSourcePreparedCounter.addAndGet(1);
return false;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
onContinueLoadingRequestedCalled.set(true);
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
ProgressiveMediaSource.Factory mediaSourceFactory =
new ProgressiveMediaSource.Factory(
@ -298,42 +240,16 @@ public final class PreloadMediaSourceTest {
shadowOf(Looper.getMainLooper()).idle();
assertThat(preloadMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
assertThat(onSourcePreparedCounter.get()).isEqualTo(1);
assertThat(onTracksSelectedCalled.get()).isFalse();
assertThat(onContinueLoadingRequestedCalled.get()).isFalse();
assertThat(onUsedByPlayerCalled.get()).isFalse();
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(1);
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
assertThat(preloadControl.onContinueLoadingRequestedCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
assertThat(preloadControl.onPreloadErrorCalled).isFalse();
}
@Test
public void preload_whileSourceIsAccessedByExternalCaller_notProceedWithPreloading() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean(false);
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
TestPreloadControl preloadControl = new TestPreloadControl();
TrackSelector trackSelector = new FakeTrackSelector();
trackSelector.init(() -> {}, bandwidthMeter);
PreloadMediaSource.Factory preloadMediaSourceFactory =
@ -361,52 +277,21 @@ public final class PreloadMediaSourceTest {
shadowOf(Looper.getMainLooper()).idle();
assertThat(externalCallerMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
assertThat(onSourcePreparedCalled.get()).isFalse();
assertThat(onTracksSelectedCalled.get()).isFalse();
assertThat(onUsedByPlayerCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(0);
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isTrue();
}
@Test
public void preload_loadToTheEndOfSource() throws Exception {
AtomicInteger onSourcePreparedCounter = new AtomicInteger();
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
AtomicBoolean onContinueLoadingRequestedCalled = new AtomicBoolean();
AtomicBoolean onLoadedToTheEndOfSourceCalled = new AtomicBoolean();
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCounter.addAndGet(1);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
// In fact, this method is not necessarily to be called if the
// LOADING_CHECK_INTERVAL_BYTES set for the ProgressiveMediaSource.Factory is large
// enough to have the media load to the end in one round. However, since we explicitly
// set with a small value below, we will still expect this method to be called for at
// least once.
onContinueLoadingRequestedCalled.set(true);
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
AtomicReference<PreloadMediaSource> preloadMediaSourceReference = new AtomicReference<>();
TestPreloadControl preloadControl =
new TestPreloadControl() {
@Override
public void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) {
onLoadedToTheEndOfSourceCalled.set(true);
super.onLoadedToTheEndOfSource(mediaSource);
onLoadedToTheEndOfSourceCalled = true;
preloadMediaSourceReference.set(mediaSource);
}
};
ProgressiveMediaSource.Factory mediaSourceFactory =
@ -432,45 +317,184 @@ public final class PreloadMediaSourceTest {
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(onLoadedToTheEndOfSourceCalled::get);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
assertThat(onSourcePreparedCounter.get()).isEqualTo(1);
assertThat(onTracksSelectedCalled.get()).isTrue();
assertThat(onContinueLoadingRequestedCalled.get()).isTrue();
assertThat(onUsedByPlayerCalled.get()).isFalse();
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(1);
assertThat(preloadControl.onTrackSelectedCalled).isTrue();
// In fact, PreloadControl.onContinueLoadingRequested is not necessarily to be called if the
// LOADING_CHECK_INTERVAL_BYTES set for the ProgressiveMediaSource.Factory is large
// enough to have the media load to the end in one round. However, since we explicitly
// set with a small value below, we will still expect this method to be called for at
// least once.
assertThat(preloadControl.onContinueLoadingRequestedCalled).isTrue();
assertThat(preloadControl.onLoadedToTheEndOfSourceCalled).isTrue();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
}
@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 =
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) {
return new FakeMediaSource(/* timeline= */ null) {
@Override
public void maybeThrowSourceInfoRefreshError() 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(() -> preloadMediaSourceReference.get() != null);
assertThat(preloadControl.onPreloadErrorCalled).isTrue();
assertThat(preloadExceptionReference.get()).hasCauseThat().isEqualTo(causeException);
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(0);
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
assertThat(preloadControl.onContinueLoadingRequestedCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
}
@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 =
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) {
return new FakeMediaSource() {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
return new FakeMediaPeriod(
TrackGroupArray.EMPTY,
allocator,
startPositionUs,
new MediaSourceEventListener.EventDispatcher()) {
@Override
public void prepare(Callback callback, long positionUs) {
// Do nothing to simulate that something wrong happens and onPrepared will not
// be called.
}
@Override
public void maybeThrowPrepareError() 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(() -> preloadMediaSourceReference.get() != null);
assertThat(preloadControl.onPreloadErrorCalled).isTrue();
assertThat(preloadExceptionReference.get()).hasCauseThat().isEqualTo(causeException);
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
assertThat(preloadControl.onContinueLoadingRequestedCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isFalse();
}
@Test
public void
prepareSource_beforeSourceInfoRefreshedForPreloading_onlyInvokeExternalCallerOnSourceInfoRefreshed() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean(false);
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
TestPreloadControl preloadControl = new TestPreloadControl();
FakeMediaSourceFactory mediaSourceFactory = new FakeMediaSourceFactory();
TrackSelector trackSelector = new FakeTrackSelector();
trackSelector.init(() -> {}, bandwidthMeter);
@ -501,41 +525,14 @@ public final class PreloadMediaSourceTest {
shadowOf(Looper.getMainLooper()).idle();
assertThat(externalCallerMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
assertThat(onSourcePreparedCalled.get()).isFalse();
assertThat(onTracksSelectedCalled.get()).isFalse();
assertThat(onUsedByPlayerCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isEqualTo(0);
assertThat(preloadControl.onTrackSelectedCalled).isFalse();
assertThat(preloadControl.onUsedByPlayerCalled).isTrue();
}
@Test
public void prepareSource_afterPreload_immediatelyInvokeExternalCallerOnSourceInfoRefreshed() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean(false);
AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled.set(true);
}
};
TestPreloadControl preloadControl = new TestPreloadControl();
FakeMediaSourceFactory mediaSourceFactory = new FakeMediaSourceFactory();
TrackSelector trackSelector = new FakeTrackSelector();
trackSelector.init(() -> {}, bandwidthMeter);
@ -562,38 +559,15 @@ public final class PreloadMediaSourceTest {
preloadMediaSource.prepareSource(
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
assertThat(onSourcePreparedCalled.get()).isTrue();
assertThat(onTracksSelectedCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
assertThat(preloadControl.onTrackSelectedCalled).isTrue();
assertThat(externalCallerMediaSourceReference.get()).isSameInstanceAs(preloadMediaSource);
assertThat(onUsedByPlayerCalled.get()).isTrue();
assertThat(preloadControl.onUsedByPlayerCalled).isTrue();
}
@Test
public void createPeriodWithSameMediaPeriodIdAndStartPosition_returnExistingPeriod()
throws Exception {
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -639,7 +613,7 @@ public final class PreloadMediaSourceTest {
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mockMediaSourceFactory,
preloadControl,
new TestPreloadControl(),
mockTrackSelector,
bandwidthMeter,
getRendererCapabilities(renderersFactory),
@ -669,36 +643,12 @@ public final class PreloadMediaSourceTest {
MediaSource.MediaPeriodId mediaPeriodId = new MediaSource.MediaPeriodId(periodPosition.first);
preloadMediaSource.createPeriod(mediaPeriodId, allocator, periodPosition.second);
assertThat(onTracksSelectedCalled.get()).isTrue();
verify(internalSourceReference.get()).createPeriod(any(), any(), anyLong());
}
@Test
public void createPeriodWithSameMediaPeriodIdAndDifferentStartPosition_returnNewPeriod()
throws Exception {
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -744,7 +694,7 @@ public final class PreloadMediaSourceTest {
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mockMediaSourceFactory,
preloadControl,
new TestPreloadControl(),
mockTrackSelector,
bandwidthMeter,
getRendererCapabilities(renderersFactory),
@ -775,35 +725,11 @@ public final class PreloadMediaSourceTest {
MediaSource.MediaPeriodId mediaPeriodId = new MediaSource.MediaPeriodId(periodPosition.first);
preloadMediaSource.createPeriod(mediaPeriodId, allocator, periodPosition.second);
assertThat(onTracksSelectedCalled.get()).isTrue();
verify(internalSourceReference.get(), times(2)).createPeriod(any(), any(), anyLong());
}
@Test
public void clear_preloadingPeriodReleased() throws Exception {
AtomicBoolean onTracksSelectedCalled = new AtomicBoolean();
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTracksSelectedCalled.set(true);
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
public void clear_preloadingPeriodReleased() {
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
AtomicBoolean preloadingMediaPeriodReleased = new AtomicBoolean();
when(mockMediaSourceFactory.createMediaSource(any()))
@ -836,7 +762,7 @@ public final class PreloadMediaSourceTest {
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mockMediaSourceFactory,
preloadControl,
new TestPreloadControl(),
trackSelector,
bandwidthMeter,
getRendererCapabilities(renderersFactory),
@ -848,7 +774,7 @@ public final class PreloadMediaSourceTest {
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(onTracksSelectedCalled::get);
shadowOf(Looper.getMainLooper()).idle();
preloadMediaSource.clear();
shadowOf(Looper.getMainLooper()).idle();
@ -858,27 +784,6 @@ public final class PreloadMediaSourceTest {
@Test
public void releaseSourceByAllExternalCallers_preloadNotCalledBefore_releaseInternalSource() {
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -904,7 +809,7 @@ public final class PreloadMediaSourceTest {
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mockMediaSourceFactory,
preloadControl,
new TestPreloadControl(),
trackSelector,
bandwidthMeter,
getRendererCapabilities(renderersFactory),
@ -931,29 +836,7 @@ public final class PreloadMediaSourceTest {
@Test
public void releaseSourceByAllExternalCallers_stillPreloading_notReleaseInternalSource() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
TestPreloadControl preloadControl = new TestPreloadControl();
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -999,7 +882,7 @@ public final class PreloadMediaSourceTest {
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadMediaSource.releaseSource(externalCaller);
assertThat(onSourcePreparedCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
assertThat(externalCallerSourceInfoRefreshedCalled.get()).isTrue();
MediaSource internalSource = internalSourceReference.get();
assertThat(internalSource).isNotNull();
@ -1009,27 +892,6 @@ public final class PreloadMediaSourceTest {
@Test
public void
releaseSourceNotByAllExternalCallers_preloadNotCalledBefore_notReleaseInternalSource() {
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -1055,7 +917,7 @@ public final class PreloadMediaSourceTest {
PreloadMediaSource.Factory preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mockMediaSourceFactory,
preloadControl,
new TestPreloadControl(),
trackSelector,
bandwidthMeter,
getRendererCapabilities(renderersFactory),
@ -1088,29 +950,7 @@ public final class PreloadMediaSourceTest {
@Test
public void releasePreloadMediaSource_notUsedByExternalCallers_releaseInternalSource() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return false;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
TestPreloadControl preloadControl = new TestPreloadControl();
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -1152,7 +992,7 @@ public final class PreloadMediaSourceTest {
preloadMediaSource.releasePreloadMediaSource();
shadowOf(Looper.getMainLooper()).idle();
assertThat(onSourcePreparedCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
MediaSource internalSource = internalSourceReference.get();
assertThat(internalSource).isNotNull();
verify(internalSource).releaseSource(any());
@ -1160,29 +1000,7 @@ public final class PreloadMediaSourceTest {
@Test
public void releasePreloadMediaSource_stillUsedByExternalCallers_releaseInternalSource() {
AtomicBoolean onSourcePreparedCalled = new AtomicBoolean(false);
PreloadMediaSource.PreloadControl preloadControl =
new PreloadMediaSource.PreloadControl() {
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalled.set(true);
return false;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
return false;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
return false;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {}
};
TestPreloadControl preloadControl = new TestPreloadControl();
AtomicReference<MediaSource> internalSourceReference = new AtomicReference<>();
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
when(mockMediaSourceFactory.createMediaSource(any()))
@ -1230,13 +1048,57 @@ public final class PreloadMediaSourceTest {
preloadMediaSource.releasePreloadMediaSource();
shadowOf(Looper.getMainLooper()).idle();
assertThat(onSourcePreparedCalled.get()).isTrue();
assertThat(preloadControl.onSourcePreparedCalledCount).isGreaterThan(0);
assertThat(externalCallerSourceInfoRefreshedCalled.get()).isTrue();
MediaSource internalSource = internalSourceReference.get();
assertThat(internalSource).isNotNull();
verify(internalSource, times(0)).releaseSource(any());
}
private static class TestPreloadControl implements PreloadMediaSource.PreloadControl {
public int onSourcePreparedCalledCount;
public boolean onTrackSelectedCalled;
public boolean onContinueLoadingRequestedCalled;
public boolean onUsedByPlayerCalled;
public boolean onLoadedToTheEndOfSourceCalled;
public boolean onPreloadErrorCalled;
@Override
public boolean onSourcePrepared(PreloadMediaSource mediaSource) {
onSourcePreparedCalledCount++;
return true;
}
@Override
public boolean onTracksSelected(PreloadMediaSource mediaSource) {
onTrackSelectedCalled = true;
return true;
}
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
onContinueLoadingRequestedCalled = true;
return true;
}
@Override
public void onUsedByPlayer(PreloadMediaSource mediaSource) {
onUsedByPlayerCalled = true;
}
@Override
public void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) {
onLoadedToTheEndOfSourceCalled = true;
}
@Override
public void onPreloadError(PreloadException error, PreloadMediaSource mediaSource) {
onPreloadErrorCalled = true;
}
}
private static RendererCapabilities[] getRendererCapabilities(RenderersFactory renderersFactory) {
Renderer[] renderers =
renderersFactory.createRenderers(