Skip to content

Commit 731c32e

Browse files
tianyifshahdDaghash
authored andcommitted
[PreCacheHelper] Start downloader after loaders for preparing released
This is only applied to progressive media only. #cherrypick PiperOrigin-RevId: 778856425 (cherry picked from commit b8066f9)
1 parent 9cf7b0c commit 731c32e

File tree

4 files changed

+168
-30
lines changed

4 files changed

+168
-30
lines changed

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadHelper.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@
7575
import androidx.media3.exoplayer.upstream.Allocator;
7676
import androidx.media3.exoplayer.upstream.BandwidthMeter;
7777
import androidx.media3.exoplayer.upstream.DefaultAllocator;
78+
import androidx.media3.exoplayer.util.ReleasableExecutor;
7879
import androidx.media3.extractor.ExtractorsFactory;
7980
import androidx.media3.extractor.SeekMap;
81+
import com.google.common.base.Supplier;
8082
import com.google.common.collect.ImmutableList;
8183
import com.google.errorprone.annotations.CanIgnoreReturnValue;
8284
import java.io.IOException;
@@ -118,11 +120,13 @@ public static final class Factory {
118120
@Nullable private RenderersFactory renderersFactory;
119121
private TrackSelectionParameters trackSelectionParameters;
120122
@Nullable private DrmSessionManager drmSessionManager;
123+
@Nullable private Supplier<ReleasableExecutor> loadExecutorSupplier;
121124
private boolean debugLoggingEnabled;
122125

123126
/** Creates a {@link Factory}. */
124127
public Factory() {
125128
this.trackSelectionParameters = DEFAULT_TRACK_SELECTOR_PARAMETERS;
129+
loadExecutorSupplier = null;
126130
}
127131

128132
/**
@@ -181,6 +185,21 @@ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManage
181185
return this;
182186
}
183187

188+
/**
189+
* Sets a supplier for an {@link ReleasableExecutor} that is used for loading the media.
190+
*
191+
* <p>This is only used for progressive streams.
192+
*
193+
* @param loadExecutor A {@link Supplier} that provides an externally managed {@link
194+
* ReleasableExecutor} for loading.
195+
* @return This factory, for convenience.
196+
*/
197+
@CanIgnoreReturnValue
198+
public Factory setLoadExecutor(Supplier<ReleasableExecutor> loadExecutor) {
199+
this.loadExecutorSupplier = loadExecutor;
200+
return this;
201+
}
202+
184203
/**
185204
* Sets whether to log debug information. The default is {@code false}.
186205
*
@@ -209,7 +228,10 @@ public DownloadHelper create(MediaItem mediaItem) {
209228
isProgressive && dataSourceFactory == null
210229
? null
211230
: createMediaSourceInternal(
212-
mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
231+
mediaItem,
232+
castNonNull(dataSourceFactory),
233+
drmSessionManager,
234+
loadExecutorSupplier),
213235
trackSelectionParameters,
214236
renderersFactory != null
215237
? new DefaultRendererCapabilitiesList.Factory(renderersFactory)
@@ -461,7 +483,10 @@ public static MediaSource createMediaSource(
461483
DataSource.Factory dataSourceFactory,
462484
@Nullable DrmSessionManager drmSessionManager) {
463485
return createMediaSourceInternal(
464-
downloadRequest.toMediaItem(), dataSourceFactory, drmSessionManager);
486+
downloadRequest.toMediaItem(),
487+
dataSourceFactory,
488+
drmSessionManager,
489+
/* loadExecutorSupplier= */ null);
465490
}
466491

