PiperOrigin-RevId: 643329324
This commit is contained in:
bachinger 2024-06-14 06:40:19 -07:00 committed by Copybara-Service
parent 4cd8d64446
commit 872d8f078b
10 changed files with 180 additions and 38 deletions

View File

@ -437,6 +437,31 @@ public interface ExoPlayer extends Player {
default void onOffloadedPlayback(boolean isOffloadedPlayback) {}
}
/** Configuration options for preloading playlist items. */
@UnstableApi
class PreloadConfiguration {
/** Default preload configuration that disables playlist preloading. */
public static final PreloadConfiguration DEFAULT =
new PreloadConfiguration(/* targetPreloadDurationUs= */ C.TIME_UNSET);
/**
* The target duration to buffer when preloading, in microseconds or {@link C#TIME_UNSET} to
* disable preloading.
*/
public final long targetPreloadDurationUs;
/**
* Creates an instance.
*
* @param targetPreloadDurationUs The target duration to preload, in microseconds or {@link
* C#TIME_UNSET} to disable preloading.
*/
public PreloadConfiguration(long targetPreloadDurationUs) {
this.targetPreloadDurationUs = targetPreloadDurationUs;
}
}
/**
* A builder for {@link ExoPlayer} instances.
*
@ -1556,6 +1581,19 @@ public interface ExoPlayer extends Player {
@UnstableApi
void setShuffleOrder(ShuffleOrder shuffleOrder);
/**
* Sets the {@linkplain PreloadConfiguration preload configuration} to configure playlist
* preloading.
*
* @param preloadConfiguration The preload configuration.
*/
@UnstableApi
void setPreloadConfiguration(PreloadConfiguration preloadConfiguration);
/** Returns the {@linkplain PreloadConfiguration preload configuration}. */
@UnstableApi
PreloadConfiguration getPreloadConfiguration();
/**
* {@inheritDoc}
*

View File

@ -141,30 +141,6 @@ import java.util.concurrent.TimeoutException;
MediaLibraryInfo.registerModule("media3.exoplayer");
}
/** Configuration options for preloading playlist items. */
public static final class PreloadConfiguration {
/** Default preload configuration that disables playlist preloading. */
public static final PreloadConfiguration DEFAULT =
new PreloadConfiguration(/* targetPreloadDurationUs= */ C.TIME_UNSET);
/**
* The target duration to buffer when preloading, in microseconds or {@link C#TIME_UNSET} to
* disable preloading.
*/
public final long targetPreloadDurationUs;
/**
* Creates an instance.
*
* @param targetPreloadDurationUs The target duration to preload, in microseconds or {@link
* C#TIME_UNSET} to disable preloading.
*/
public PreloadConfiguration(long targetPreloadDurationUs) {
this.targetPreloadDurationUs = targetPreloadDurationUs;
}
}
private static final String TAG = "ExoPlayerImpl";
/**
@ -909,6 +885,7 @@ import java.util.concurrent.TimeoutException;
return shuffleModeEnabled;
}
@Override
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
verifyApplicationThread();
if (this.preloadConfiguration.equals(preloadConfiguration)) {
@ -918,6 +895,7 @@ import java.util.concurrent.TimeoutException;
internalPlayer.setPreloadConfiguration(preloadConfiguration);
}
@Override
public PreloadConfiguration getPreloadConfiguration() {
return preloadConfiguration;
}

View File

@ -57,6 +57,7 @@ import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSourceException;
import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DrmSession;
@ -241,7 +242,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
@Nullable private ExoPlaybackException pendingRecoverableRendererError;
private long setForegroundModeTimeoutMs;
private long playbackMaybeBecameStuckAtMs;
private ExoPlayerImpl.PreloadConfiguration preloadConfiguration;
private PreloadConfiguration preloadConfiguration;
private Timeline lastPreloadPoolInvalidationTimeline;
public ExoPlayerImplInternal(
@ -263,7 +264,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
PlaybackInfoUpdateListener playbackInfoUpdateListener,
PlayerId playerId,
Looper playbackLooper,
ExoPlayerImpl.PreloadConfiguration preloadConfiguration) {
PreloadConfiguration preloadConfiguration) {
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
this.renderers = renderers;
this.trackSelector = trackSelector;
@ -372,7 +373,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
handler.obtainMessage(MSG_SET_SHUFFLE_ENABLED, shuffleModeEnabled ? 1 : 0, 0).sendToTarget();
}
public void setPreloadConfiguration(ExoPlayerImpl.PreloadConfiguration preloadConfiguration) {
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
handler.obtainMessage(MSG_SET_PRELOAD_CONFIGURATION, preloadConfiguration).sendToTarget();
}
@ -561,7 +562,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
setShuffleModeEnabledInternal(msg.arg1 != 0);
break;
case MSG_SET_PRELOAD_CONFIGURATION:
setPreloadConfigurationInternal((ExoPlayerImpl.PreloadConfiguration) msg.obj);
setPreloadConfigurationInternal((PreloadConfiguration) msg.obj);
break;
case MSG_DO_SOME_WORK:
doSomeWork();
@ -950,8 +951,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
}
private void setPreloadConfigurationInternal(
ExoPlayerImpl.PreloadConfiguration preloadConfiguration) {
private void setPreloadConfigurationInternal(PreloadConfiguration preloadConfiguration) {
this.preloadConfiguration = preloadConfiguration;
queue.updatePreloadConfiguration(playbackInfo.timeline, preloadConfiguration);
}

View File

@ -28,6 +28,7 @@ import androidx.media3.common.Player.RepeatMode;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
@ -84,7 +85,7 @@ import java.util.List;
private int length;
@Nullable private Object oldFrontPeriodUid;
private long oldFrontPeriodWindowSequenceNumber;
private ExoPlayerImpl.PreloadConfiguration preloadConfiguration;
private PreloadConfiguration preloadConfiguration;
private List<MediaPeriodHolder> preloadPriorityList;
/**
@ -99,7 +100,7 @@ import java.util.List;
AnalyticsCollector analyticsCollector,
HandlerWrapper analyticsCollectorHandler,
MediaPeriodHolder.Factory mediaPeriodHolderFactory,
ExoPlayerImpl.PreloadConfiguration preloadConfiguration) {
PreloadConfiguration preloadConfiguration) {
this.analyticsCollector = analyticsCollector;
this.analyticsCollectorHandler = analyticsCollectorHandler;
this.mediaPeriodHolderFactory = mediaPeriodHolderFactory;
@ -142,7 +143,7 @@ import java.util.List;
* @param preloadConfiguration The new preload configuration.
*/
public void updatePreloadConfiguration(
Timeline timeline, ExoPlayerImpl.PreloadConfiguration preloadConfiguration) {
Timeline timeline, PreloadConfiguration preloadConfiguration) {
this.preloadConfiguration = preloadConfiguration;
invalidatePreloadPool(timeline);
}

