Reuse thread in MetadataRetriever

MetadataRetriever is often used in a loop to retrieve data about many
items in a directory for example. In such cases, it's wasteful to
start a new 'playback' thread for each retrieval, in particular since
this thread is doing almost nothing (just triggering loads and handling
events). This change re-uses an existing thread if one exists already.

PiperOrigin-RevId: 650633343
This commit is contained in:
tonihei 2024-07-09 08:13:12 -07:00 committed by Copybara-Service
parent 00d1e70a34
commit 5777e30979

View File

@ -17,11 +17,14 @@
package androidx.media3.exoplayer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -94,8 +97,6 @@ public final class MetadataRetriever {
private static ListenableFuture<TrackGroupArray> retrieveMetadata(
MediaSource.Factory mediaSourceFactory, MediaItem mediaItem, Clock clock) {
// Recreate thread and handler every time this method is called so that it can be used
// concurrently.
return new MetadataRetrieverInternal(mediaSourceFactory, clock).retrieveMetadata(mediaItem);
}
@ -106,17 +107,17 @@ public final class MetadataRetriever {
private static final int MESSAGE_CONTINUE_LOADING = 3;
private static final int MESSAGE_RELEASE = 4;
private static final SharedWorkerThread SHARED_WORKER_THREAD = new SharedWorkerThread();
private final MediaSource.Factory mediaSourceFactory;
private final HandlerThread mediaSourceThread;
private final HandlerWrapper mediaSourceHandler;
private final SettableFuture<TrackGroupArray> trackGroupsFuture;
public MetadataRetrieverInternal(MediaSource.Factory mediaSourceFactory, Clock clock) {
this.mediaSourceFactory = mediaSourceFactory;
mediaSourceThread = new HandlerThread("ExoPlayer:MetadataRetriever");
mediaSourceThread.start();
Looper workerThreadLooper = SHARED_WORKER_THREAD.addWorker();
mediaSourceHandler =
clock.createHandler(mediaSourceThread.getLooper(), new MediaSourceHandlerCallback());
clock.createHandler(workerThreadLooper, new MediaSourceHandlerCallback());
trackGroupsFuture = SettableFuture.create();
}
@ -172,7 +173,7 @@ public final class MetadataRetriever {
}
checkNotNull(mediaSource).releaseSource(mediaSourceCaller);
mediaSourceHandler.removeCallbacksAndMessages(/* token= */ null);
mediaSourceThread.quit();
SHARED_WORKER_THREAD.removeWorker();
return true;
default:
return false;
@ -225,4 +226,27 @@ public final class MetadataRetriever {
}
}
}
private static final class SharedWorkerThread {
@Nullable private HandlerThread mediaSourceThread;
private int referenceCount;
public synchronized Looper addWorker() {
if (mediaSourceThread == null) {
checkState(referenceCount == 0);
mediaSourceThread = new HandlerThread("ExoPlayer:MetadataRetriever");
mediaSourceThread.start();
}
referenceCount++;
return mediaSourceThread.getLooper();
}
public synchronized void removeWorker() {
if (--referenceCount == 0) {
checkNotNull(mediaSourceThread).quit();
mediaSourceThread = null;
}
}
}
}