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
This commit is contained in:
tianyifeng 2024-05-08 06:14:19 -07:00 committed by Copybara-Service
parent 9ece3932e8
commit 1a5f57e9eb
5 changed files with 192 additions and 68 deletions

View file

@ -18,6 +18,12 @@
* Fix issue with updating the last rebuffer time which resulted in * Fix issue with updating the last rebuffer time which resulted in
incorrect `bs` (buffer starvation) key in CMCD incorrect `bs` (buffer starvation) key in CMCD
([#1124](https://github.com/androidx/media/issues/1124)). ([#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: * Transformer:
* Work around a decoder bug where the number of audio channels was capped * Work around a decoder bug where the number of audio channels was capped
at stereo when handling PCM input. at stereo when handling PCM input.

View file

@ -203,13 +203,17 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Override @Override
public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) { public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) {
return continueOrCompletePreloading( return continueOrCompletePreloading(
mediaSource, status -> status.getStage() > Status.STAGE_TIMELINE_REFRESHED); mediaSource,
/* continueLoadingPredicate= */ status ->
status.getStage() > Status.STAGE_TIMELINE_REFRESHED);
} }
@Override @Override
public boolean onPrepared(PreloadMediaSource mediaSource) { public boolean onPrepared(PreloadMediaSource mediaSource) {
return continueOrCompletePreloading( return continueOrCompletePreloading(
mediaSource, status -> status.getStage() > Status.STAGE_SOURCE_PREPARED); mediaSource,
/* continueLoadingPredicate= */ status ->
status.getStage() > Status.STAGE_SOURCE_PREPARED);
} }
@Override @Override
@ -217,9 +221,9 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
PreloadMediaSource mediaSource, long bufferedPositionUs) { PreloadMediaSource mediaSource, long bufferedPositionUs) {
return continueOrCompletePreloading( return continueOrCompletePreloading(
mediaSource, mediaSource,
status -> /* continueLoadingPredicate= */ status ->
(status.getStage() == Status.STAGE_LOADED_TO_POSITION_MS status.getStage() == Status.STAGE_LOADED_TO_POSITION_MS
&& status.getValue() > Util.usToMs(bufferedPositionUs))); && status.getValue() > Util.usToMs(bufferedPositionUs));
} }
@Override @Override
@ -227,6 +231,11 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
onPreloadCompleted(mediaSource); onPreloadCompleted(mediaSource);
} }
@Override
public void onLoadedToTheEndOfSource(PreloadMediaSource mediaSource) {
onPreloadCompleted(mediaSource);
}
private boolean continueOrCompletePreloading( private boolean continueOrCompletePreloading(
MediaSource mediaSource, Predicate<Status> continueLoadingPredicate) { MediaSource mediaSource, Predicate<Status> continueLoadingPredicate) {
@Nullable @Nullable

View file

@ -81,9 +81,12 @@ public final class PreloadMediaSource extends WrappingMediaSource {
/** /**
* Called from {@link PreloadMediaSource} when it requests to continue loading. * Called from {@link PreloadMediaSource} when it requests to continue loading.
* *
* <p>If fully loaded, then {@link #onLoadedToTheEndOfSource(PreloadMediaSource)} will be called
* instead.
*
* @param mediaSource The {@link PreloadMediaSource} that requests to continue loading. * @param mediaSource The {@link PreloadMediaSource} that requests to continue loading.
* @param bufferedPositionUs An estimate of the absolute position in microseconds up to which * @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); 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. * @param mediaSource The {@link PreloadMediaSource} that the player starts using.
*/ */
void onUsedByPlayer(PreloadMediaSource mediaSource); void onUsedByPlayer(PreloadMediaSource mediaSource);
/**
* Called from {@link PreloadMediaSource} when it has loaded to the end of source.
*
* <p>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}. */ /** Factory for {@link PreloadMediaSource}. */
@ -402,7 +414,9 @@ public final class PreloadMediaSource extends WrappingMediaSource {
return; return;
} }
PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod; PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod;
if (!prepared if (prepared && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE) {
preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this);
} else if (!prepared
|| preloadControl.onContinueLoadingRequested( || preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) { PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) {
preloadMediaPeriod.continueLoading( preloadMediaPeriod.continueLoading(

View file

@ -15,19 +15,25 @@
*/ */
package androidx.media3.exoplayer.source.preload; 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 com.google.common.truth.Truth.assertThat;
import static java.lang.Math.abs;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf; import static org.robolectric.Shadows.shadowOf;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.util.SystemClock; import androidx.media3.common.util.SystemClock;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList; import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RendererCapabilitiesList; import androidx.media3.exoplayer.RendererCapabilitiesList;
@ -35,6 +41,7 @@ import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelector; import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.Allocator;
@ -50,6 +57,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -161,20 +169,26 @@ public class DefaultPreloadManagerTest {
} }
@Test @Test
public void public void invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder()
invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { throws Exception {
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
AtomicInteger currentPlayingItemIndex = new AtomicInteger();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( if (abs(rankingData - currentPlayingItemIndex.get()) == 1) {
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); 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 = DefaultPreloadManager preloadManager =
new DefaultPreloadManager( new DefaultPreloadManager(
targetPreloadStatusControl, targetPreloadStatusControl,
fakeMediaSourceFactory, mediaSourceFactory,
trackSelector, trackSelector,
bandwidthMeter, bandwidthMeter,
rendererCapabilitiesListFactory, rendererCapabilitiesListFactory,
@ -182,44 +196,51 @@ public class DefaultPreloadManagerTest {
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 = 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 = 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 = 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); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
preloadManager.add(mediaItem1, /* rankingData= */ 1); preloadManager.add(mediaItem1, /* rankingData= */ 1);
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false);
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); runMainLooperUntil(() -> targetPreloadStatusControlCallStates.size() == 3);
assertThat(targetPreloadStatusControlCallStates).containsExactly(0); assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2).inOrder();
wrappedMediaSource0.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
} }
@Test @Test
public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder()
throws Exception {
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
AtomicInteger currentPlayingItemIndex = new AtomicInteger();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( if (abs(rankingData - currentPlayingItemIndex.get()) == 1) {
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); 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 = DefaultPreloadManager preloadManager =
new DefaultPreloadManager( new DefaultPreloadManager(
targetPreloadStatusControl, targetPreloadStatusControl,
fakeMediaSourceFactory, mediaSourceFactory,
trackSelector, trackSelector,
bandwidthMeter, bandwidthMeter,
rendererCapabilitiesListFactory, rendererCapabilitiesListFactory,
@ -227,32 +248,33 @@ public class DefaultPreloadManagerTest {
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 = 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 = 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 = 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); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
preloadManager.add(mediaItem1, /* rankingData= */ 1); preloadManager.add(mediaItem1, /* rankingData= */ 1);
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false);
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
PreloadMediaSource preloadMediaSource2 = PreloadMediaSource preloadMediaSource2 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem2); (PreloadMediaSource) preloadManager.getMediaSource(mediaItem2);
preloadMediaSource2.prepareSource( preloadMediaSource2.prepareSource(
(source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET); (source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(2); preloadManager.setCurrentPlayingIndex(2);
currentPlayingItemIndex.set(2);
preloadManager.invalidate(); 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(); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder();
} }
@ -262,8 +284,7 @@ public class DefaultPreloadManagerTest {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory();
DefaultPreloadManager preloadManager = DefaultPreloadManager preloadManager =
@ -306,8 +327,7 @@ public class DefaultPreloadManagerTest {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory();
DefaultPreloadManager preloadManager = DefaultPreloadManager preloadManager =
@ -374,11 +394,13 @@ public class DefaultPreloadManagerTest {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return null; return null;
}; };
FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory(); ProgressiveMediaSource.Factory mediaSourceFactory =
new ProgressiveMediaSource.Factory(
new DefaultDataSource.Factory(ApplicationProvider.getApplicationContext()));
DefaultPreloadManager preloadManager = DefaultPreloadManager preloadManager =
new DefaultPreloadManager( new DefaultPreloadManager(
targetPreloadStatusControl, targetPreloadStatusControl,
fakeMediaSourceFactory, mediaSourceFactory,
trackSelector, trackSelector,
bandwidthMeter, bandwidthMeter,
rendererCapabilitiesListFactory, rendererCapabilitiesListFactory,
@ -386,20 +408,23 @@ public class DefaultPreloadManagerTest {
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 = 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 = 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 = 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); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
preloadManager.add(mediaItem1, /* rankingData= */ 1); preloadManager.add(mediaItem1, /* rankingData= */ 1);
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false);
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -410,8 +435,7 @@ public class DefaultPreloadManagerTest {
@Test @Test
public void removeByMediaItems_correspondingHeldSourceRemovedAndReleased() { public void removeByMediaItems_correspondingHeldSourceRemovedAndReleased() {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
DefaultPreloadManager preloadManager = DefaultPreloadManager preloadManager =
new DefaultPreloadManager( new DefaultPreloadManager(
@ -462,8 +486,7 @@ public class DefaultPreloadManagerTest {
@Test @Test
public void removeByMediaSources_heldSourceRemovedAndReleased() { public void removeByMediaSources_heldSourceRemovedAndReleased() {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
DefaultPreloadManager preloadManager = DefaultPreloadManager preloadManager =
new DefaultPreloadManager( new DefaultPreloadManager(
@ -521,8 +544,7 @@ public class DefaultPreloadManagerTest {
@Test @Test
public void reset_returnZeroCount_sourcesButNotRendererCapabilitiesListReleased() { public void reset_returnZeroCount_sourcesButNotRendererCapabilitiesListReleased() {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
List<FakeRenderer> underlyingRenderers = new ArrayList<>(); List<FakeRenderer> underlyingRenderers = new ArrayList<>();
RenderersFactory renderersFactory = RenderersFactory renderersFactory =
@ -593,8 +615,7 @@ public class DefaultPreloadManagerTest {
@Test @Test
public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased() { public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased() {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> rankingData -> new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
List<FakeRenderer> underlyingRenderers = new ArrayList<>(); List<FakeRenderer> underlyingRenderers = new ArrayList<>();
RenderersFactory renderersFactory = RenderersFactory renderersFactory =

View file

@ -362,6 +362,80 @@ public final class PreloadMediaSourceTest {
assertThat(onUsedByPlayerCalled.get()).isTrue(); 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 @Test
public void public void
prepareSource_beforeSourceInfoRefreshedForPreloading_onlyInvokeExternalCallerOnSourceInfoRefreshed() { prepareSource_beforeSourceInfoRefreshedForPreloading_onlyInvokeExternalCallerOnSourceInfoRefreshed() {