diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1b4899aa5e..69a539b66a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ * Use `SingleThreadExecutor` for releasing `AudioTrack` instances to avoid OutOfMemory errors when releasing multiple players at the same time ([#10057](https://github.com/google/ExoPlayer/issues/10057)). + * Limit parallel download removals to 1 to avoid excessive thread creation + ([#10458](https://github.com/google/ExoPlayer/issues/10458)). +* Metadata: * `MetadataRenderer` can now be configured to render metadata as soon as they are available. Create an instance with `MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory, diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadManager.java index 2679e634f2..c5f03d874b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadManager.java @@ -709,6 +709,7 @@ public final class DownloadManager { private int maxParallelDownloads; private int minRetryCount; private int activeDownloadTaskCount; + private boolean hasActiveRemoveTask; public InternalHandler( HandlerThread thread, @@ -1060,6 +1061,10 @@ public final class DownloadManager { return; } + if (hasActiveRemoveTask) { + return; + } + // We can start a remove task. Downloader downloader = downloaderFactory.createDownloader(download.request); activeTask = @@ -1071,6 +1076,7 @@ public final class DownloadManager { minRetryCount, /* internalHandler= */ this); activeTasks.put(download.request.id, activeTask); + hasActiveRemoveTask = true; activeTask.start(); } @@ -1100,7 +1106,9 @@ public final class DownloadManager { activeTasks.remove(downloadId); boolean isRemove = task.isRemove; - if (!isRemove && --activeDownloadTaskCount == 0) { + if (isRemove) { + hasActiveRemoveTask = false; + } else if (--activeDownloadTaskCount == 0) { removeMessages(MSG_UPDATE_PROGRESS); } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadManagerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadManagerTest.java index 9cce8afbed..858d98f2eb 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadManagerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadManagerTest.java @@ -284,14 +284,14 @@ public class DownloadManagerTest { postRemoveAllRequest(); // Both downloads should be removed. FakeDownloader downloader2 = getDownloaderAt(2); - FakeDownloader downloader3 = getDownloaderAt(3); downloader2.assertId(ID1); - downloader3.assertId(ID2); downloader2.assertRemoveStarted(); - downloader3.assertRemoveStarted(); downloader2.finish(); - downloader3.finish(); assertRemoved(ID1); + FakeDownloader downloader3 = getDownloaderAt(3); + downloader3.assertId(ID2); + downloader3.assertRemoveStarted(); + downloader3.finish(); assertRemoved(ID2); downloadManagerListener.blockUntilIdleAndThrowAnyFailure(); @@ -713,6 +713,36 @@ public class DownloadManagerTest { assertEqualIgnoringUpdateTime(mergedDownload, expectedDownload); } + @Test + public void removeRequests_runSequentially() throws Throwable { + // Trigger two remove requests. + postDownloadRequest(ID1); + getDownloaderAt(0).finish(); + postDownloadRequest(ID2); + getDownloaderAt(1).finish(); + postRemoveRequest(ID1); + postRemoveRequest(ID2); + + // Assert first remove request is executing, second one is queued. + assertRemoving(ID1); + assertQueued(ID2); + FakeDownloader downloader2 = getDownloaderAt(2); + downloader2.assertId(ID1); + downloader2.assertRemoveStarted(); + downloader2.finish(); + assertRemoved(ID1); + + // Assert second one is running after first one finished + assertRemoving(ID2); + FakeDownloader downloader3 = getDownloaderAt(3); + downloader3.assertId(ID2); + downloader3.assertRemoveStarted(); + downloader3.finish(); + assertRemoved(ID2); + + downloadManagerListener.blockUntilIdleAndThrowAnyFailure(); + } + private void setupDownloadManager(int maxParallelDownloads) throws Exception { if (downloadManager != null) { releaseDownloadManager();