View File

@ -992,6 +992,18 @@ public class SimpleExoPlayer extends BasePlayer
player.setRepeatMode(repeatMode);
}
@Override
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
blockUntilConstructorFinished();
player.setPreloadConfiguration(preloadConfiguration);
}
@Override
public PreloadConfiguration getPreloadConfiguration() {
blockUntilConstructorFinished();
return player.getPreloadConfiguration();
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
blockUntilConstructorFinished();

View File

@ -274,14 +274,16 @@ public class ExoPlayerTest {
}
private TestExoPlayerBuilder parameterizeTestExoPlayerBuilder(TestExoPlayerBuilder builder) {
// TODO: set PreloadConfiguration when enabled again after the release.
return builder;
return builder.setPreloadConfiguration(createPreloadConfiguration());
}
private ExoPlayerTestRunner.Builder parameterizeExoPlayerTestRunnerBuilder(
ExoPlayerTestRunner.Builder builder) {
// TODO: set PreloadConfiguration when enabled again after the release.
return builder;
return builder.setPreloadConfiguration(createPreloadConfiguration());
}
private ExoPlayer.PreloadConfiguration createPreloadConfiguration() {
return new ExoPlayer.PreloadConfiguration(getTargetPreloadDurationUs());
}
/**
@ -6806,6 +6808,80 @@ public class ExoPlayerTest {
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source prepared */);
}
@Test
public void prepare_preloadingEnabled_nextWindowPeriodCreatedForPreloading() throws Exception {
FakeMediaSource mediaSource1 =
new FakeMediaSource(
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * 2)));
List<MediaPeriodId> createdMediaPeriodIds = new ArrayList<>();
FakeMediaSource mediaSource2 =
new FakeMediaSource() {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
createdMediaPeriodIds.add(id);
return super.createPeriod(id, allocator, startPositionUs);
}
};
ExoPlayer player =
// Intentionally not using `parameterizeTestExoPlayerBuilder()` for preload specific test.
new TestExoPlayerBuilder(context)
.setPreloadConfiguration(
new ExoPlayer.PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L))
.build();
player.setMediaSources(ImmutableList.of(mediaSource1, mediaSource2));
player.prepare();
run(player).untilPendingCommandsAreFullyHandled();
assertThat(createdMediaPeriodIds).hasSize(1);
play(player).untilState(Player.STATE_ENDED);
assertThat(createdMediaPeriodIds).hasSize(1);
player.release();
}
@Test
public void prepare_preloadingEnabledRepeatModeOne_sameWindowPeriodCreatedForPreloading()
throws Exception {
FakeTimeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * 2));
List<MediaPeriodId> createdMediaPeriodIds = new ArrayList<>();
FakeMediaSource mediaSource =
new FakeMediaSource(timeline) {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
createdMediaPeriodIds.add(id);
return super.createPeriod(id, allocator, startPositionUs);
}
};
ExoPlayer player =
// Intentionally not using `parameterizeTestExoPlayerBuilder()` for preload specific test.
new TestExoPlayerBuilder(context)
.setPreloadConfiguration(
new ExoPlayer.PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L))
.build();
player.setRepeatMode(Player.REPEAT_MODE_ONE);
player.setMediaSource(mediaSource);
player.prepare();
run(player).untilPendingCommandsAreFullyHandled();
assertThat(createdMediaPeriodIds).hasSize(2);
player.setRepeatMode(Player.REPEAT_MODE_OFF);
play(player).untilState(Player.STATE_ENDED);
assertThat(createdMediaPeriodIds).hasSize(2);
player.release();
}
@Test
public void seekToIndexLargerThanNumberOfPlaylistItems() throws Exception {
Timeline fakeTimeline =
@ -14507,6 +14583,7 @@ public class ExoPlayerTest {
new DefaultRenderersFactory(context).setAllowedVideoJoiningTimeMs(0))
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
player.setPreloadConfiguration(createPreloadConfiguration());
player.setPauseAtEndOfMediaItems(true);
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 0));
player.setVideoSurface(surface);

