diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MetadataRetriever.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MetadataRetriever.java index 1600ab01fe..7b7d4c0047 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MetadataRetriever.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MetadataRetriever.java @@ -16,6 +16,7 @@ package androidx.media3.exoplayer; +import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; @@ -24,6 +25,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; @@ -44,12 +46,18 @@ import androidx.media3.extractor.ExtractorsFactory; import androidx.media3.extractor.mp4.Mp4Extractor; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.atomic.AtomicInteger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Retrieves the static metadata of {@link MediaItem MediaItems}. */ @UnstableApi public final class MetadataRetriever { + /** The default number of maximum parallel retrievals. */ + public static final int DEFAULT_MAXIMUM_PARALLEL_RETRIEVALS = 5; + private MetadataRetriever() {} /** @@ -97,7 +105,19 @@ public final class MetadataRetriever { private static ListenableFuture retrieveMetadata( MediaSource.Factory mediaSourceFactory, MediaItem mediaItem, Clock clock) { - return new MetadataRetrieverInternal(mediaSourceFactory, clock).retrieveMetadata(mediaItem); + return new MetadataRetrieverInternal(mediaSourceFactory, mediaItem, clock).retrieveMetadata(); + } + + /** + * Sets the maximum number of metadata retrievals run in parallel. + * + *

The default is {@link #DEFAULT_MAXIMUM_PARALLEL_RETRIEVALS}. + * + * @param maximumParallelRetrievals The maximum number of parallel retrievals. + */ + public static void setMaximumParallelRetrievals(int maximumParallelRetrievals) { + checkArgument(maximumParallelRetrievals >= 1); + SharedWorkerThread.MAX_PARALLEL_RETRIEVALS.set(maximumParallelRetrievals); } private static final class MetadataRetrieverInternal { @@ -110,22 +130,29 @@ public final class MetadataRetriever { private static final SharedWorkerThread SHARED_WORKER_THREAD = new SharedWorkerThread(); private final MediaSource.Factory mediaSourceFactory; + private final MediaItem mediaItem; private final HandlerWrapper mediaSourceHandler; private final SettableFuture trackGroupsFuture; - public MetadataRetrieverInternal(MediaSource.Factory mediaSourceFactory, Clock clock) { + public MetadataRetrieverInternal( + MediaSource.Factory mediaSourceFactory, MediaItem mediaItem, Clock clock) { this.mediaSourceFactory = mediaSourceFactory; + this.mediaItem = mediaItem; Looper workerThreadLooper = SHARED_WORKER_THREAD.addWorker(); mediaSourceHandler = clock.createHandler(workerThreadLooper, new MediaSourceHandlerCallback()); trackGroupsFuture = SettableFuture.create(); } - public ListenableFuture retrieveMetadata(MediaItem mediaItem) { - mediaSourceHandler.obtainMessage(MESSAGE_PREPARE_SOURCE, mediaItem).sendToTarget(); + public ListenableFuture retrieveMetadata() { + SHARED_WORKER_THREAD.startRetrieval(this); return trackGroupsFuture; } + public void start() { + mediaSourceHandler.obtainMessage(MESSAGE_PREPARE_SOURCE, mediaItem).sendToTarget(); + } + private final class MediaSourceHandlerCallback implements Handler.Callback { private static final int ERROR_POLL_INTERVAL_MS = 100; @@ -229,9 +256,18 @@ public final class MetadataRetriever { private static final class SharedWorkerThread { + public static final AtomicInteger MAX_PARALLEL_RETRIEVALS = + new AtomicInteger(DEFAULT_MAXIMUM_PARALLEL_RETRIEVALS); + + private final Deque pendingRetrievals; + @Nullable private HandlerThread mediaSourceThread; private int referenceCount; + public SharedWorkerThread() { + pendingRetrievals = new ArrayDeque<>(); + } + public synchronized Looper addWorker() { if (mediaSourceThread == null) { checkState(referenceCount == 0); @@ -242,10 +278,29 @@ public final class MetadataRetriever { return mediaSourceThread.getLooper(); } + public synchronized void startRetrieval(MetadataRetrieverInternal retrieval) { + pendingRetrievals.addLast(retrieval); + maybeStartNewRetrieval(); + } + public synchronized void removeWorker() { if (--referenceCount == 0) { checkNotNull(mediaSourceThread).quit(); mediaSourceThread = null; + } else { + maybeStartNewRetrieval(); + } + } + + @GuardedBy("this") + private void maybeStartNewRetrieval() { + if (pendingRetrievals.isEmpty()) { + return; + } + int activeRetrievals = referenceCount - pendingRetrievals.size(); + if (activeRetrievals < MAX_PARALLEL_RETRIEVALS.get()) { + MetadataRetrieverInternal retrieval = pendingRetrievals.removeFirst(); + retrieval.start(); } } }