From 1a5f57e9eb4cd21f85366d2fe7d2569f541e0963 Mon Sep 17 00:00:00 2001 From: tianyifeng Date: Wed, 8 May 2024 06:14:19 -0700 Subject: [PATCH] Complete preloading when the period has loaded to the end of the source When the period has loaded to the end of the source, the `period.getBufferedPositionUs` will be set to `C.TIME_END_OF_SOURCE`, which is a negative value. Thus, the original `continueLoadingPredicate` will never turn to `false`, as the `bufferedPositionUs` is definitely less than the target preload position that is expected to be positive. In this change, we added `PreloadMediaSource.PreloadControl.onLoadedToTheEndOfSource(PreloadMediaSource)` to indicate that the source has loaded to the end. This allows the `DefaultPreloadManager` and the custom `PreloadMediaSource.PreloadControl` implementations to preload the next source or take other actions. This bug was not revealed by the the `DefaultPreloadManagerTest` because the related tests were all using the `FakeMediaSource` and only setting the preload target to `STAGE_TIMELINE_REFRESHED`. Thus, the tests for testing the `invalidate()` behaviors were modified to use the real progressive media whenever possible, unless we have to use `FakeMediaSource` to squeeze a chance to do more operations between the preloading of sources to test some special scenarios. PiperOrigin-RevId: 631776442 --- RELEASENOTES.md | 6 + .../source/preload/DefaultPreloadManager.java | 19 ++- .../source/preload/PreloadMediaSource.java | 18 ++- .../preload/DefaultPreloadManagerTest.java | 143 ++++++++++-------- .../preload/PreloadMediaSourceTest.java | 74 +++++++++ 5 files changed, 192 insertions(+), 68 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b06cdf2c94..d9e76c6bf0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -18,6 +18,12 @@ * Fix issue with updating the last rebuffer time which resulted in incorrect `bs` (buffer starvation) key in CMCD ([#1124](https://github.com/androidx/media/issues/1124)). + * Add + `PreloadMediaSource.PreloadControl.onLoadedToTheEndOfSource(PreloadMediaSource)` + to indicate that the source has loaded to the end. This allows the + `DefaultPreloadManager` and the custom + `PreloadMediaSource.PreloadControl` implementations to preload the next + source or take other actions. * Transformer: * Work around a decoder bug where the number of audio channels was capped at stereo when handling PCM input. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java index cb094a04fa..465829a3cc 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java @@ -203,13 +203,17 @@ public final class DefaultPreloadManager extends BasePreloadManager { @Override public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) { return continueOrCompletePreloading( - mediaSource, status -> status.getStage() > Status.STAGE_TIMELINE_REFRESHED); + mediaSource, + /* continueLoadingPredicate= */ status -> + status.getStage() > Status.STAGE_TIMELINE_REFRESHED); } @Override public boolean onPrepared(PreloadMediaSource mediaSource) { return continueOrCompletePreloading( - mediaSource, status -> status.getStage() > Status.STAGE_SOURCE_PREPARED); + mediaSource, + /* continueLoadingPredicate= */ status -> + status.getStage() > Status.STAGE_SOURCE_PREPARED); } @Override @@ -217,9 +221,9 @@ public final class DefaultPreloadManager extends BasePreloadManager { PreloadMediaSource mediaSource, long bufferedPositionUs) { return continueOrCompletePreloading( mediaSource, - status -> - (status.getStage() == Status.STAGE_LOADED_TO_POSITION_MS - && status.getValue() > Util.usToMs(bufferedPositionUs))); + /* continueLoadingPredicate= */ status -> + status.getStage() == Status.STAGE_LOADED_TO_POSITION_MS + && status.getValue() > Util.usToMs(bufferedPositionUs)); } @Override @@ -227,6 +231,11 @@ public final class DefaultPreloadManager extends BasePreloadManager { onPreloadCompleted(mediaSource); } + @Override + public void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) { + onPreloadCompleted(mediaSource); + } + private boolean continueOrCompletePreloading( MediaSource mediaSource, Predicate continueLoadingPredicate) { @Nullable diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreloadMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreloadMediaSource.java index 6b2fd13d8d..162bb4a05f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreloadMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreloadMediaSource.java @@ -81,9 +81,12 @@ public final class PreloadMediaSource extends WrappingMediaSource { /** * Called from {@link PreloadMediaSource} when it requests to continue loading. * + *

If fully loaded, then {@link #onLoadedToTheEndOfSource(PreloadMediaSource)} will be called + * instead. + * * @param mediaSource The {@link PreloadMediaSource} that requests to continue loading. * @param bufferedPositionUs An estimate of the absolute position in microseconds up to which - * data is buffered, or {@link C#TIME_END_OF_SOURCE} if the track is fully buffered. + * data is buffered. */ boolean onContinueLoadingRequested(PreloadMediaSource mediaSource, long bufferedPositionUs); @@ -93,6 +96,15 @@ public final class PreloadMediaSource extends WrappingMediaSource { * @param mediaSource The {@link PreloadMediaSource} that the player starts using. */ void onUsedByPlayer(PreloadMediaSource mediaSource); + + /** + * Called from {@link PreloadMediaSource} when it has loaded to the end of source. + * + *

The default implementation is a no-op. + * + * @param mediaSource The {@link PreloadMediaSource} that has loaded to the end of source. + */ + default void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) {} } /** Factory for {@link PreloadMediaSource}. */ @@ -402,7 +414,9 @@ public final class PreloadMediaSource extends WrappingMediaSource { return; } PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod; - if (!prepared + if (prepared && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE) { + preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this); + } else if (!prepared || preloadControl.onContinueLoadingRequested( PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) { preloadMediaPeriod.continueLoading( diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java index 833ea29ef1..631c07bb48 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java @@ -15,19 +15,25 @@ */ package androidx.media3.exoplayer.source.preload; +import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS; +import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED; +import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.abs; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.content.Context; +import android.net.Uri; import android.os.Looper; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.util.SystemClock; import androidx.media3.common.util.Util; +import androidx.media3.datasource.DefaultDataSource; import androidx.media3.exoplayer.DefaultRendererCapabilitiesList; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.RendererCapabilitiesList; @@ -35,6 +41,7 @@ import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.trackselection.TrackSelector; import androidx.media3.exoplayer.upstream.Allocator; @@ -50,6 +57,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -161,20 +169,26 @@ public class DefaultPreloadManagerTest { } @Test - public void - invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { + public void invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() + throws Exception { ArrayList targetPreloadStatusControlCallStates = new ArrayList<>(); + AtomicInteger currentPlayingItemIndex = new AtomicInteger(); TargetPreloadStatusControl targetPreloadStatusControl = rankingData -> { targetPreloadStatusControlCallStates.add(rankingData); - return new DefaultPreloadManager.Status( - DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + if (abs(rankingData - currentPlayingItemIndex.get()) == 1) { + return new DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 100L); + } else { + return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); + } }; - FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); + ProgressiveMediaSource.Factory mediaSourceFactory = + new ProgressiveMediaSource.Factory( + new DefaultDataSource.Factory(ApplicationProvider.getApplicationContext())); DefaultPreloadManager preloadManager = new DefaultPreloadManager( targetPreloadStatusControl, - fakeMediaSourceFactory, + mediaSourceFactory, trackSelector, bandwidthMeter, rendererCapabilitiesListFactory, @@ -182,44 +196,51 @@ public class DefaultPreloadManagerTest { Util.getCurrentOrMainLooper()); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem mediaItem0 = - mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build(); + mediaItemBuilder + .setMediaId("mediaId0") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem1 = - mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); + mediaItemBuilder + .setMediaId("mediaId1") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem2 = - mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); + mediaItemBuilder + .setMediaId("mediaId2") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); preloadManager.add(mediaItem0, /* rankingData= */ 0); - FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource0.setAllowPreparation(false); preloadManager.add(mediaItem1, /* rankingData= */ 1); - FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource1.setAllowPreparation(false); preloadManager.add(mediaItem2, /* rankingData= */ 2); - FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource2.setAllowPreparation(false); preloadManager.invalidate(); - shadowOf(Looper.getMainLooper()).idle(); + runMainLooperUntil(() -> targetPreloadStatusControlCallStates.size() == 3); - assertThat(targetPreloadStatusControlCallStates).containsExactly(0); - wrappedMediaSource0.setAllowPreparation(true); - shadowOf(Looper.getMainLooper()).idle(); - assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder(); + assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2).inOrder(); } @Test - public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { + public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() + throws Exception { ArrayList targetPreloadStatusControlCallStates = new ArrayList<>(); + AtomicInteger currentPlayingItemIndex = new AtomicInteger(); TargetPreloadStatusControl targetPreloadStatusControl = rankingData -> { targetPreloadStatusControlCallStates.add(rankingData); - return new DefaultPreloadManager.Status( - DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + if (abs(rankingData - currentPlayingItemIndex.get()) == 1) { + return new DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 100L); + } else { + return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); + } }; - FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); + ProgressiveMediaSource.Factory mediaSourceFactory = + new ProgressiveMediaSource.Factory( + new DefaultDataSource.Factory(ApplicationProvider.getApplicationContext())); DefaultPreloadManager preloadManager = new DefaultPreloadManager( targetPreloadStatusControl, - fakeMediaSourceFactory, + mediaSourceFactory, trackSelector, bandwidthMeter, rendererCapabilitiesListFactory, @@ -227,32 +248,33 @@ public class DefaultPreloadManagerTest { Util.getCurrentOrMainLooper()); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem mediaItem0 = - mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build(); + mediaItemBuilder + .setMediaId("mediaId0") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem1 = - mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); + mediaItemBuilder + .setMediaId("mediaId1") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem2 = - mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); + mediaItemBuilder + .setMediaId("mediaId2") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); preloadManager.add(mediaItem0, /* rankingData= */ 0); - FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource0.setAllowPreparation(false); preloadManager.add(mediaItem1, /* rankingData= */ 1); - FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource1.setAllowPreparation(false); preloadManager.add(mediaItem2, /* rankingData= */ 2); - FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource2.setAllowPreparation(false); PreloadMediaSource preloadMediaSource2 = (PreloadMediaSource) preloadManager.getMediaSource(mediaItem2); preloadMediaSource2.prepareSource( (source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET); preloadManager.setCurrentPlayingIndex(2); + currentPlayingItemIndex.set(2); preloadManager.invalidate(); - shadowOf(Looper.getMainLooper()).idle(); + runMainLooperUntil(() -> targetPreloadStatusControlCallStates.size() == 3); - assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1); - wrappedMediaSource1.setAllowPreparation(true); - shadowOf(Looper.getMainLooper()).idle(); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder(); } @@ -262,8 +284,7 @@ public class DefaultPreloadManagerTest { TargetPreloadStatusControl targetPreloadStatusControl = rankingData -> { targetPreloadStatusControlCallStates.add(rankingData); - return new DefaultPreloadManager.Status( - DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); }; FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); DefaultPreloadManager preloadManager = @@ -306,8 +327,7 @@ public class DefaultPreloadManagerTest { TargetPreloadStatusControl targetPreloadStatusControl = rankingData -> { targetPreloadStatusControlCallStates.add(rankingData); - return new DefaultPreloadManager.Status( - DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); }; FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); DefaultPreloadManager preloadManager = @@ -374,11 +394,13 @@ public class DefaultPreloadManagerTest { targetPreloadStatusControlCallStates.add(rankingData); return null; }; - FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); + ProgressiveMediaSource.Factory mediaSourceFactory = + new ProgressiveMediaSource.Factory( + new DefaultDataSource.Factory(ApplicationProvider.getApplicationContext())); DefaultPreloadManager preloadManager = new DefaultPreloadManager( targetPreloadStatusControl, - fakeMediaSourceFactory, + mediaSourceFactory, trackSelector, bandwidthMeter, rendererCapabilitiesListFactory, @@ -386,20 +408,23 @@ public class DefaultPreloadManagerTest { Util.getCurrentOrMainLooper()); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem mediaItem0 = - mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build(); + mediaItemBuilder + .setMediaId("mediaId0") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem1 = - mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); + mediaItemBuilder + .setMediaId("mediaId1") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); MediaItem mediaItem2 = - mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); + mediaItemBuilder + .setMediaId("mediaId2") + .setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4")) + .build(); preloadManager.add(mediaItem0, /* rankingData= */ 0); - FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource0.setAllowPreparation(false); preloadManager.add(mediaItem1, /* rankingData= */ 1); - FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource1.setAllowPreparation(false); preloadManager.add(mediaItem2, /* rankingData= */ 2); - FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); - wrappedMediaSource2.setAllowPreparation(false); preloadManager.invalidate(); shadowOf(Looper.getMainLooper()).idle(); @@ -410,8 +435,7 @@ public class DefaultPreloadManagerTest { @Test public void removeByMediaItems_correspondingHeldSourceRemovedAndReleased() { TargetPreloadStatusControl targetPreloadStatusControl = - rankingData -> - new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); DefaultPreloadManager preloadManager = new DefaultPreloadManager( @@ -462,8 +486,7 @@ public class DefaultPreloadManagerTest { @Test public void removeByMediaSources_heldSourceRemovedAndReleased() { TargetPreloadStatusControl targetPreloadStatusControl = - rankingData -> - new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); DefaultPreloadManager preloadManager = new DefaultPreloadManager( @@ -521,8 +544,7 @@ public class DefaultPreloadManagerTest { @Test public void reset_returnZeroCount_sourcesButNotRendererCapabilitiesListReleased() { TargetPreloadStatusControl targetPreloadStatusControl = - rankingData -> - new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); List underlyingRenderers = new ArrayList<>(); RenderersFactory renderersFactory = @@ -593,8 +615,7 @@ public class DefaultPreloadManagerTest { @Test public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased() { TargetPreloadStatusControl targetPreloadStatusControl = - rankingData -> - new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); List underlyingRenderers = new ArrayList<>(); RenderersFactory renderersFactory = diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/PreloadMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/PreloadMediaSourceTest.java index b3c2bdf40e..f0f2bda45a 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/PreloadMediaSourceTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/PreloadMediaSourceTest.java @@ -362,6 +362,80 @@ public final class PreloadMediaSourceTest { assertThat(onUsedByPlayerCalled.get()).isTrue(); } + @Test + public void preload_loadToTheEndOfSource() throws Exception { + AtomicBoolean onTimelineRefreshedCalled = new AtomicBoolean(); + AtomicBoolean onPreparedCalled = new AtomicBoolean(); + AtomicBoolean onContinueLoadingRequestedCalled = new AtomicBoolean(); + AtomicBoolean onLoadedToTheEndOfSourceCalled = new AtomicBoolean(); + AtomicBoolean onUsedByPlayerCalled = new AtomicBoolean(); + PreloadMediaSource.PreloadControl preloadControl = + new PreloadMediaSource.PreloadControl() { + @Override + public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) { + onTimelineRefreshedCalled.set(true); + return true; + } + + @Override + public boolean onPrepared(PreloadMediaSource mediaSource) { + onPreparedCalled.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); + } + + @Override + public void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) { + onLoadedToTheEndOfSourceCalled.set(true); + } + }; + ProgressiveMediaSource.Factory mediaSourceFactory = + new ProgressiveMediaSource.Factory( + new DefaultDataSource.Factory(ApplicationProvider.getApplicationContext())); + mediaSourceFactory.setContinueLoadingCheckIntervalBytes(LOADING_CHECK_INTERVAL_BYTES); + 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(onLoadedToTheEndOfSourceCalled::get); + + assertThat(onTimelineRefreshedCalled.get()).isTrue(); + assertThat(onPreparedCalled.get()).isTrue(); + assertThat(onContinueLoadingRequestedCalled.get()).isTrue(); + assertThat(onUsedByPlayerCalled.get()).isFalse(); + } + @Test public void prepareSource_beforeSourceInfoRefreshedForPreloading_onlyInvokeExternalCallerOnSourceInfoRefreshed() {