View File

@ -40,7 +40,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.exoplayer.ExoPlayerImpl.PreloadConfiguration;
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
import androidx.media3.exoplayer.analytics.PlayerId;

View File

@ -284,6 +284,16 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul
return this;
}
/**
* @see ExoPlayer#setPreloadConfiguration(ExoPlayer.PreloadConfiguration)
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setPreloadConfiguration(ExoPlayer.PreloadConfiguration preloadConfiguration) {
testPlayerBuilder.setPreloadConfiguration(preloadConfiguration);
return this;
}
/**
* Sets an {@link ActionSchedule} to be run by the test runner. The first action will be
* executed immediately before {@link ExoPlayer#prepare()}.

View File

@ -154,6 +154,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
@Override
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
throw new UnsupportedOperationException();
}
@Override
public PreloadConfiguration getPreloadConfiguration() {
throw new UnsupportedOperationException();
}
@Override
public void setMediaSource(MediaSource mediaSource) {
throw new UnsupportedOperationException();

View File

@ -56,6 +56,7 @@ public class TestExoPlayerBuilder {
private long seekForwardIncrementMs;
private boolean deviceVolumeControlEnabled;
private boolean suppressPlaybackWhenUnsuitableOutput;
@Nullable private ExoPlayer.PreloadConfiguration preloadConfiguration;
private boolean dynamicSchedulingEnabled;
public TestExoPlayerBuilder(Context context) {
@ -161,6 +162,18 @@ public class TestExoPlayerBuilder {
return this;
}
/**
* Sets the preload configuration.
*
* @see ExoPlayer#setPreloadConfiguration(ExoPlayer.PreloadConfiguration)
*/
@CanIgnoreReturnValue
public TestExoPlayerBuilder setPreloadConfiguration(
ExoPlayer.PreloadConfiguration preloadConfiguration) {
this.preloadConfiguration = preloadConfiguration;
return this;
}
/**
* Returns the {@link Renderer Renderers} that have been set with {@link #setRenderers} or null if
* no {@link Renderer Renderers} have been explicitly set. Note that these renderers may not be
@ -373,6 +386,9 @@ public class TestExoPlayerBuilder {
builder.setMediaSourceFactory(mediaSourceFactory);
}
ExoPlayer exoPlayer = builder.build();
if (preloadConfiguration != null) {
exoPlayer.setPreloadConfiguration(preloadConfiguration);
}
return exoPlayer;
}
}