Use buffered duration from start position to control preload progress

`PreloadMediaSource` allows to have a `startPositionUs` passed when `preload` is called, then in `PreloadControl.onContinueLoadingRequested`, it can be more intuitive to see the buffered duration rather than the absolute buffered position as the preload progress. Similar in `DefaultPreloadManager`, we haven't allowed the apps to set a custom start position for individual sources though, once we add this support, using the "duration from the start position" than the absolute position will be less error-prone, otherwise, it can run into a case that the position that the apps set is smaller than the start position.

PiperOrigin-RevId: 674251362
This commit is contained in:
tianyifeng 2024-09-13 05:01:54 -07:00 committed by Copybara-Service
parent 023fd32cb1
commit 72ae454f67
7 changed files with 36 additions and 25 deletions

View File

@ -10,6 +10,13 @@
* Deprecated `MediaCodecUtil.getCodecProfileAndLevel`. Use * Deprecated `MediaCodecUtil.getCodecProfileAndLevel`. Use
`androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel` `androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel`
instead. instead.
* Pass `bufferedDurationUs` instead of `bufferedPositionUs` with
`PreloadMediaSource.PreloadControl.onContinueLoadingRequested()`. Also
changes `DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS` to
`DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS`, apps then
need to pass a value representing a specific duration from the default
start position for which the corresponding media source has to be
preloaded with this IntDef, instead of a position.
* Transformer: * Transformer:
* Track Selection: * Track Selection:
* Extractors: * Extractors:

View File

@ -33,7 +33,7 @@ import androidx.media3.exoplayer.DefaultRendererCapabilitiesList
import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
@ -200,9 +200,9 @@ class ViewPagerMediaAdapter(
inner class DefaultPreloadControl : TargetPreloadStatusControl<Int> { inner class DefaultPreloadControl : TargetPreloadStatusControl<Int> {
override fun getTargetPreloadStatus(rankingData: Int): DefaultPreloadManager.Status? { override fun getTargetPreloadStatus(rankingData: Int): DefaultPreloadManager.Status? {
if (abs(rankingData - currentPlayingIndex) == 2) { if (abs(rankingData - currentPlayingIndex) == 2) {
return DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 500L) return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 500L)
} else if (abs(rankingData - currentPlayingIndex) == 1) { } else if (abs(rankingData - currentPlayingIndex) == 1) {
return DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 1000L) return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 1000L)
} }
return null return null
} }

View File

@ -57,7 +57,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
/** /**
* Stages for the preload status. One of {@link #STAGE_SOURCE_PREPARED}, {@link * Stages for the preload status. One of {@link #STAGE_SOURCE_PREPARED}, {@link
* #STAGE_TRACKS_SELECTED} or {@link #STAGE_LOADED_TO_POSITION_MS}. * #STAGE_TRACKS_SELECTED} or {@link #STAGE_LOADED_FOR_DURATION_MS}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -66,7 +66,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
value = { value = {
STAGE_SOURCE_PREPARED, STAGE_SOURCE_PREPARED,
STAGE_TRACKS_SELECTED, STAGE_TRACKS_SELECTED,
STAGE_LOADED_TO_POSITION_MS, STAGE_LOADED_FOR_DURATION_MS,
}) })
public @interface Stage {} public @interface Stage {}
@ -76,8 +76,11 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
/** The {@link PreloadMediaSource} has tracks selected. */ /** The {@link PreloadMediaSource} has tracks selected. */
public static final int STAGE_TRACKS_SELECTED = 1; public static final int STAGE_TRACKS_SELECTED = 1;
/** The {@link PreloadMediaSource} is loaded to a specific position in microseconds. */ /**
public static final int STAGE_LOADED_TO_POSITION_MS = 2; * The {@link PreloadMediaSource} is loaded for a specific duration from the default start
* position, in milliseconds.
*/
public static final int STAGE_LOADED_FOR_DURATION_MS = 2;
private final @Stage int stage; private final @Stage int stage;
private final long value; private final long value;
@ -124,7 +127,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
* @param allocator The {@link Allocator}. It should be the same allocator of the {@link * @param allocator The {@link Allocator}. It should be the same allocator of the {@link
* ExoPlayer} that will play the managed {@link PreloadMediaSource}. * ExoPlayer} that will play the managed {@link PreloadMediaSource}.
* @param preloadLooper The {@link Looper} that will be used for preloading. It should be the same * @param preloadLooper The {@link Looper} that will be used for preloading. It should be the same
* playback looper of the {@link ExoPlayer} that will play the manager {@link * playback looper of the {@link ExoPlayer} that will play the managed {@link
* PreloadMediaSource}. * PreloadMediaSource}.
*/ */
public DefaultPreloadManager( public DefaultPreloadManager(
@ -228,14 +231,14 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Override @Override
public boolean onContinueLoadingRequested( public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) { PreloadMediaSource mediaSource, long bufferedDurationUs) {
// Set `clearExceededDataFromTargetPreloadStatus` to `false` as clearing the exceeded data // Set `clearExceededDataFromTargetPreloadStatus` to `false` as clearing the exceeded data
// from the status STAGE_LOADED_TO_POSITION_MS is not supported. // from the status STAGE_LOADED_FOR_DURATION_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_FOR_DURATION_MS
&& status.getValue() > Util.usToMs(bufferedPositionUs), && status.getValue() > Util.usToMs(bufferedDurationUs),
/* clearExceededDataFromTargetPreloadStatus= */ false); /* clearExceededDataFromTargetPreloadStatus= */ false);
} }

