diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e814645c9e..d52126b445 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,8 @@ `TargetPreloadStatusControl.getTargetPreloadStatus(T)` to indicate not to preload a `MediaSource` with the given `rankingData`. * Add `remove(MediaSource)` to `BasePreloadManager`. + * Add `reset` to `BasePreloadManager` to release all the holding sources + while keep the preload manager instance. * Transformer: * Add `audioConversionProcess` and `videoConversionProcess` to `ExportResult` indicating how the respective track in the output file diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java index 2e841da271..6a66cadffe 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/BasePreloadManager.java @@ -184,8 +184,10 @@ public abstract class BasePreloadManager { return false; } - /** Releases the preload manager. */ - public final void release() { + /** + * Resets the preload manager. All sources that the preload manager is holding will be released. + */ + public final void reset() { for (MediaSourceHolder sourceHolder : mediaItemMediaSourceHolderMap.values()) { releaseSourceInternal(sourceHolder.mediaSource); } @@ -194,6 +196,15 @@ public abstract class BasePreloadManager { sourceHolderPriorityQueue.clear(); targetPreloadStatusOfCurrentPreloadingSource = null; } + } + + /** + * Releases the preload manager. + * + *

The preload manager must not be used after calling this method. + */ + public final void release() { + reset(); releaseInternal(); } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java index 1657968dca..833ea29ef1 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/preload/DefaultPreloadManagerTest.java @@ -15,7 +15,6 @@ */ package androidx.media3.exoplayer.source.preload; -import static androidx.media3.common.util.Assertions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -49,10 +48,8 @@ import androidx.media3.test.utils.FakeRenderer; import androidx.media3.test.utils.FakeVideoRenderer; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -522,31 +519,101 @@ public class DefaultPreloadManagerTest { } @Test - public void release_returnZeroCountAndNullSources_sourcesReleased() { + public void reset_returnZeroCount_sourcesButNotRendererCapabilitiesListReleased() { TargetPreloadStatusControl targetPreloadStatusControl = rankingData -> new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); - AtomicReference> underlyingRenderersReference = new AtomicReference<>(); + List underlyingRenderers = new ArrayList<>(); RenderersFactory renderersFactory = (eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput) -> { - FakeRenderer[] createdRenderers = - new FakeRenderer[] { - new FakeVideoRenderer( - SystemClock.DEFAULT.createHandler( - eventHandler.getLooper(), /* callback= */ null), - videoRendererEventListener), - new FakeAudioRenderer( - SystemClock.DEFAULT.createHandler( - eventHandler.getLooper(), /* callback= */ null), - audioRendererEventListener) + FakeRenderer fakeVideoRenderer = + new FakeVideoRenderer( + SystemClock.DEFAULT.createHandler(eventHandler.getLooper(), /* callback= */ null), + videoRendererEventListener); + underlyingRenderers.add(fakeVideoRenderer); + FakeRenderer fakeAudioRenderer = + new FakeAudioRenderer( + SystemClock.DEFAULT.createHandler(eventHandler.getLooper(), /* callback= */ null), + audioRendererEventListener); + underlyingRenderers.add(fakeAudioRenderer); + return underlyingRenderers.toArray(new Renderer[2]); + }; + DefaultPreloadManager preloadManager = + new DefaultPreloadManager( + targetPreloadStatusControl, + mockMediaSourceFactory, + trackSelector, + bandwidthMeter, + new DefaultRendererCapabilitiesList.Factory(renderersFactory), + allocator, + Util.getCurrentOrMainLooper()); + MediaItem.Builder mediaItemBuilder = new MediaItem.Builder(); + MediaItem mediaItem1 = + mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); + MediaItem mediaItem2 = + mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); + ArrayList internalSourceToReleaseReferenceByMediaId = new ArrayList<>(); + when(mockMediaSourceFactory.createMediaSource(any())) + .thenAnswer( + invocation -> { + MediaItem mediaItem = invocation.getArgument(0); + return new FakeMediaSource() { + @Override + public MediaItem getMediaItem() { + return mediaItem; + } + + @Override + protected void releaseSourceInternal() { + internalSourceToReleaseReferenceByMediaId.add(mediaItem.mediaId); + super.releaseSourceInternal(); + } }; - underlyingRenderersReference.set(ImmutableList.copyOf(createdRenderers)); - return createdRenderers; + }); + preloadManager.add(mediaItem1, /* rankingData= */ 1); + preloadManager.add(mediaItem2, /* rankingData= */ 2); + preloadManager.invalidate(); + shadowOf(Looper.getMainLooper()).idle(); + + preloadManager.reset(); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(preloadManager.getSourceCount()).isEqualTo(0); + assertThat(internalSourceToReleaseReferenceByMediaId).containsExactly("mediaId1", "mediaId2"); + for (FakeRenderer renderer : underlyingRenderers) { + assertThat(renderer.isReleased).isFalse(); + } + } + + @Test + public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased() { + TargetPreloadStatusControl targetPreloadStatusControl = + rankingData -> + new DefaultPreloadManager.Status(DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); + MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class); + List underlyingRenderers = new ArrayList<>(); + RenderersFactory renderersFactory = + (eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput) -> { + FakeRenderer fakeVideoRenderer = + new FakeVideoRenderer( + SystemClock.DEFAULT.createHandler(eventHandler.getLooper(), /* callback= */ null), + videoRendererEventListener); + underlyingRenderers.add(fakeVideoRenderer); + FakeRenderer fakeAudioRenderer = + new FakeAudioRenderer( + SystemClock.DEFAULT.createHandler(eventHandler.getLooper(), /* callback= */ null), + audioRendererEventListener); + underlyingRenderers.add(fakeAudioRenderer); + return underlyingRenderers.toArray(new Renderer[2]); }; DefaultPreloadManager preloadManager = new DefaultPreloadManager( @@ -589,10 +656,7 @@ public class DefaultPreloadManagerTest { shadowOf(Looper.getMainLooper()).idle(); assertThat(preloadManager.getSourceCount()).isEqualTo(0); - assertThat(preloadManager.getMediaSource(mediaItem1)).isNull(); - assertThat(preloadManager.getMediaSource(mediaItem2)).isNull(); assertThat(internalSourceToReleaseReferenceByMediaId).containsExactly("mediaId1", "mediaId2"); - List underlyingRenderers = checkNotNull(underlyingRenderersReference.get()); for (FakeRenderer renderer : underlyingRenderers) { assertThat(renderer.isReleased).isTrue(); }