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
`androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel`
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:
* Track Selection:
* Extractors:

View File

@ -33,7 +33,7 @@ import androidx.media3.exoplayer.DefaultRendererCapabilitiesList
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
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.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
@ -200,9 +200,9 @@ class ViewPagerMediaAdapter(
inner class DefaultPreloadControl : TargetPreloadStatusControl<Int> {
override fun getTargetPreloadStatus(rankingData: Int): DefaultPreloadManager.Status? {
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) {
return DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 1000L)
return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 1000L)
}
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
* #STAGE_TRACKS_SELECTED} or {@link #STAGE_LOADED_TO_POSITION_MS}.
* #STAGE_TRACKS_SELECTED} or {@link #STAGE_LOADED_FOR_DURATION_MS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@ -66,7 +66,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
value = {
STAGE_SOURCE_PREPARED,
STAGE_TRACKS_SELECTED,
STAGE_LOADED_TO_POSITION_MS,
STAGE_LOADED_FOR_DURATION_MS,
})
public @interface Stage {}
@ -76,8 +76,11 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
/** The {@link PreloadMediaSource} has tracks selected. */
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 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
* ExoPlayer} that will play the managed {@link PreloadMediaSource}.
* @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}.
*/
public DefaultPreloadManager(
@ -228,14 +231,14 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
PreloadMediaSource mediaSource, long bufferedDurationUs) {
// 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(
mediaSource,
/* continueLoadingPredicate= */ status ->
status.getStage() == Status.STAGE_LOADED_TO_POSITION_MS
&& status.getValue() > Util.usToMs(bufferedPositionUs),
status.getStage() == Status.STAGE_LOADED_FOR_DURATION_MS
&& status.getValue() > Util.usToMs(bufferedDurationUs),
/* clearExceededDataFromTargetPreloadStatus= */ false);
}

View File

@ -87,10 +87,10 @@ public final class PreloadMediaSource extends WrappingMediaSource {
* 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.
* @param bufferedDurationUs An estimate of the duration from the start position for which data
* 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.
@ -502,14 +502,15 @@ public final class PreloadMediaSource extends WrappingMediaSource {
return;
}
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);
stopPreloading();
return;
}
if (prepared
&& !preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, preloadMediaPeriod.getBufferedPositionUs())) {
PreloadMediaSource.this, bufferedPositionUs - periodStartPositionUs)) {
stopPreloading();
return;
}

View File

@ -15,7 +15,7 @@
*/
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_TRACKS_SELECTED;
import static androidx.media3.test.utils.FakeMediaSourceFactory.DEFAULT_WINDOW_UID;
@ -195,7 +195,7 @@ public class DefaultPreloadManagerTest {
rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData);
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 {
return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
}
@ -256,7 +256,7 @@ public class DefaultPreloadManagerTest {
rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData);
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 {
return new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
}

View File

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

View File

@ -91,7 +91,7 @@ import org.junit.runner.RunWith;
public final class PreloadMediaSourceTest {
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 BandwidthMeter bandwidthMeter;
@ -121,9 +121,9 @@ public final class PreloadMediaSourceTest {
new TestPreloadControl() {
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
PreloadMediaSource mediaSource, long bufferedDurationUs) {
onContinueLoadingRequestedCalled = true;
if (bufferedPositionUs >= TARGET_PRELOAD_POSITION_US) {
if (bufferedDurationUs >= TARGET_PRELOAD_DURATION_US) {
preloadMediaSourceReference.set(mediaSource);
return false;
}
@ -1186,7 +1186,7 @@ public final class PreloadMediaSourceTest {
@Override
public boolean onContinueLoadingRequested(
PreloadMediaSource mediaSource, long bufferedPositionUs) {
PreloadMediaSource mediaSource, long bufferedDurationUs) {
onContinueLoadingRequestedCalled = true;
return true;
}