Add DefaultPreloadManager.Builder

The `DefaultPreloadManager.Builder` is able to build the `DefaultPreloadManager` and `ExoPlayer` instances with the consistently shared configurations. Apps can:

* Simply setup the `DefaultPreloadManager` and `ExoPlayer` with all default configurations via `build()` and `buildExoPlayer()`;
* Or customize the shared configurations by the setters on `DefaultPreloadManager.Builder` and setup via `build()` and `buildExoPlayer()`;
* Or customize the player-only configurations for `ExoPlayer` via `buildExoPlayer(ExoPlayer.Builder)`.

PiperOrigin-RevId: 684852808
This commit is contained in:
tianyifeng 2024-10-11 08:52:28 -07:00 committed by Copybara-Service
parent 337e59e733
commit 98dc7f2def
7 changed files with 368 additions and 218 deletions

View File

@ -48,6 +48,9 @@
* Add method `MediaSourceEventListener.EventDispatcher.dispatchEvent()` to
allow invoking events of subclass listeners
([1736](https://github.com/androidx/media/pull/1736)).
* Add `DefaultPreloadManager.Builder` that builds the
`DefaultPreloadManager` and `ExoPlayer` instances with consistently
shared configurations.
* Transformer:
* Make setting the image duration using
`MediaItem.Builder.setImageDurationMs` mandatory for image export.

View File

@ -15,16 +15,13 @@
*/
package androidx.media3.demo.shortform
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.LoadControl
import androidx.media3.exoplayer.RenderersFactory
import androidx.media3.exoplayer.upstream.BandwidthMeter
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Builder
import androidx.media3.exoplayer.util.EventLogger
import com.google.common.collect.BiMap
import com.google.common.collect.HashBiMap
@ -34,14 +31,7 @@ import java.util.LinkedList
import java.util.Queue
@OptIn(UnstableApi::class)
class PlayerPool(
private val numberOfPlayers: Int,
context: Context,
playbackLooper: Looper,
loadControl: LoadControl,
renderersFactory: RenderersFactory,
bandwidthMeter: BandwidthMeter,
) {
class PlayerPool(private val numberOfPlayers: Int, preloadManagerBuilder: Builder) {
/** Creates a player instance to be used by the pool. */
interface PlayerFactory {
@ -52,8 +42,7 @@ class PlayerPool(
private val availablePlayerQueue: Queue<Int> = LinkedList()
private val playerMap: BiMap<Int, ExoPlayer> = Maps.synchronizedBiMap(HashBiMap.create())
private val playerRequestTokenSet: MutableSet<Int> = Collections.synchronizedSet(HashSet<Int>())
private val playerFactory: PlayerFactory =
DefaultPlayerFactory(context, playbackLooper, loadControl, renderersFactory, bandwidthMeter)
private val playerFactory: PlayerFactory = DefaultPlayerFactory(preloadManagerBuilder)
fun acquirePlayer(token: Int, callback: (ExoPlayer) -> Unit) {
synchronized(playerMap) {
@ -126,23 +115,11 @@ class PlayerPool(
}
@OptIn(UnstableApi::class)
private class DefaultPlayerFactory(
private val context: Context,
private val playbackLooper: Looper,
private val loadControl: LoadControl,
private val renderersFactory: RenderersFactory,
private val bandwidthMeter: BandwidthMeter,
) : PlayerFactory {
private class DefaultPlayerFactory(private val preloadManagerBuilder: Builder) : PlayerFactory {
private var playerCounter = 0
override fun createPlayer(): ExoPlayer {
val player =
ExoPlayer.Builder(context)
.setPlaybackLooper(playbackLooper)
.setLoadControl(loadControl)
.setRenderersFactory(renderersFactory)
.setBandwidthMeter(bandwidthMeter)
.build()
val player = preloadManagerBuilder.buildExoPlayer()
player.addAnalyticsListener(EventLogger("player-$playerCounter"))
playerCounter++
player.repeatMode = ExoPlayer.REPEAT_MODE_ONE

View File

@ -16,8 +16,6 @@
package androidx.media3.demo.shortform.viewpager
import android.content.Context
import android.os.HandlerThread
import android.os.Process
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.OptIn
@ -29,14 +27,9 @@ import androidx.media3.demo.shortform.MediaItemDatabase
import androidx.media3.demo.shortform.PlayerPool
import androidx.media3.demo.shortform.R
import androidx.media3.exoplayer.DefaultLoadControl
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_FOR_DURATION_MS
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
@ -46,8 +39,6 @@ class ViewPagerMediaAdapter(
numberOfPlayers: Int,
context: Context,
) : RecyclerView.Adapter<ViewPagerMediaHolder>() {
private val playbackThread: HandlerThread =
HandlerThread("playback-thread", Process.THREAD_PRIORITY_AUDIO)
private val preloadManager: DefaultPreloadManager
private val currentMediaItemsAndIndexes: ArrayDeque<Pair<MediaItem, Int>> = ArrayDeque()
private var playerPool: PlayerPool
@ -64,7 +55,6 @@ class ViewPagerMediaAdapter(
}
init {
playbackThread.start()
val loadControl =
DefaultLoadControl.Builder()
.setBufferDurationsMs(
@ -75,29 +65,11 @@ class ViewPagerMediaAdapter(
)
.setPrioritizeTimeOverSizeThresholds(true)
.build()
val renderersFactory = DefaultRenderersFactory(context)
playerPool =
PlayerPool(
numberOfPlayers,
context,
playbackThread.looper,
loadControl,
renderersFactory,
DefaultBandwidthMeter.getSingletonInstance(context),
)
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, DefaultPreloadControl()).setLoadControl(loadControl)
playerPool = PlayerPool(numberOfPlayers, preloadManagerBuilder)
holderMap = mutableMapOf()
val trackSelector = DefaultTrackSelector(context)
trackSelector.init({}, DefaultBandwidthMeter.getSingletonInstance(context))
preloadManager =
DefaultPreloadManager(
DefaultPreloadControl(),
DefaultMediaSourceFactory(context),
trackSelector,
DefaultBandwidthMeter.getSingletonInstance(context),
DefaultRendererCapabilitiesList.Factory(renderersFactory),
loadControl.allocator,
playbackThread.looper,
)
preloadManager = preloadManagerBuilder.build()
for (i in 0 until MANAGED_ITEM_COUNT) {
addMediaItem(index = i, isAddingToRightEnd = true)
}
@ -157,9 +129,8 @@ class ViewPagerMediaAdapter(
}
fun onDestroy() {
preloadManager.release()
playerPool.destroyPlayers()
playbackThread.quit()
preloadManager.release()
}
fun onPageSelected(position: Int) {

View File

@ -28,6 +28,7 @@ import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaSource;
import com.google.common.base.Supplier;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
@ -47,15 +48,15 @@ public abstract class BasePreloadManager<T> {
protected final Comparator<T> rankingDataComparator;
protected final TargetPreloadStatusControl<T> targetPreloadStatusControl;
protected final MediaSource.Factory mediaSourceFactory;
protected Supplier<MediaSource.Factory> mediaSourceFactorySupplier;
public BuilderBase(
Comparator<T> rankingDataComparator,
TargetPreloadStatusControl<T> targetPreloadStatusControl,
MediaSource.Factory mediaSourceFactory) {
Supplier<MediaSource.Factory> mediaSourceFactorySupplier) {
this.rankingDataComparator = rankingDataComparator;
this.targetPreloadStatusControl = targetPreloadStatusControl;
this.mediaSourceFactory = mediaSourceFactory;
this.mediaSourceFactorySupplier = mediaSourceFactorySupplier;
}
public abstract BasePreloadManager<T> build();

View File

@ -17,25 +17,38 @@ 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;
import android.content.Context;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.DefaultLoadControl;
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
import androidx.media3.exoplayer.DefaultRenderersFactory;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.LoadControl;
import androidx.media3.exoplayer.PlaybackLooperProvider;
import androidx.media3.exoplayer.RendererCapabilitiesList;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.SampleQueue;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -49,6 +62,211 @@ import java.util.Comparator;
@UnstableApi
public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
/** A builder for {@link DefaultPreloadManager} instances. */
public static final class Builder extends BuilderBase<Integer> {
private final Context context;
private PlaybackLooperProvider preloadLooperProvider;
private TrackSelector.Factory trackSelectorFactory;
private Supplier<BandwidthMeter> bandwidthMeterSupplier;
private Supplier<RenderersFactory> renderersFactorySupplier;
private Supplier<LoadControl> loadControlSupplier;
private boolean buildCalled;
private boolean buildExoPlayerCalled;
/**
* Creates a builder.
*
* @param context A {@link Context}.
* @param targetPreloadStatusControl A {@link TargetPreloadStatusControl<Integer>}.
*/
public Builder(
Context context, TargetPreloadStatusControl<Integer> targetPreloadStatusControl) {
super(
new RankingDataComparator(),
targetPreloadStatusControl,
Suppliers.memoize(() -> new DefaultMediaSourceFactory(context)));
this.context = context;
this.preloadLooperProvider = new PlaybackLooperProvider();
this.trackSelectorFactory = DefaultTrackSelector::new;
this.bandwidthMeterSupplier = () -> DefaultBandwidthMeter.getSingletonInstance(context);
this.renderersFactorySupplier = Suppliers.memoize(() -> new DefaultRenderersFactory(context));
this.loadControlSupplier = Suppliers.memoize(DefaultLoadControl::new);
}
/**
* Sets the {@link MediaSource.Factory} that will be used by the built {@link
* DefaultPreloadManager} and {@link ExoPlayer}.
*
* <p>The default is a {@link DefaultMediaSourceFactory}.
*
* @param mediaSourceFactory A {@link MediaSource.Factory}
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setMediaSourceFactory(MediaSource.Factory mediaSourceFactory) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.mediaSourceFactorySupplier = () -> mediaSourceFactory;
return this;
}
/**
* Sets the {@link RenderersFactory} that will be used by the built {@link
* DefaultPreloadManager} and {@link ExoPlayer}.
*
* <p>The default is a {@link DefaultRenderersFactory}.
*
* @param renderersFactory A {@link RenderersFactory}.
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.renderersFactorySupplier = () -> renderersFactory;
return this;
}
/**
* Sets the {@link TrackSelector.Factory} that will be used by the built {@link
* DefaultPreloadManager} and {@link ExoPlayer}.
*
* <p>The default is a {@link TrackSelector.Factory} that always creates a new {@link
* DefaultTrackSelector}.
*
* @param trackSelectorFactory A {@link TrackSelector.Factory}.
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setTrackSelectorFactory(TrackSelector.Factory trackSelectorFactory) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.trackSelectorFactory = trackSelectorFactory;
return this;
}
/**
* Sets the {@link LoadControl} that will be used by the built {@link DefaultPreloadManager} and
* {@link ExoPlayer}.
*
* <p>The default is a {@link DefaultLoadControl}.
*
* @param loadControl A {@link LoadControl}.
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setLoadControl(LoadControl loadControl) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.loadControlSupplier = () -> loadControl;
return this;
}
/**
* Sets the {@link BandwidthMeter} that will be used by the built {@link DefaultPreloadManager}
* and {@link ExoPlayer}.
*
* <p>The default is a {@link DefaultBandwidthMeter}.
*
* @param bandwidthMeter A {@link BandwidthMeter}.
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.bandwidthMeterSupplier = () -> bandwidthMeter;
return this;
}
/**
* Sets the {@link Looper} that will be used for preload and playback.
*
* <p>The backing thread should run with priority {@link Process#THREAD_PRIORITY_AUDIO} and
* should handle messages within 10ms.
*
* <p>The default is a looper that is associated with a new thread created internally.
*
* @param preloadLooper A {@link Looper}.
* @return This builder.
* @throws IllegalStateException If {@link #build()}, {@link #buildExoPlayer()} or {@link
* #buildExoPlayer(ExoPlayer.Builder)} has already been called.
*/
@CanIgnoreReturnValue
public Builder setPreloadLooper(Looper preloadLooper) {
checkState(!buildCalled && !buildExoPlayerCalled);
this.preloadLooperProvider = new PlaybackLooperProvider(preloadLooper);
return this;
}
/**
* Builds an {@link ExoPlayer}.
*
* <p>See {@link #buildExoPlayer(ExoPlayer.Builder)} for the list of values populated on and
* resulting from this builder that the built {@link ExoPlayer} uses.
*
* <p>For the other configurations than above, the built {@link ExoPlayer} uses the default
* values, see {@link ExoPlayer.Builder#Builder(Context)} for the list of default values.
*
* @return An {@link ExoPlayer} instance.
*/
public ExoPlayer buildExoPlayer() {
return buildExoPlayer(new ExoPlayer.Builder(context));
}
/**
* Builds an {@link ExoPlayer} with an {@link ExoPlayer.Builder} passed in.
*
* <p>The built {@link ExoPlayer} uses the following values populated on and resulting from this
* builder:
*
* <ul>
* <li>{@link #setMediaSourceFactory(MediaSource.Factory) MediaSource.Factory}
* <li>{@link #setRenderersFactory(RenderersFactory) RenderersFactory}
* <li>{@link #setTrackSelectorFactory(TrackSelector.Factory) TrackSelector.Factory}
* <li>{@link #setLoadControl(LoadControl) LoadControl}
* <li>{@link #setBandwidthMeter(BandwidthMeter) BandwidthMeter}
* <li>{@linkplain #setPreloadLooper(Looper)} preload looper}
* </ul>
*
* <p>For the other configurations than above, the built {@link ExoPlayer} uses the values from
* the passed {@link ExoPlayer.Builder}.
*
* @param exoPlayerBuilder An {@link ExoPlayer.Builder} that is used to build the {@link
* ExoPlayer}.
* @return An {@link ExoPlayer} instance.
*/
public ExoPlayer buildExoPlayer(ExoPlayer.Builder exoPlayerBuilder) {
buildExoPlayerCalled = true;
return exoPlayerBuilder
.setMediaSourceFactory(mediaSourceFactorySupplier.get())
.setBandwidthMeter(bandwidthMeterSupplier.get())
.setRenderersFactory(renderersFactorySupplier.get())
.setLoadControl(loadControlSupplier.get())
.setPlaybackLooperProvider(preloadLooperProvider)
.setTrackSelector(trackSelectorFactory.createTrackSelector(context))
.build();
}
/**
* Builds a {@link DefaultPreloadManager} instance.
*
* @throws IllegalStateException If this method has already been called.
*/
@Override
public DefaultPreloadManager build() {
checkState(!buildCalled);
buildCalled = true;
return new DefaultPreloadManager(this);
}
}
/**
* An implementation of {@link TargetPreloadStatusControl.PreloadStatus} that describes the
* preload status of the {@link PreloadMediaSource}.
@ -106,30 +324,39 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
}
private final RendererCapabilitiesList rendererCapabilitiesList;
private final TrackSelector trackSelector;
private final PlaybackLooperProvider preloadLooperProvider;
private final PreloadMediaSource.Factory preloadMediaSourceFactory;
private final boolean deprecatedConstructorCalled;
private DefaultPreloadManager(Builder builder) {
super(
new RankingDataComparator(),
builder.targetPreloadStatusControl,
builder.mediaSourceFactorySupplier.get());
rendererCapabilitiesList =
new DefaultRendererCapabilitiesList.Factory(builder.renderersFactorySupplier.get())
.createRendererCapabilitiesList();
preloadLooperProvider = builder.preloadLooperProvider;
trackSelector = builder.trackSelectorFactory.createTrackSelector(builder.context);
BandwidthMeter bandwidthMeter = builder.bandwidthMeterSupplier.get();
trackSelector.init(() -> {}, bandwidthMeter);
preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
builder.mediaSourceFactorySupplier.get(),
new SourcePreloadControl(),
trackSelector,
bandwidthMeter,
rendererCapabilitiesList.getRendererCapabilities(),
builder.loadControlSupplier.get().getAllocator(),
preloadLooperProvider.obtainLooper());
deprecatedConstructorCalled = false;
}
/**
* Constructs a new instance.
*
* @param targetPreloadStatusControl The {@link TargetPreloadStatusControl}.
* @param mediaSourceFactory The {@link MediaSource.Factory}.
* @param trackSelector The {@link TrackSelector}. The instance passed should be {@link
* TrackSelector#init(TrackSelector.InvalidationListener, BandwidthMeter) initialized}.
* @param bandwidthMeter The {@link BandwidthMeter}. It should be the same bandwidth meter of the
* {@link ExoPlayer} that will play the managed {@link PreloadMediaSource}.
* @param rendererCapabilitiesListFactory The {@link RendererCapabilitiesList.Factory}. To make
* preloading work properly, it must create a {@link RendererCapabilitiesList} holding an
* {@linkplain RendererCapabilitiesList#getRendererCapabilities() array of renderer
* capabilities} that matches the {@linkplain ExoPlayer#getRendererCount() count} and the
* {@linkplain ExoPlayer#getRendererType(int) renderer types} of the array of {@linkplain
* Renderer renderers} created by the {@link RenderersFactory} used by the {@link ExoPlayer}
* that will play the managed {@link PreloadMediaSource}.
* @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 managed {@link
* PreloadMediaSource}.
* @deprecated Use {@link Builder} instead.
*/
@Deprecated
public DefaultPreloadManager(
TargetPreloadStatusControl<Integer> targetPreloadStatusControl,
MediaSource.Factory mediaSourceFactory,
@ -141,6 +368,8 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
super(new RankingDataComparator(), targetPreloadStatusControl, mediaSourceFactory);
this.rendererCapabilitiesList =
rendererCapabilitiesListFactory.createRendererCapabilitiesList();
this.preloadLooperProvider = new PlaybackLooperProvider(preloadLooper);
this.trackSelector = trackSelector;
preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mediaSourceFactory,
@ -149,7 +378,8 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
bandwidthMeter,
rendererCapabilitiesList.getRendererCapabilities(),
allocator,
preloadLooper);
preloadLooperProvider.obtainLooper());
deprecatedConstructorCalled = true;
}
/**
@ -189,6 +419,12 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Override
protected void releaseInternal() {
rendererCapabilitiesList.release();
preloadLooperProvider.releaseLooper();
if (!deprecatedConstructorCalled) {
// TODO: Remove the property deprecatedConstructorCalled and release the TrackSelector anyway
// after the deprecated constructor is removed.
trackSelector.release();
}
}
private static final class RankingDataComparator implements Comparator<Integer> {

View File

@ -17,6 +17,7 @@ package androidx.media3.exoplayer.trackselection;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
@ -94,6 +95,21 @@ import androidx.media3.exoplayer.upstream.BandwidthMeter;
@UnstableApi
public abstract class TrackSelector {
/**
* Factory for creating {@linkplain TrackSelector track selectors} from {@linkplain Context
* contexts}.
*/
public interface Factory {
/**
* Creates a new {@link TrackSelector} with the specified {@link Context}.
*
* @param context The context.
* @return The new {@linkplain TrackSelector track selector}.
*/
TrackSelector createTrackSelector(Context context);
}
/** Notified when selections previously made by a {@link TrackSelector} are no longer valid. */
public interface InvalidationListener {

View File

@ -39,9 +39,7 @@ import androidx.media3.common.util.SystemClock;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RendererCapabilitiesList;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
@ -53,11 +51,7 @@ import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import androidx.media3.test.utils.FakeAudioRenderer;
@ -84,19 +78,14 @@ import org.mockito.Mock;
@RunWith(AndroidJUnit4.class)
public class DefaultPreloadManagerTest {
private Context context;
@Mock private TargetPreloadStatusControl<Integer> mockTargetPreloadStatusControl;
private TrackSelector trackSelector;
private Allocator allocator;
private BandwidthMeter bandwidthMeter;
private RendererCapabilitiesList.Factory rendererCapabilitiesListFactory;
private RenderersFactory renderersFactory;
@Before
public void setUp() {
trackSelector = new DefaultTrackSelector(ApplicationProvider.getApplicationContext());
allocator = new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
bandwidthMeter =
new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build();
RenderersFactory renderersFactory =
context = ApplicationProvider.getApplicationContext();
renderersFactory =
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
new Renderer[] {
new FakeVideoRenderer(
@ -106,21 +95,15 @@ public class DefaultPreloadManagerTest {
SystemClock.DEFAULT.createHandler(handler.getLooper(), /* callback= */ null),
audioListener)
};
rendererCapabilitiesListFactory = new DefaultRendererCapabilitiesList.Factory(renderersFactory);
trackSelector.init(/* listener= */ () -> {}, bandwidthMeter);
}
@Test
public void addByMediaItems_getCorrectCountAndSources() {
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
mockTargetPreloadStatusControl,
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()),
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, mockTargetPreloadStatusControl)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
@ -138,14 +121,10 @@ public class DefaultPreloadManagerTest {
@Test
public void addByMediaSources_getCorrectCountAndSources() {
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
mockTargetPreloadStatusControl,
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()),
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, mockTargetPreloadStatusControl)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
@ -167,14 +146,10 @@ public class DefaultPreloadManagerTest {
@Test
public void getMediaSourceForMediaItemNotAdded() {
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
mockTargetPreloadStatusControl,
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()),
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, mockTargetPreloadStatusControl)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem mediaItem =
new MediaItem.Builder()
.setMediaId("mediaId1")
@ -206,14 +181,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
@ -268,14 +240,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
@ -325,14 +294,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
fakeMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(fakeMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
@ -352,7 +318,9 @@ public class DefaultPreloadManagerTest {
PreloadMediaSource preloadMediaSource0 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem0);
preloadMediaSource0.prepareSource(
(source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
(source, timeline) -> {},
DefaultBandwidthMeter.getSingletonInstance(context).getTransferListener(),
PlayerId.UNSET);
wrappedMediaSource0.setAllowPreparation(true);
wrappedMediaSource1.setAllowPreparation(true);
shadowOf(preloadThread.getLooper()).idle();
@ -377,14 +345,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
fakeMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(fakeMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
@ -451,14 +416,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
@ -549,14 +511,11 @@ public class DefaultPreloadManagerTest {
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
preloadThread.getLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(preloadThread.getLooper())
.build();
TestPreloadManagerListener preloadManagerListener = new TestPreloadManagerListener();
preloadManager.addListener(preloadManagerListener);
preloadManager.add(mediaItem0, /* rankingData= */ 0);
@ -633,14 +592,11 @@ public class DefaultPreloadManagerTest {
};
});
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mockMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 =
mediaItemBuilder
@ -685,7 +641,9 @@ public class DefaultPreloadManagerTest {
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem4);
// Simulate that preloadMediaSource4 is using by the player.
preloadMediaSource4.prepareSource(
(source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
(source, timeline) -> {},
DefaultBandwidthMeter.getSingletonInstance(context).getTransferListener(),
PlayerId.UNSET);
currentPlayingIndex.set(4);
preloadManager.setCurrentPlayingIndex(4);
@ -706,14 +664,11 @@ public class DefaultPreloadManagerTest {
rankingData -> new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mockMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
@ -757,14 +712,11 @@ public class DefaultPreloadManagerTest {
rankingData -> new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mockMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
@ -834,14 +786,11 @@ public class DefaultPreloadManagerTest {
return underlyingRenderers.toArray(new Renderer[2]);
};
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mockMediaSourceFactory,
trackSelector,
bandwidthMeter,
new DefaultRendererCapabilitiesList.Factory(renderersFactory),
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
@ -905,14 +854,11 @@ public class DefaultPreloadManagerTest {
return underlyingRenderers.toArray(new Renderer[2]);
};
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
mockMediaSourceFactory,
trackSelector,
bandwidthMeter,
new DefaultRendererCapabilitiesList.Factory(renderersFactory),
allocator,
Util.getCurrentOrMainLooper());
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();