Allow target preload status to be null to indicate not to preload

PiperOrigin-RevId: 619246259
This commit is contained in:
tianyifeng 2024-03-26 10:59:30 -07:00 committed by Copybara-Service
parent 98dac54816
commit 8f2f3bb7e4
5 changed files with 107 additions and 39 deletions

View File

@ -30,6 +30,9 @@
should be shown. Custom `SimpleDecoder` implementations can check
`isAtLeastOutputStartTimeUs` if needed or mark other buffers with
`DecoderOutputBuffer.shouldBeSkipped` to skip them.
* Allow a null value to be returned by
`TargetPreloadStatusControl.getTargetPreloadStatus(T)` to indicate not
to preload a `MediaSource` with the given `rankingData`.
* Transformer:
* Add `audioConversionProcess` and `videoConversionProcess` to
`ExportResult` indicating how the respective track in the output file

View File

@ -126,7 +126,9 @@ public abstract class BasePreloadManager<T> {
synchronized (lock) {
sourceHolderPriorityQueue.clear();
sourceHolderPriorityQueue.addAll(mediaItemMediaSourceHolderMap.values());
maybeStartPreloadNextSource();
while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource()) {
sourceHolderPriorityQueue.poll();
}
}
}
@ -180,8 +182,9 @@ public abstract class BasePreloadManager<T> {
|| checkNotNull(sourceHolderPriorityQueue.peek()).mediaSource != source) {
return;
}
do {
sourceHolderPriorityQueue.poll();
maybeStartPreloadNextSource();
} while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource());
}
});
}
@ -237,15 +240,27 @@ public abstract class BasePreloadManager<T> {
/** Releases the preload manager, see {@link #release()}. */
protected void releaseInternal() {}
/**
* Starts to preload the {@link MediaSource} at the head of the priority queue, if the {@linkplain
* TargetPreloadStatusControl.PreloadStatus target preload status} for that source is not null.
*
* @return {@code true} if the {@link MediaSource} at the head of the priority queue starts to
* preload, otherwise {@code false}.
* @throws NullPointerException if the priority queue is empty.
*/
@GuardedBy("lock")
private void maybeStartPreloadNextSource() {
if (!sourceHolderPriorityQueue.isEmpty() && shouldStartPreloadingNextSource()) {
private boolean maybeStartPreloadNextSource() {
if (shouldStartPreloadingNextSource()) {
MediaSourceHolder preloadingHolder = checkNotNull(sourceHolderPriorityQueue.peek());
this.targetPreloadStatusOfCurrentPreloadingSource =
targetPreloadStatusControl.getTargetPreloadStatus(preloadingHolder.rankingData);
if (targetPreloadStatusOfCurrentPreloadingSource != null) {
preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs);
return true;
}
}
return false;
}
/** A holder for information for preloading a single media source. */
private final class MediaSourceHolder implements Comparable<MediaSourceHolder> {

View File

@ -17,7 +17,6 @@ package androidx.media3.exoplayer.source.preload;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.Math.abs;
import static java.lang.annotation.ElementType.TYPE_USE;
@ -233,12 +232,13 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Nullable
TargetPreloadStatusControl.PreloadStatus targetPreloadStatus =
getTargetPreloadStatus(mediaSource);
checkState(targetPreloadStatus instanceof Status);
if (targetPreloadStatus != null) {
Status status = (Status) targetPreloadStatus;
if (continueLoadingPredicate.apply(checkNotNull(status))) {
return true;
}
onPreloadCompleted(mediaSource);
}
return false;
}
}

View File

@ -15,16 +15,22 @@
*/
package androidx.media3.exoplayer.source.preload;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaSource;
/** Controls the target preload status. */
@UnstableApi
public interface TargetPreloadStatusControl<T> {
/** Returns the target preload status for a source with the given {@code rankingData}. */
/**
* Returns the target preload status for a source with the given {@code rankingData}. May be null
* if a {@link MediaSource} with the given {@code rankingData} should not be preloaded.
*/
@Nullable
PreloadStatus getTargetPreloadStatus(T rankingData);
/** Defines the status of the preloading for a source. */
/** Defines the status of the preloading for a {@link MediaSource}. */
interface PreloadStatus {
/** The stage of the preloading. */

View File

@ -166,10 +166,10 @@ public class DefaultPreloadManagerTest {
@Test
public void
invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>();
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData);
targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
};
@ -190,7 +190,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
@ -200,21 +199,22 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0);
assertThat(targetPreloadStatusControlCallStates).containsExactly(0);
wrappedMediaSource0.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
}
@Test
public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>();
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData);
targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
};
@ -235,7 +235,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
@ -245,27 +244,27 @@ public class DefaultPreloadManagerTest {
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);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1);
assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1);
wrappedMediaSource1.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1, 0).inOrder();
assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder();
}
@Test
public void invalidate_sourceHandedOverToPlayerDuringPreloading_continuesPreloadingNextSource() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>();
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData);
targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
};
@ -284,7 +283,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
@ -292,7 +290,7 @@ public class DefaultPreloadManagerTest {
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false);
preloadManager.invalidate();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0);
assertThat(targetPreloadStatusControlCallStates).containsExactly(0);
PreloadMediaSource preloadMediaSource0 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem0);
@ -302,15 +300,15 @@ public class DefaultPreloadManagerTest {
// The preload of mediaItem0 should complete and the preload manager continues to preload
// mediaItem1, even when the preloadMediaSource0 hasn't finished preparation.
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
}
@Test
public void invalidate_beforePreloadCompletedForLastInvalidate_preloadRespectsToLatestOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>();
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData);
targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
};
@ -340,18 +338,18 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
MediaSource.MediaSourceCaller externalCaller = (source, timeline) -> {};
PreloadMediaSource preloadMediaSource0 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem0);
preloadMediaSource0.prepareSource(
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(0);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder();
targetPreloadStatusControlCallReference.clear();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
targetPreloadStatusControlCallStates.clear();
preloadMediaSource0.releaseSource(externalCaller);
PreloadMediaSource preloadMediaSource2 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem2);
@ -359,11 +357,57 @@ public class DefaultPreloadManagerTest {
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(2);
preloadManager.invalidate();
// Simulate the delay of the preparation of wrappedMediaSource0, which was triggered at the
// first call of invalidate(). This is expected to result in nothing, as the whole flow of
// preloading should respect the priority order triggered by the latest call of invalidate().
wrappedMediaSource0.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1).inOrder();
assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1).inOrder();
wrappedMediaSource1.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1, 0).inOrder();
assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder();
}
@Test
public void invalidate_provideNullTargetPreloadStatus_sourcesSkippedForPreload() {
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData);
return null;
};
FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
fakeMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 =
mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").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();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2);
}
@Test
@ -404,11 +448,11 @@ public class DefaultPreloadManagerTest {
}
};
});
preloadManager.add(mediaItem1, /* rankingData= */ 1);
preloadManager.add(mediaItem2, /* rankingData= */ 2);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
preloadManager.remove(mediaItem1);
shadowOf(Looper.getMainLooper()).idle();
@ -477,11 +521,11 @@ public class DefaultPreloadManagerTest {
}
};
});
preloadManager.add(mediaItem1, /* rankingData= */ 1);
preloadManager.add(mediaItem2, /* rankingData= */ 2);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
preloadManager.release();
shadowOf(Looper.getMainLooper()).idle();