View File

@ -87,10 +87,10 @@ public final class PreloadMediaSource extends WrappingMediaSource {
* instead. * 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 bufferedDurationUs An estimate of the duration from the start position for which data
* data is buffered. * is buffered, in microseconds.
*/ */
boolean onContinueLoadingRequested(PreloadMediaSource mediaSource, long bufferedPositionUs); boolean onContinueLoadingRequested(PreloadMediaSource mediaSource, long bufferedDurationUs);
/** /**
* Called from {@link PreloadMediaSource} when the player starts using this source. * Called from {@link PreloadMediaSource} when the player starts using this source.
@ -502,14 +502,15 @@ public final class PreloadMediaSource extends WrappingMediaSource {
return; return;
} }
PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod; PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod;
if (prepared && mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE) { long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
if (prepared && bufferedPositionUs == C.TIME_END_OF_SOURCE) {
preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this); preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this);
stopPreloading(); stopPreloading();
return; return;
} }
if (prepared if (prepared
&& !preloadControl.onContinueLoadingRequested( && !preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) { PreloadMediaSource.this, bufferedPositionUs - periodStartPositionUs)) {
stopPreloading(); stopPreloading();
return; return;
} }

View File

@ -15,7 +15,7 @@
*/ */
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_FOR_DURATION_MS;
import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_SOURCE_PREPARED; import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_SOURCE_PREPARED;
import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_TRACKS_SELECTED; import static androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_TRACKS_SELECTED;
import static androidx.media3.test.utils.FakeMediaSourceFactory.DEFAULT_WINDOW_UID; import static androidx.media3.test.utils.FakeMediaSourceFactory.DEFAULT_WINDOW_UID;
@ -195,7 +195,7 @@ public class DefaultPreloadManagerTest {
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
if (abs(rankingData - currentPlayingItemIndex.get()) == 1) { if (abs(rankingData - currentPlayingItemIndex.get()) == 1) {
return new DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 100L); return new DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 100L);
} else { } else {
return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED); return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
} }
@ -256,7 +256,7 @@ public class DefaultPreloadManagerTest {
rankingData -> { rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
if (abs(rankingData - currentPlayingItemIndex.get()) == 1) { if (abs(rankingData - currentPlayingItemIndex.get()) == 1) {
return new DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 100L); return new DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 100L);
} else { } else {
return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED); return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
} }

View File

@ -116,7 +116,7 @@ public class PreloadAndPlaybackCoordinationTest {
@Override @Override
public boolean onContinueLoadingRequested( public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) { PreloadMediaSource mediaSource, long bufferedDurationUs) {
return true; return true;
} }

View File

@ -91,7 +91,7 @@ import org.junit.runner.RunWith;
public final class PreloadMediaSourceTest { public final class PreloadMediaSourceTest {
private static final int LOADING_CHECK_INTERVAL_BYTES = 10 * 1024; private static final int LOADING_CHECK_INTERVAL_BYTES = 10 * 1024;
private static final int TARGET_PRELOAD_POSITION_US = 10000; private static final int TARGET_PRELOAD_DURATION_US = 10000;
private Allocator allocator; private Allocator allocator;
private BandwidthMeter bandwidthMeter; private BandwidthMeter bandwidthMeter;
@ -121,9 +121,9 @@ public final class PreloadMediaSourceTest {
new TestPreloadControl() { new TestPreloadControl() {
@Override @Override
public boolean onContinueLoadingRequested( public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) { PreloadMediaSource mediaSource, long bufferedDurationUs) {
onContinueLoadingRequestedCalled = true; onContinueLoadingRequestedCalled = true;
if (bufferedPositionUs >= TARGET_PRELOAD_POSITION_US) { if (bufferedDurationUs >= TARGET_PRELOAD_DURATION_US) {
preloadMediaSourceReference.set(mediaSource); preloadMediaSourceReference.set(mediaSource);
return false; return false;
} }
@ -1186,7 +1186,7 @@ public final class PreloadMediaSourceTest {
@Override @Override
public boolean onContinueLoadingRequested( public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) { PreloadMediaSource mediaSource, long bufferedDurationUs) {
onContinueLoadingRequestedCalled = true; onContinueLoadingRequestedCalled = true;
return true; return true;
} }