From 98dc7f2def8b7404383a118d02f309870a5bede4 Mon Sep 17 00:00:00 2001 From: tianyifeng Date: Fri, 11 Oct 2024 08:52:28 -0700 Subject: [PATCH] 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 --- RELEASENOTES.md | 3 + .../media3/demo/shortform/PlayerPool.kt | 33 +-- .../viewpager/ViewPagerMediaAdapter.kt | 39 +-- .../source/preload/BasePreloadManager.java | 7 +- .../source/preload/DefaultPreloadManager.java | 280 ++++++++++++++++-- .../trackselection/TrackSelector.java | 16 + .../preload/DefaultPreloadManagerTest.java | 208 +++++-------- 7 files changed, 368 insertions(+), 218 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 00de9a258a..ba28adca08 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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. diff --git a/demos/shortform/src/main/java/androidx/media3/demo/shortform/PlayerPool.kt b/demos/shortform/src/main/java/androidx/media3/demo/shortform/PlayerPool.kt index e1d791832c..801f75bb96 100644 --- a/demos/shortform/src/main/java/androidx/media3/demo/shortform/PlayerPool.kt +++ b/demos/shortform/src/main/java/androidx/media3/demo/shortform/PlayerPool.kt @@ -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 = LinkedList() private val playerMap: BiMap = Maps.synchronizedBiMap(HashBiMap.create()) private val playerRequestTokenSet: MutableSet = Collections.synchronizedSet(HashSet()) - 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 diff --git a/demos/shortform/src/main/java/androidx/media3/demo/shortform/viewpager/ViewPagerMediaAdapter.kt b/demos/shortform/src/main/java/androidx/media3/demo/shortform/viewpager/ViewPagerMediaAdapter.kt index a78709fce4..b3c252cb6d 100644 --- a/demos/shortform/src/main/java/androidx/media3/demo/shortform/viewpager/ViewPagerMediaAdapter.kt +++ b/demos/shortform/src/main/java/androidx/media3/demo/shortform/viewpager/ViewPagerMediaAdapter.kt @@ -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() { - private val playbackThread: HandlerThread = - HandlerThread("playback-thread", Process.THREAD_PRIORITY_AUDIO) private val preloadManager: DefaultPreloadManager private val currentMediaItemsAndIndexes: ArrayDeque> = 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) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java index 13961ab684..76707df129 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java @@ -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 { protected final Comparator rankingDataComparator; protected final TargetPreloadStatusControl targetPreloadStatusControl; - protected final MediaSource.Factory mediaSourceFactory; + protected Supplier mediaSourceFactorySupplier; public BuilderBase( Comparator rankingDataComparator, TargetPreloadStatusControl targetPreloadStatusControl, - MediaSource.Factory mediaSourceFactory) { + Supplier mediaSourceFactorySupplier) { this.rankingDataComparator = rankingDataComparator; this.targetPreloadStatusControl = targetPreloadStatusControl; - this.mediaSourceFactory = mediaSourceFactory; + this.mediaSourceFactorySupplier = mediaSourceFactorySupplier; } public abstract BasePreloadManager build(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java index d3740991ce..ca8bf45eec 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManager.java @@ -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 { + /** A builder for {@link DefaultPreloadManager} instances. */ + public static final class Builder extends BuilderBase { + + private final Context context; + private PlaybackLooperProvider preloadLooperProvider; + private TrackSelector.Factory trackSelectorFactory; + private Supplier bandwidthMeterSupplier; + private Supplier renderersFactorySupplier; + private Supplier loadControlSupplier; + private boolean buildCalled; + private boolean buildExoPlayerCalled; + + /** + * Creates a builder. + * + * @param context A {@link Context}. + * @param targetPreloadStatusControl A {@link TargetPreloadStatusControl}. + */ + public Builder( + Context context, TargetPreloadStatusControl 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}. + * + *

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}. + * + *

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}. + * + *

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}. + * + *

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}. + * + *

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. + * + *

The backing thread should run with priority {@link Process#THREAD_PRIORITY_AUDIO} and + * should handle messages within 10ms. + * + *

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}. + * + *

See {@link #buildExoPlayer(ExoPlayer.Builder)} for the list of values populated on and + * resulting from this builder that the built {@link ExoPlayer} uses. + * + *

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. + * + *

The built {@link ExoPlayer} uses the following values populated on and resulting from this + * builder: + * + *

    + *
  • {@link #setMediaSourceFactory(MediaSource.Factory) MediaSource.Factory} + *
  • {@link #setRenderersFactory(RenderersFactory) RenderersFactory} + *
  • {@link #setTrackSelectorFactory(TrackSelector.Factory) TrackSelector.Factory} + *
  • {@link #setLoadControl(LoadControl) LoadControl} + *
  • {@link #setBandwidthMeter(BandwidthMeter) BandwidthMeter} + *
  • {@linkplain #setPreloadLooper(Looper)} preload looper} + *
+ * + *

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 { } 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 targetPreloadStatusControl, MediaSource.Factory mediaSourceFactory, @@ -141,6 +368,8 @@ public final class DefaultPreloadManager extends BasePreloadManager { 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 { bandwidthMeter, rendererCapabilitiesList.getRendererCapabilities(), allocator, - preloadLooper); + preloadLooperProvider.obtainLooper()); + deprecatedConstructorCalled = true; } /** @@ -189,6 +419,12 @@ public final class DefaultPreloadManager extends BasePreloadManager { @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 { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java index 47e72ffaf1..6c3c1cd5fc 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java @@ -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 { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java index 2cdcf0c739..0470a0ab48 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java @@ -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 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();