Clear the PreloadMediaSource when deprioritized by the preload manager
PiperOrigin-RevId: 633917110
This commit is contained in:
parent
a5c94245f8
commit
e4f0ff8177
@ -265,6 +265,14 @@ public abstract class BasePreloadManager<T> {
|
|||||||
*/
|
*/
|
||||||
protected abstract void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs);
|
protected abstract void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the preloaded data of the given {@link MediaSource}, while not releasing the instance of
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @param mediaSource The media source to clear.
|
||||||
|
*/
|
||||||
|
protected abstract void clearSourceInternal(MediaSource mediaSource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the given {@link MediaSource}.
|
* Releases the given {@link MediaSource}.
|
||||||
*
|
*
|
||||||
@ -292,6 +300,8 @@ public abstract class BasePreloadManager<T> {
|
|||||||
if (targetPreloadStatusOfCurrentPreloadingSource != null) {
|
if (targetPreloadStatusOfCurrentPreloadingSource != null) {
|
||||||
preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs);
|
preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs);
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
clearSourceInternal(preloadingHolder.mediaSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -169,15 +169,19 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
|
|||||||
@Override
|
@Override
|
||||||
protected void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs) {
|
protected void preloadSourceInternal(MediaSource mediaSource, long startPositionsUs) {
|
||||||
checkArgument(mediaSource instanceof PreloadMediaSource);
|
checkArgument(mediaSource instanceof PreloadMediaSource);
|
||||||
PreloadMediaSource preloadMediaSource = (PreloadMediaSource) mediaSource;
|
((PreloadMediaSource) mediaSource).preload(startPositionsUs);
|
||||||
preloadMediaSource.preload(startPositionsUs);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void clearSourceInternal(MediaSource mediaSource) {
|
||||||
|
checkArgument(mediaSource instanceof PreloadMediaSource);
|
||||||
|
((PreloadMediaSource) mediaSource).clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void releaseSourceInternal(MediaSource mediaSource) {
|
protected void releaseSourceInternal(MediaSource mediaSource) {
|
||||||
checkArgument(mediaSource instanceof PreloadMediaSource);
|
checkArgument(mediaSource instanceof PreloadMediaSource);
|
||||||
PreloadMediaSource preloadMediaSource = (PreloadMediaSource) mediaSource;
|
((PreloadMediaSource) mediaSource).releasePreloadMediaSource();
|
||||||
preloadMediaSource.releasePreloadMediaSource();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -202,28 +206,38 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
|
|||||||
private final class SourcePreloadControl implements PreloadMediaSource.PreloadControl {
|
private final class SourcePreloadControl implements PreloadMediaSource.PreloadControl {
|
||||||
@Override
|
@Override
|
||||||
public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) {
|
public boolean onTimelineRefreshed(PreloadMediaSource mediaSource) {
|
||||||
|
// The PreloadMediaSource may have more data preloaded than the target preload status if it
|
||||||
|
// has been preloaded before, thus we set `clearExceededDataFromTargetPreloadStatus` to
|
||||||
|
// `true` to clear the exceeded data.
|
||||||
return continueOrCompletePreloading(
|
return continueOrCompletePreloading(
|
||||||
mediaSource,
|
mediaSource,
|
||||||
/* continueLoadingPredicate= */ status ->
|
/* continueLoadingPredicate= */ status ->
|
||||||
status.getStage() > Status.STAGE_TIMELINE_REFRESHED);
|
status.getStage() > Status.STAGE_TIMELINE_REFRESHED,
|
||||||
|
/* clearExceededDataFromTargetPreloadStatus= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepared(PreloadMediaSource mediaSource) {
|
public boolean onPrepared(PreloadMediaSource mediaSource) {
|
||||||
|
// Set `clearExceededDataFromTargetPreloadStatus` to `false` as clearing the exceeded data
|
||||||
|
// from the status STAGE_SOURCE_PREPARED is not supported.
|
||||||
return continueOrCompletePreloading(
|
return continueOrCompletePreloading(
|
||||||
mediaSource,
|
mediaSource,
|
||||||
/* continueLoadingPredicate= */ status ->
|
/* continueLoadingPredicate= */ status ->
|
||||||
status.getStage() > Status.STAGE_SOURCE_PREPARED);
|
status.getStage() > Status.STAGE_SOURCE_PREPARED,
|
||||||
|
/* clearExceededDataFromTargetPreloadStatus= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContinueLoadingRequested(
|
public boolean onContinueLoadingRequested(
|
||||||
PreloadMediaSource mediaSource, long bufferedPositionUs) {
|
PreloadMediaSource mediaSource, long bufferedPositionUs) {
|
||||||
|
// Set `clearExceededDataFromTargetPreloadStatus` to `false` as clearing the exceeded data
|
||||||
|
// from the status STAGE_LOADED_TO_POSITION_MS is not supported.
|
||||||
return continueOrCompletePreloading(
|
return continueOrCompletePreloading(
|
||||||
mediaSource,
|
mediaSource,
|
||||||
/* continueLoadingPredicate= */ 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),
|
||||||
|
/* clearExceededDataFromTargetPreloadStatus= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -237,7 +251,9 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean continueOrCompletePreloading(
|
private boolean continueOrCompletePreloading(
|
||||||
MediaSource mediaSource, Predicate<Status> continueLoadingPredicate) {
|
PreloadMediaSource mediaSource,
|
||||||
|
Predicate<Status> continueLoadingPredicate,
|
||||||
|
boolean clearExceededDataFromTargetPreloadStatus) {
|
||||||
@Nullable
|
@Nullable
|
||||||
TargetPreloadStatusControl.PreloadStatus targetPreloadStatus =
|
TargetPreloadStatusControl.PreloadStatus targetPreloadStatus =
|
||||||
getTargetPreloadStatus(mediaSource);
|
getTargetPreloadStatus(mediaSource);
|
||||||
@ -246,8 +262,11 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
|
|||||||
if (continueLoadingPredicate.apply(checkNotNull(status))) {
|
if (continueLoadingPredicate.apply(checkNotNull(status))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
onPreloadCompleted(mediaSource);
|
if (clearExceededDataFromTargetPreloadStatus) {
|
||||||
|
clearSourceInternal(mediaSource);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
onPreloadCompleted(mediaSource);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
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_LOADED_TO_POSITION_MS;
|
||||||
|
import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_SOURCE_PREPARED;
|
||||||
import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED;
|
import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED;
|
||||||
|
import static androidx.media3.test.utils.FakeMediaSourceFactory.DEFAULT_WINDOW_UID;
|
||||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
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 java.lang.Math.abs;
|
||||||
@ -29,19 +31,26 @@ import android.content.Context;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.AdPlaybackState;
|
||||||
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.datasource.DefaultDataSource;
|
||||||
|
import androidx.media3.datasource.TransferListener;
|
||||||
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;
|
||||||
import androidx.media3.exoplayer.RenderersFactory;
|
import androidx.media3.exoplayer.RenderersFactory;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||||
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
|
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
||||||
|
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||||
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;
|
||||||
@ -49,12 +58,15 @@ import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
|||||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||||
import androidx.media3.test.utils.FakeAudioRenderer;
|
import androidx.media3.test.utils.FakeAudioRenderer;
|
||||||
|
import androidx.media3.test.utils.FakeMediaPeriod;
|
||||||
import androidx.media3.test.utils.FakeMediaSource;
|
import androidx.media3.test.utils.FakeMediaSource;
|
||||||
import androidx.media3.test.utils.FakeMediaSourceFactory;
|
import androidx.media3.test.utils.FakeMediaSourceFactory;
|
||||||
import androidx.media3.test.utils.FakeRenderer;
|
import androidx.media3.test.utils.FakeRenderer;
|
||||||
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
import androidx.media3.test.utils.FakeVideoRenderer;
|
import androidx.media3.test.utils.FakeVideoRenderer;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -66,6 +78,7 @@ import org.mockito.Mock;
|
|||||||
/** Unit test for {@link DefaultPreloadManager}. */
|
/** Unit test for {@link DefaultPreloadManager}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class DefaultPreloadManagerTest {
|
public class DefaultPreloadManagerTest {
|
||||||
|
|
||||||
@Mock private TargetPreloadStatusControl<Integer> mockTargetPreloadStatusControl;
|
@Mock private TargetPreloadStatusControl<Integer> mockTargetPreloadStatusControl;
|
||||||
private TrackSelector trackSelector;
|
private TrackSelector trackSelector;
|
||||||
private Allocator allocator;
|
private Allocator allocator;
|
||||||
@ -432,6 +445,131 @@ public class DefaultPreloadManagerTest {
|
|||||||
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2);
|
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidate_clearsDeprioritizedSources() throws Exception {
|
||||||
|
final AtomicInteger currentPlayingIndex = new AtomicInteger();
|
||||||
|
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
|
||||||
|
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
|
||||||
|
rankingData -> {
|
||||||
|
targetPreloadStatusControlCallStates.add(rankingData);
|
||||||
|
if (abs(rankingData - currentPlayingIndex.get()) <= 2) {
|
||||||
|
return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
|
||||||
|
} else if (abs(rankingData - currentPlayingIndex.get()) == 3) {
|
||||||
|
return new DefaultPreloadManager.Status(STAGE_TIMELINE_REFRESHED);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
|
||||||
|
ArrayList<String> releasedPreloadingPeriodMediaIds = new ArrayList<>();
|
||||||
|
when(mockMediaSourceFactory.createMediaSource(any()))
|
||||||
|
.thenAnswer(
|
||||||
|
invocation -> {
|
||||||
|
MediaItem mediaItem = invocation.getArgument(0);
|
||||||
|
FakeTimeline.TimelineWindowDefinition timelineWindowDefinition =
|
||||||
|
new FakeTimeline.TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ DEFAULT_WINDOW_UID,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* isLive= */ false,
|
||||||
|
/* isPlaceholder= */ false,
|
||||||
|
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||||
|
/* defaultPositionUs= */ 2 * C.MICROS_PER_SECOND,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(123456789),
|
||||||
|
ImmutableList.of(AdPlaybackState.NONE),
|
||||||
|
mediaItem);
|
||||||
|
return new FakeMediaSource(new FakeTimeline(timelineWindowDefinition)) {
|
||||||
|
@Override
|
||||||
|
protected MediaPeriod createMediaPeriod(
|
||||||
|
MediaPeriodId id,
|
||||||
|
TrackGroupArray trackGroupArray,
|
||||||
|
Allocator allocator,
|
||||||
|
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
|
||||||
|
DrmSessionManager drmSessionManager,
|
||||||
|
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
|
||||||
|
@Nullable TransferListener transferListener) {
|
||||||
|
return new FakeMediaPeriod(
|
||||||
|
trackGroupArray,
|
||||||
|
allocator,
|
||||||
|
FakeTimeline.TimelineWindowDefinition
|
||||||
|
.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||||
|
mediaSourceEventDispatcher) {
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
releasedPreloadingPeriodMediaIds.add(mediaItem.mediaId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
DefaultPreloadManager preloadManager =
|
||||||
|
new DefaultPreloadManager(
|
||||||
|
targetPreloadStatusControl,
|
||||||
|
mockMediaSourceFactory,
|
||||||
|
trackSelector,
|
||||||
|
bandwidthMeter,
|
||||||
|
rendererCapabilitiesListFactory,
|
||||||
|
allocator,
|
||||||
|
Util.getCurrentOrMainLooper());
|
||||||
|
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
|
||||||
|
MediaItem mediaItem0 =
|
||||||
|
mediaItemBuilder
|
||||||
|
.setMediaId("mediaId0")
|
||||||
|
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem1 =
|
||||||
|
mediaItemBuilder
|
||||||
|
.setMediaId("mediaId1")
|
||||||
|
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem2 =
|
||||||
|
mediaItemBuilder
|
||||||
|
.setMediaId("mediaId2")
|
||||||
|
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem3 =
|
||||||
|
mediaItemBuilder
|
||||||
|
.setMediaId("mediaId3")
|
||||||
|
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem4 =
|
||||||
|
mediaItemBuilder
|
||||||
|
.setMediaId("mediaId4")
|
||||||
|
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
|
||||||
|
.build();
|
||||||
|
preloadManager.add(mediaItem0, /* rankingData= */ 0);
|
||||||
|
preloadManager.add(mediaItem1, /* rankingData= */ 1);
|
||||||
|
preloadManager.add(mediaItem2, /* rankingData= */ 2);
|
||||||
|
preloadManager.add(mediaItem3, /* rankingData= */ 3);
|
||||||
|
preloadManager.add(mediaItem4, /* rankingData= */ 4);
|
||||||
|
currentPlayingIndex.set(C.INDEX_UNSET);
|
||||||
|
|
||||||
|
preloadManager.invalidate();
|
||||||
|
runMainLooperUntil(() -> targetPreloadStatusControlCallStates.size() == 5);
|
||||||
|
|
||||||
|
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2, 3, 4).inOrder();
|
||||||
|
assertThat(releasedPreloadingPeriodMediaIds).isEmpty();
|
||||||
|
|
||||||
|
targetPreloadStatusControlCallStates.clear();
|
||||||
|
PreloadMediaSource preloadMediaSource4 =
|
||||||
|
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem4);
|
||||||
|
// Simulate that preloadMediaSource4 is using by the player.
|
||||||
|
preloadMediaSource4.prepareSource(
|
||||||
|
(source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
|
||||||
|
currentPlayingIndex.set(4);
|
||||||
|
preloadManager.setCurrentPlayingIndex(4);
|
||||||
|
|
||||||
|
preloadManager.invalidate();
|
||||||
|
runMainLooperUntil(() -> releasedPreloadingPeriodMediaIds.size() == 2);
|
||||||
|
|
||||||
|
assertThat(targetPreloadStatusControlCallStates).containsExactly(4, 3, 2, 1, 0).inOrder();
|
||||||
|
// The sources for mediaItem4, mediaItem3 and mediaItem2 either got used by the player or
|
||||||
|
// preload more after the second invalidate() call because their priorities increased. Thus the
|
||||||
|
// sources got cleared are the ones for mediaItem1 and mediaItem0 due to their decreased
|
||||||
|
// priorities.
|
||||||
|
assertThat(releasedPreloadingPeriodMediaIds).containsExactly("mediaId1", "mediaId0");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void removeByMediaItems_correspondingHeldSourceRemovedAndReleased() {
|
public void removeByMediaItems_correspondingHeldSourceRemovedAndReleased() {
|
||||||
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
|
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user