467492
private static final String TAG = "DownloadHelper";
@@ -1185,11 +1210,19 @@ private TrackSelectorResult runTrackSelection(int periodIndex) throws ExoPlaybac
11851210
private static MediaSource createMediaSourceInternal(
11861211
MediaItem mediaItem,
11871212
DataSource.Factory dataSourceFactory,
1188-
@Nullable DrmSessionManager drmSessionManager) {
1189-
MediaSource.Factory mediaSourceFactory =
1190-
isProgressive(checkNotNull(mediaItem.localConfiguration))
1191-
? new ProgressiveMediaSource.Factory(dataSourceFactory)
1192-
: new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY);
1213+
@Nullable DrmSessionManager drmSessionManager,
1214+
@Nullable Supplier<ReleasableExecutor> loadExecutorSupplier) {
1215+
MediaSource.Factory mediaSourceFactory;
1216+
if (isProgressive(checkNotNull(mediaItem.localConfiguration))) {
1217+
mediaSourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);
1218+
if (loadExecutorSupplier != null) {
1219+
((ProgressiveMediaSource.Factory) mediaSourceFactory)
1220+
.setDownloadExecutor(loadExecutorSupplier);
1221+
}
1222+
} else {
1223+
mediaSourceFactory =
1224+
new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY);
1225+
}
11931226
if (drmSessionManager != null) {
11941227
mediaSourceFactory.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
11951228
}

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,21 @@ public Factory setDrmSessionManagerProvider(
259259
@CanIgnoreReturnValue
260260
public <T extends Executor> Factory setDownloadExecutor(
261261
Supplier<T> downloadExecutor, Consumer<T> downloadExecutorReleaser) {
262-
this.downloadExecutorSupplier =
263-
() -> ReleasableExecutor.from(downloadExecutor.get(), downloadExecutorReleaser);
262+
setDownloadExecutor(
263+
() -> ReleasableExecutor.from(downloadExecutor.get(), downloadExecutorReleaser));
264+
return this;
265+
}
266+
267+
/**
268+
* Sets a supplier for an {@link ReleasableExecutor} that is used for loading the media.
269+
*
270+
* @param downloadExecutor A {@link Supplier} that provides an externally managed {@link
271+
* ReleasableExecutor} for downloading and extraction.
272+
* @return This factory, for convenience.
273+
*/
274+
@CanIgnoreReturnValue
275+
public MediaSource.Factory setDownloadExecutor(Supplier<ReleasableExecutor> downloadExecutor) {
276+
this.downloadExecutorSupplier = downloadExecutor;
264277
return this;
265278
}
266279

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreCacheHelper.java

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@
4343
import androidx.media3.exoplayer.offline.Downloader;
4444
import androidx.media3.exoplayer.offline.DownloaderFactory;
4545
import androidx.media3.exoplayer.source.MediaSource;
46+
import androidx.media3.exoplayer.util.ReleasableExecutor;
4647
import com.google.common.base.Supplier;
4748
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4849
import java.io.IOException;
4950
import java.util.concurrent.CancellationException;
5051
import java.util.concurrent.Executor;
52+
import java.util.concurrent.ExecutorService;
53+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
5154

5255
/** A helper for pre-caching a single media. */
5356
@UnstableApi
@@ -260,7 +263,7 @@ public PreCacheHelper create(MediaItem mediaItem) {
260263
new DefaultDownloaderFactory(cacheDataSourceFactory, downloadExecutor);
261264
return new PreCacheHelper(
262265
mediaItem,
263-
/* mediaSourceFactory= */ null,
266+
/* testMediaSourceFactory= */ null,
264267
downloadHelperFactory,
265268
downloaderFactory,
266269
preCacheLooper,
@@ -271,7 +274,9 @@ public PreCacheHelper create(MediaItem mediaItem) {
271274
@VisibleForTesting /* package */ static final int DEFAULT_MIN_RETRY_COUNT = 5;
272275

273276
private final MediaItem mediaItem;
274-
private final Supplier<DownloadHelper> downloadHelperSupplier;
277+
278+
@Nullable private final MediaSource.Factory testMediaSourceFactory;
279+
private final DownloadHelper.Factory downloadHelperFactory;
275280
private final DownloaderFactory downloaderFactory;
276281
@Nullable private final Listener listener;
277282
private final Handler preCacheHandler;
@@ -280,17 +285,14 @@ public PreCacheHelper create(MediaItem mediaItem) {
280285

281286
/* package */ PreCacheHelper(
282287
MediaItem mediaItem,
283-
@Nullable MediaSource.Factory mediaSourceFactory,
288+
@Nullable MediaSource.Factory testMediaSourceFactory,
284289
DownloadHelper.Factory downloadHelperFactory,
285290
DownloaderFactory downloaderFactory,
286291
Looper preCacheLooper,
287292
@Nullable Listener listener) {
288293
this.mediaItem = mediaItem;
289-
this.downloadHelperSupplier =
290-
() ->
291-
mediaSourceFactory != null
292-
? downloadHelperFactory.create(mediaSourceFactory.createMediaSource(mediaItem))
293-
: downloadHelperFactory.create(mediaItem);
294+
this.testMediaSourceFactory = testMediaSourceFactory;
295+
this.downloadHelperFactory = downloadHelperFactory;
294296
this.downloaderFactory = downloaderFactory;
295297
this.listener = listener;
296298
this.preCacheHandler = Util.createHandler(preCacheLooper, /* callback= */ null);
@@ -352,14 +354,84 @@ public void release(boolean removeCachedContent) {
352354
});
353355
}
354356

357+
private static final class ReleasableSingleThreadExecutor implements ReleasableExecutor {
358+
359+
private final ExecutorService executor;
360+
private final Runnable releaseRunnable;
361+
362+
private ReleasableSingleThreadExecutor(Runnable releaseRunnable) {
363+
this.executor = Util.newSingleThreadExecutor("PreCacheHelper:Loader");
364+
this.releaseRunnable = releaseRunnable;
365+
}
366+
367+
@Override
368+
public void release() {
369+
execute(releaseRunnable);
370+
executor.shutdown();
371+
}
372+
373+
@Override
374+
public void execute(Runnable command) {
375+
executor.execute(command);
376+
}
377+
}
378+
379+
private static final class ReleasableExecutorSupplier implements Supplier<ReleasableExecutor> {
380+
private final Handler preCacheHandler;
381+
private @MonotonicNonNull DownloadCallback downloadCallback;
382+
383+
@GuardedBy("this")
384+
private int executorCount;
385+
386+
private ReleasableExecutorSupplier(Handler preCacheHandler) {
387+
this.preCacheHandler = preCacheHandler;
388+
}
389+
390+
public void setDownloadCallback(DownloadCallback downloadCallback) {
391+
this.downloadCallback = downloadCallback;
392+
}
393+
394+
@Override
395+
public ReleasableSingleThreadExecutor get() {
396+
synchronized (ReleasableExecutorSupplier.this) {
397+
executorCount++;
398+
}
399+
return new ReleasableSingleThreadExecutor(this::onExecutorReleased);
400+
}
401+
402+
private void onExecutorReleased() {
403+
synchronized (ReleasableExecutorSupplier.this) {
404+
checkState(executorCount > 0);
405+
executorCount--;
406+
if (wereExecutorsReleased()) {
407+
preCacheHandler.post(
408+
() -> {
409+
checkState(wereExecutorsReleased());
410+
if (downloadCallback != null) {
411+
downloadCallback.maybeSubmitPendingDownloadRequest();
412+
}
413+
});
414+
}
415+
}
416+
}
417+
418+
public boolean wereExecutorsReleased() {
419+
synchronized (ReleasableExecutorSupplier.this) {
420+
return executorCount == 0;
421+
}
422+
}
423+
}
424+
355425
private final class DownloadCallback implements DownloadHelper.Callback {
356426

357427
private final Object lock;
358428
private final long startPositionMs;
359429
private final long durationMs;
430+
@Nullable private final ReleasableExecutorSupplier releasableExecutorSupplier;
360431
private final DownloadHelper downloadHelper;
361432

362433
private boolean isPreparationOngoing;
434+
@Nullable private DownloadRequest pendingDownloadRequest;
363435
@Nullable private Downloader downloader;
364436
@Nullable private Task downloaderTask;
365437

@@ -371,7 +443,16 @@ public DownloadCallback(long startPositionMs, long durationMs) {
371443
this.lock = new Object();
372444
this.startPositionMs = startPositionMs;
373445
this.durationMs = durationMs;
374-
this.downloadHelper = downloadHelperSupplier.get();
446+
if (testMediaSourceFactory != null) {
447+
this.releasableExecutorSupplier = null;
448+
this.downloadHelper =
449+
downloadHelperFactory.create(testMediaSourceFactory.createMediaSource(mediaItem));
450+
} else {
451+
this.releasableExecutorSupplier = new ReleasableExecutorSupplier(preCacheHandler);
452+
downloadHelperFactory.setLoadExecutor(releasableExecutorSupplier);
453+
this.downloadHelper = downloadHelperFactory.create(mediaItem);
454+
this.releasableExecutorSupplier.setDownloadCallback(this);
455+
}
375456
this.isPreparationOngoing = true;
376457
this.downloadHelper.prepare(this);
377458
}
@@ -386,14 +467,11 @@ public void onPrepared(DownloadHelper helper, boolean tracksInfoAvailable) {
386467
downloadHelper.release();
387468
MediaItem updatedMediaItem = downloadRequest.toMediaItem(mediaItem.buildUpon());
388469
notifyListeners(listener -> listener.onPrepared(mediaItem, updatedMediaItem));
389-
downloader = downloaderFactory.createDownloader(downloadRequest);
390-
downloaderTask =
391-
new Task(
392-
downloader,
393-
/* isRemove= */ false,
394-
DEFAULT_MIN_RETRY_COUNT,
395-
/* downloadCallback= */ this);
396-
downloaderTask.start();
470+
pendingDownloadRequest = downloadRequest;
471+
if (releasableExecutorSupplier == null
472+
|| releasableExecutorSupplier.wereExecutorsReleased()) {
473+
maybeSubmitPendingDownloadRequest();
474+
}
397475
}
398476

399477
@Override
@@ -405,6 +483,21 @@ public void onPrepareError(DownloadHelper helper, IOException e) {
405483
notifyListeners(listener -> listener.onPrepareError(mediaItem, e));
406484
}
407485

486+
public void maybeSubmitPendingDownloadRequest() {
487+
checkState(Looper.myLooper() == preCacheHandler.getLooper());
488+
if (pendingDownloadRequest != null) {
489+
downloader = downloaderFactory.createDownloader(pendingDownloadRequest);
490+
downloaderTask =
491+
new Task(
492+
downloader,
493+
/* isRemove= */ false,
494+
DEFAULT_MIN_RETRY_COUNT,
495+
/* downloadCallback= */ this);
496+
downloaderTask.start();
497+
pendingDownloadRequest = null;
498+
}
499+
}
500+
408501
public void onDownloadStopped(Task task) {
409502
preCacheHandler.post(
410503
() -> {
@@ -440,6 +533,7 @@ public void cancel(boolean removeCachedContent) {
440533
synchronized (lock) {
441534
isCanceled = true;
442535
}
536+
pendingDownloadRequest = null;
443537
downloadHelper.release();
444538
if (downloaderTask != null && downloaderTask.isRemove) {
445539
return;

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/PreCacheHelperTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
import java.util.concurrent.atomic.AtomicReference;
7575
import org.junit.After;
7676
import org.junit.Before;
77-
import org.junit.Ignore;
7877
import org.junit.Test;
7978
import org.junit.runner.RunWith;
8079

@@ -113,7 +112,6 @@ public void tearDown() {
113112
}
114113

115114
@Test
116-
@Ignore("TODO: Fix the flakiness of this test and re-enable it")
117115
public void preCache_succeeds() throws Exception {
118116
PreCacheHelper preCacheHelper =
119117
new PreCacheHelper.Factory(
@@ -931,7 +929,7 @@ public void releaseWithRemovingCachedContent_whileDownloadOngoing_downloaderCanc
931929
PreCacheHelper preCacheHelper =
932930
new PreCacheHelper(
933931
mediaItem,
934-
/* mediaSourceFactory= */ null,
932+
/* testMediaSourceFactory= */ null,
935933
downloadHelperFactory,
936934
fakeDownloaderFactory,
937935
preCacheLooper,
@@ -965,7 +963,7 @@ public void releaseWithRemovingCachedContent_whileRemoveOngoing_doNotCancelDownl
965963
PreCacheHelper preCacheHelper =
966964
new PreCacheHelper(
967965
mediaItem,
968-
/* mediaSourceFactory= */ null,
966+
/* testMediaSourceFactory= */ null,
969967
downloadHelperFactory,
970968
fakeDownloaderFactory,
971969
preCacheLooper,

0 commit comments

Comments
 (0)