Limit maximum number of parallel metadata retrievals.

The operation almost always acquires resources straight-away,
for example a new thread or file access. This means starting
many metadata retrievals in parallel easily causes resource
contention. This problem can be alleviated by limiting the
maximum number of parallel retrievals.

Local testing showed a 20% speedup for local file retrievals
when limited to 5 in parallel. Any more parallel retrievals
did not show any improvement or started getting slower again
the more operations are allowed.

PiperOrigin-RevId: 650674685
This commit is contained in:
tonihei 2024-07-09 10:22:04 -07:00 committed by Copybara-Service
parent 5777e30979
commit b145a9b35e

View File

@ -16,6 +16,7 @@
package androidx.media3.exoplayer; 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.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
@ -24,6 +25,7 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C; import androidx.media3.common.C;
@ -44,12 +46,18 @@ import androidx.media3.extractor.ExtractorsFactory;
import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; 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; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Retrieves the static metadata of {@link MediaItem MediaItems}. */ /** Retrieves the static metadata of {@link MediaItem MediaItems}. */
@UnstableApi @UnstableApi
public final class MetadataRetriever { public final class MetadataRetriever {
/** The default number of maximum parallel retrievals. */
public static final int DEFAULT_MAXIMUM_PARALLEL_RETRIEVALS = 5;
private MetadataRetriever() {} private MetadataRetriever() {}
/** /**
@ -97,7 +105,19 @@ public final class MetadataRetriever {
private static ListenableFuture<TrackGroupArray> retrieveMetadata( private static ListenableFuture<TrackGroupArray> retrieveMetadata(
MediaSource.Factory mediaSourceFactory, MediaItem mediaItem, Clock clock) { 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.
*
* <p>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 { private static final class MetadataRetrieverInternal {
@ -110,22 +130,29 @@ public final class MetadataRetriever {
private static final SharedWorkerThread SHARED_WORKER_THREAD = new SharedWorkerThread(); private static final SharedWorkerThread SHARED_WORKER_THREAD = new SharedWorkerThread();
private final MediaSource.Factory mediaSourceFactory; private final MediaSource.Factory mediaSourceFactory;
private final MediaItem mediaItem;
private final HandlerWrapper mediaSourceHandler; private final HandlerWrapper mediaSourceHandler;
private final SettableFuture<TrackGroupArray> trackGroupsFuture; private final SettableFuture<TrackGroupArray> trackGroupsFuture;
public MetadataRetrieverInternal(MediaSource.Factory mediaSourceFactory, Clock clock) { public MetadataRetrieverInternal(
MediaSource.Factory mediaSourceFactory, MediaItem mediaItem, Clock clock) {
this.mediaSourceFactory = mediaSourceFactory; this.mediaSourceFactory = mediaSourceFactory;
this.mediaItem = mediaItem;
Looper workerThreadLooper = SHARED_WORKER_THREAD.addWorker(); Looper workerThreadLooper = SHARED_WORKER_THREAD.addWorker();
mediaSourceHandler = mediaSourceHandler =
clock.createHandler(workerThreadLooper, new MediaSourceHandlerCallback()); clock.createHandler(workerThreadLooper, new MediaSourceHandlerCallback());
trackGroupsFuture = SettableFuture.create(); trackGroupsFuture = SettableFuture.create();
} }
public ListenableFuture<TrackGroupArray> retrieveMetadata(MediaItem mediaItem) { public ListenableFuture<TrackGroupArray> retrieveMetadata() {
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE_SOURCE, mediaItem).sendToTarget(); SHARED_WORKER_THREAD.startRetrieval(this);
return trackGroupsFuture; return trackGroupsFuture;
} }
public void start() {
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE_SOURCE, mediaItem).sendToTarget();
}
private final class MediaSourceHandlerCallback implements Handler.Callback { private final class MediaSourceHandlerCallback implements Handler.Callback {
private static final int ERROR_POLL_INTERVAL_MS = 100; private static final int ERROR_POLL_INTERVAL_MS = 100;
@ -229,9 +256,18 @@ public final class MetadataRetriever {
private static final class SharedWorkerThread { private static final class SharedWorkerThread {
public static final AtomicInteger MAX_PARALLEL_RETRIEVALS =
new AtomicInteger(DEFAULT_MAXIMUM_PARALLEL_RETRIEVALS);
private final Deque<MetadataRetrieverInternal> pendingRetrievals;
@Nullable private HandlerThread mediaSourceThread; @Nullable private HandlerThread mediaSourceThread;
private int referenceCount; private int referenceCount;
public SharedWorkerThread() {
pendingRetrievals = new ArrayDeque<>();
}
public synchronized Looper addWorker() { public synchronized Looper addWorker() {
if (mediaSourceThread == null) { if (mediaSourceThread == null) {
checkState(referenceCount == 0); checkState(referenceCount == 0);
@ -242,10 +278,29 @@ public final class MetadataRetriever {
return mediaSourceThread.getLooper(); return mediaSourceThread.getLooper();
} }
public synchronized void startRetrieval(MetadataRetrieverInternal retrieval) {
pendingRetrievals.addLast(retrieval);
maybeStartNewRetrieval();
}
public synchronized void removeWorker() { public synchronized void removeWorker() {
if (--referenceCount == 0) { if (--referenceCount == 0) {
checkNotNull(mediaSourceThread).quit(); checkNotNull(mediaSourceThread).quit();
mediaSourceThread = null; 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();
} }
} }
} }