From f7a1b19001d1bbb9ce46c97d2d51e40e63651fd7 Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 24 Sep 2024 11:25:15 -0700 Subject: [PATCH 1/6] Allow custom Executor to be supplied to ChunkSampleStream --- .../source/chunk/ChunkSampleStream.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java index d11fdf9ef5..8f9bcb2876 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java @@ -49,6 +49,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -119,6 +121,7 @@ public class ChunkSampleStream * events. * @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if * the first chunk can't start at the beginning and needs to preroll data. + */ public ChunkSampleStream( @C.TrackType int primaryTrackType, @@ -133,6 +136,46 @@ public class ChunkSampleStream LoadErrorHandlingPolicy loadErrorHandlingPolicy, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, boolean canReportInitialDiscontinuity) { + this(primaryTrackType, embeddedTrackTypes, embeddedTrackFormats, chunkSource, callback, + allocator, positionUs, drmSessionManager, drmEventDispatcher, loadErrorHandlingPolicy, + mediaSourceEventDispatcher, canReportInitialDiscontinuity, null); + } + + /** + * Constructs an instance. + * + * @param primaryTrackType The {@link C.TrackType type} of the primary track. + * @param embeddedTrackTypes The types of any embedded tracks, or null. + * @param embeddedTrackFormats The formats of the embedded tracks, or null. + * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. + * @param callback An {@link Callback} for the stream. + * @param allocator An {@link Allocator} from which allocations can be obtained. + * @param positionUs The position from which to start loading media. + * @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions} + * from. + * @param drmEventDispatcher A dispatcher to notify of {@link DrmSessionEventListener} events. + * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. + * @param mediaSourceEventDispatcher A dispatcher to notify of {@link MediaSourceEventListener} + * events. + * @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if + * the first chunk can't start at the beginning and needs to preroll data. + * @param downloadExecutor An {@link Executor} for supplying the loader's thread. If null, + * a default single thread executor is used. + */ + public ChunkSampleStream( + @C.TrackType int primaryTrackType, + @Nullable int[] embeddedTrackTypes, + @Nullable Format[] embeddedTrackFormats, + T chunkSource, + Callback> callback, + Allocator allocator, + long positionUs, + DrmSessionManager drmSessionManager, + DrmSessionEventListener.EventDispatcher drmEventDispatcher, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, + MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, + boolean canReportInitialDiscontinuity, + @Nullable Executor downloadExecutor) { this.primaryTrackType = primaryTrackType; this.embeddedTrackTypes = embeddedTrackTypes == null ? new int[0] : embeddedTrackTypes; this.embeddedTrackFormats = embeddedTrackFormats == null ? new Format[0] : embeddedTrackFormats; @@ -141,7 +184,8 @@ public class ChunkSampleStream this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.canReportInitialDiscontinuity = canReportInitialDiscontinuity; - loader = new Loader("ChunkSampleStream"); + loader = downloadExecutor != null ? + new Loader(downloadExecutor) : new Loader("ChunkSampleStream"); nextChunkHolder = new ChunkHolder(); mediaChunks = new ArrayList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); From ea837e494ba00d977626d089b31617b7c2822bd7 Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 24 Sep 2024 11:26:43 -0700 Subject: [PATCH 2/6] Remove unused Executors import --- .../media3/exoplayer/source/chunk/ChunkSampleStream.java | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java index 8f9bcb2876..09a1f52515 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java @@ -50,7 +50,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** From 10bb2e15015ad2e5367e7794b32b15e3b0ffe53e Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 24 Sep 2024 16:09:35 -0700 Subject: [PATCH 3/6] Allow Injection of custom Executor in ProgressiveMediaSource --- .../source/ProgressiveMediaPeriod.java | 8 +++- .../source/ProgressiveMediaSource.java | 31 ++++++++++++-- .../source/ProgressiveMediaPeriodTest.java | 42 ++++++++++++++++++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java index 50cd546b1e..ff988d32bd 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java @@ -70,6 +70,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Executor; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -172,6 +173,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each * invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. * @param singleSampleDurationUs The duration of media with a single sample in microseconds. + * @param downloadExecutor An {@link Executor} for supplying the loader's thread. */ // maybeFinishPrepare is not posted to the handler until initialization completes. @SuppressWarnings({"nullness:argument", "nullness:methodref.receiver.bound"}) @@ -187,7 +189,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; Allocator allocator, @Nullable String customCacheKey, int continueLoadingCheckIntervalBytes, - long singleSampleDurationUs) { + long singleSampleDurationUs, + @Nullable Executor downloadExecutor) { this.uri = uri; this.dataSource = dataSource; this.drmSessionManager = drmSessionManager; @@ -198,7 +201,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.allocator = allocator; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - loader = new Loader("ProgressiveMediaPeriod"); + loader = downloadExecutor != null ? + new Loader(downloadExecutor) : new Loader("ProgressiveMediaPeriod"); this.progressiveMediaExtractor = progressiveMediaExtractor; this.singleSampleDurationUs = singleSampleDurationUs; loadCondition = new ConditionVariable(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java index 343fb6222e..06dc7a2455 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java @@ -37,7 +37,10 @@ import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.extractor.DefaultExtractorsFactory; import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorsFactory; +import com.google.common.base.Supplier; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.Optional; +import java.util.concurrent.Executor; /** * Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. @@ -64,6 +67,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource private DrmSessionManagerProvider drmSessionManagerProvider; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private int continueLoadingCheckIntervalBytes; + @Nullable private Supplier downloadExecutor; /** * Creates a new factory for {@link ProgressiveMediaSource}s. @@ -154,6 +158,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource this.drmSessionManagerProvider = drmSessionManagerProvider; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; + this.downloadExecutor = () -> null; } @CanIgnoreReturnValue @@ -197,6 +202,20 @@ public final class ProgressiveMediaSource extends BaseMediaSource return this; } + /** + * Sets a supplier that can return an {@link Executor} that is used for loading the media. This + * is useful if the loading thread needs to be externally managed. + * + * @param downloadExecutor a {@link Supplier} that provides an externally managed + * {@link Executor} for downloading and extraction. + * @return This factory, for convenience. + */ + @CanIgnoreReturnValue + public Factory setDownloadExecutor(Supplier downloadExecutor) { + this.downloadExecutor = downloadExecutor; + return this; + } + /** * Returns a new {@link ProgressiveMediaSource} using the current parameters. * @@ -213,7 +232,8 @@ public final class ProgressiveMediaSource extends BaseMediaSource progressiveMediaExtractorFactory, drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, - continueLoadingCheckIntervalBytes); + continueLoadingCheckIntervalBytes, + downloadExecutor); } @Override @@ -233,12 +253,12 @@ public final class ProgressiveMediaSource extends BaseMediaSource private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; private final int continueLoadingCheckIntervalBytes; + @Nullable private final Supplier downloadExecutor; private boolean timelineIsPlaceholder; private long timelineDurationUs; private boolean timelineIsSeekable; private boolean timelineIsLive; @Nullable private TransferListener transferListener; - @GuardedBy("this") private MediaItem mediaItem; @@ -248,7 +268,8 @@ public final class ProgressiveMediaSource extends BaseMediaSource ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, - int continueLoadingCheckIntervalBytes) { + int continueLoadingCheckIntervalBytes, + @Nullable Supplier downloadExecutor) { this.mediaItem = mediaItem; this.dataSourceFactory = dataSourceFactory; this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; @@ -257,6 +278,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.timelineIsPlaceholder = true; this.timelineDurationUs = C.TIME_UNSET; + this.downloadExecutor = downloadExecutor; } @Override @@ -312,7 +334,8 @@ public final class ProgressiveMediaSource extends BaseMediaSource allocator, localConfiguration.customCacheKey, continueLoadingCheckIntervalBytes, - Util.msToUs(localConfiguration.imageDurationMs)); + Util.msToUs(localConfiguration.imageDurationMs), + downloadExecutor != null ? downloadExecutor.get() : null); } @Override diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java index d243f42c4b..c78eb65893 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java @@ -19,6 +19,7 @@ import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLoop import static com.google.common.truth.Truth.assertThat; import android.net.Uri; +import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.datasource.AssetDataSource; import androidx.media3.exoplayer.LoadingInfo; @@ -34,6 +35,8 @@ import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.png.PngExtractor; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; @@ -67,8 +70,29 @@ public final class ProgressiveMediaPeriodTest { testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(extractor, C.TIME_UNSET); } + @Test + public void supplyingCustomDownloadExecutor_downloadsOnCustomThread() throws TimeoutException { + AtomicBoolean hasThreadRunBefore = new AtomicBoolean(false); + Executor executor = + Executors.newSingleThreadExecutor( + (r) -> new ExecutionTrackingThread(r, hasThreadRunBefore)); + + testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( + new BundledExtractorsAdapter(Mp4Extractor.FACTORY), C.TIME_UNSET, executor); + + assertThat(hasThreadRunBefore.get()).isTrue(); + } + private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( ProgressiveMediaExtractor extractor, long imageDurationUs) throws TimeoutException { + testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( + extractor, imageDurationUs, null); + } + + private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( + ProgressiveMediaExtractor extractor, + long imageDurationUs, + @Nullable Executor executor) throws TimeoutException { AtomicBoolean sourceInfoRefreshCalled = new AtomicBoolean(false); ProgressiveMediaPeriod.Listener sourceInfoRefreshListener = (durationUs, isSeekable, isLive) -> sourceInfoRefreshCalled.set(true); @@ -88,7 +112,8 @@ public final class ProgressiveMediaPeriodTest { new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE), /* customCacheKey= */ null, ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES, - imageDurationUs); + imageDurationUs, + executor); AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false); AtomicBoolean sourceInfoRefreshCalledBeforeOnPrepared = new AtomicBoolean(false); @@ -111,4 +136,19 @@ public final class ProgressiveMediaPeriodTest { assertThat(sourceInfoRefreshCalledBeforeOnPrepared.get()).isTrue(); } + + private class ExecutionTrackingThread extends Thread { + private final AtomicBoolean hasRun; + + public ExecutionTrackingThread(Runnable runnable, AtomicBoolean hasRun) { + super(runnable, "TestExecutionTrackingThread"); + this.hasRun = hasRun; + } + + @Override + public void run() { + hasRun.set(true); + super.run(); + } + } } From 2f6d8bf5ba20ee6c7e77a1abd8139cb87488f6e6 Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 24 Sep 2024 16:10:19 -0700 Subject: [PATCH 4/6] Remove unused import --- .../androidx/media3/exoplayer/source/ProgressiveMediaSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java index 06dc7a2455..c04c158c5f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java @@ -39,7 +39,6 @@ import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorsFactory; import com.google.common.base.Supplier; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import java.util.Optional; import java.util.concurrent.Executor; /** From 15a69068776b5ade09f4da3211f7c3d9709cc2a8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 8 Oct 2024 12:45:35 +0100 Subject: [PATCH 5/6] Formatting and javadoc --- .../source/ProgressiveMediaPeriod.java | 9 ++-- .../source/ProgressiveMediaSource.java | 8 ++-- .../source/chunk/ChunkSampleStream.java | 46 ++----------------- .../source/ProgressiveMediaPeriodTest.java | 9 ++-- .../exoplayer/dash/DashMediaPeriod.java | 3 +- .../smoothstreaming/SsMediaPeriod.java | 3 +- .../test/utils/FakeAdaptiveMediaPeriod.java | 3 +- 7 files changed, 23 insertions(+), 58 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java index ff988d32bd..9bc5c39c73 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java @@ -173,7 +173,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each * invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. * @param singleSampleDurationUs The duration of media with a single sample in microseconds. - * @param downloadExecutor An {@link Executor} for supplying the loader's thread. + * @param downloadExecutor An optional externally provided {@link Executor} for loading and + * extracting media. */ // maybeFinishPrepare is not posted to the handler until initialization completes. @SuppressWarnings({"nullness:argument", "nullness:methodref.receiver.bound"}) @@ -201,8 +202,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.allocator = allocator; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - loader = downloadExecutor != null ? - new Loader(downloadExecutor) : new Loader("ProgressiveMediaPeriod"); + loader = + downloadExecutor != null + ? new Loader(downloadExecutor) + : new Loader("ProgressiveMediaPeriod"); this.progressiveMediaExtractor = progressiveMediaExtractor; this.singleSampleDurationUs = singleSampleDurationUs; loadCondition = new ConditionVariable(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java index c04c158c5f..0710960667 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java @@ -157,7 +157,6 @@ public final class ProgressiveMediaSource extends BaseMediaSource this.drmSessionManagerProvider = drmSessionManagerProvider; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - this.downloadExecutor = () -> null; } @CanIgnoreReturnValue @@ -202,11 +201,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource } /** - * Sets a supplier that can return an {@link Executor} that is used for loading the media. This - * is useful if the loading thread needs to be externally managed. + * Sets a supplier for an {@link Executor} that is used for loading the media. * - * @param downloadExecutor a {@link Supplier} that provides an externally managed - * {@link Executor} for downloading and extraction. + * @param downloadExecutor A {@link Supplier} that provides an externally managed + * {@link Executor} for downloading and extraction. * @return This factory, for convenience. */ @CanIgnoreReturnValue diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java index 09a1f52515..80aed38b96 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java @@ -120,46 +120,8 @@ public class ChunkSampleStream * events. * @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if * the first chunk can't start at the beginning and needs to preroll data. - - */ - public ChunkSampleStream( - @C.TrackType int primaryTrackType, - @Nullable int[] embeddedTrackTypes, - @Nullable Format[] embeddedTrackFormats, - T chunkSource, - Callback> callback, - Allocator allocator, - long positionUs, - DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher, - LoadErrorHandlingPolicy loadErrorHandlingPolicy, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, - boolean canReportInitialDiscontinuity) { - this(primaryTrackType, embeddedTrackTypes, embeddedTrackFormats, chunkSource, callback, - allocator, positionUs, drmSessionManager, drmEventDispatcher, loadErrorHandlingPolicy, - mediaSourceEventDispatcher, canReportInitialDiscontinuity, null); - } - - /** - * Constructs an instance. - * - * @param primaryTrackType The {@link C.TrackType type} of the primary track. - * @param embeddedTrackTypes The types of any embedded tracks, or null. - * @param embeddedTrackFormats The formats of the embedded tracks, or null. - * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. - * @param callback An {@link Callback} for the stream. - * @param allocator An {@link Allocator} from which allocations can be obtained. - * @param positionUs The position from which to start loading media. - * @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions} - * from. - * @param drmEventDispatcher A dispatcher to notify of {@link DrmSessionEventListener} events. - * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. - * @param mediaSourceEventDispatcher A dispatcher to notify of {@link MediaSourceEventListener} - * events. - * @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if - * the first chunk can't start at the beginning and needs to preroll data. - * @param downloadExecutor An {@link Executor} for supplying the loader's thread. If null, - * a default single thread executor is used. + * @param downloadExecutor An optional externally provided {@link Executor} for loading and + * extracting media. */ public ChunkSampleStream( @C.TrackType int primaryTrackType, @@ -183,8 +145,8 @@ public class ChunkSampleStream this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.canReportInitialDiscontinuity = canReportInitialDiscontinuity; - loader = downloadExecutor != null ? - new Loader(downloadExecutor) : new Loader("ChunkSampleStream"); + loader = + downloadExecutor != null ? new Loader(downloadExecutor) : new Loader("ChunkSampleStream"); nextChunkHolder = new ChunkHolder(); mediaChunks = new ArrayList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java index c78eb65893..4800de048e 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java @@ -86,13 +86,12 @@ public final class ProgressiveMediaPeriodTest { private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( ProgressiveMediaExtractor extractor, long imageDurationUs) throws TimeoutException { testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( - extractor, imageDurationUs, null); + extractor, imageDurationUs, /* executor= */ null); } private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( - ProgressiveMediaExtractor extractor, - long imageDurationUs, - @Nullable Executor executor) throws TimeoutException { + ProgressiveMediaExtractor extractor, long imageDurationUs, @Nullable Executor executor) + throws TimeoutException { AtomicBoolean sourceInfoRefreshCalled = new AtomicBoolean(false); ProgressiveMediaPeriod.Listener sourceInfoRefreshListener = (durationUs, isSeekable, isLive) -> sourceInfoRefreshCalled.set(true); @@ -137,7 +136,7 @@ public final class ProgressiveMediaPeriodTest { assertThat(sourceInfoRefreshCalledBeforeOnPrepared.get()).isTrue(); } - private class ExecutionTrackingThread extends Thread { + private static final class ExecutionTrackingThread extends Thread { private final AtomicBoolean hasRun; public ExecutionTrackingThread(Runnable runnable, AtomicBoolean hasRun) { diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java index 37676922c0..11640b171f 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java @@ -837,7 +837,8 @@ import java.util.regex.Pattern; drmEventDispatcher, loadErrorHandlingPolicy, mediaSourceEventDispatcher, - canReportInitialDiscontinuity); + canReportInitialDiscontinuity, + /* downloadExecutor= */ null); synchronized (this) { // The map is also accessed on the loading thread so synchronize access. trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler); diff --git a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java index ea9c8ba161..6037ed7b8b 100644 --- a/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java +++ b/libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/SsMediaPeriod.java @@ -265,7 +265,8 @@ import java.util.List; drmEventDispatcher, loadErrorHandlingPolicy, mediaSourceEventDispatcher, - /* canReportInitialDiscontinuity= */ false); + /* canReportInitialDiscontinuity= */ false, + /* downloadExecutor= */ null); } private static TrackGroupArray buildTrackGroups( diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java index 5ef6781067..87ad35b0ce 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeAdaptiveMediaPeriod.java @@ -188,7 +188,8 @@ public class FakeAdaptiveMediaPeriod new DrmSessionEventListener.EventDispatcher(), new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3), mediaSourceEventDispatcher, - /* canReportInitialDiscontinuity= */ false); + /* canReportInitialDiscontinuity= */ false, + /* downloadExecutor= */ null); streams[i] = sampleStream; sampleStreams.add(sampleStream); streamResetFlags[i] = true; From b27cbe60b9d110422048c5173163fa4dbdcbf23d Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 9 Oct 2024 13:48:18 +0100 Subject: [PATCH 6/6] Add release callback and generic class to handle type casting --- .../source/ProgressiveMediaPeriod.java | 8 +-- .../source/ProgressiveMediaSource.java | 28 +++++---- .../source/chunk/ChunkSampleStream.java | 8 +-- .../media3/exoplayer/upstream/Loader.java | 22 +++---- .../exoplayer/util/ReleasableExecutor.java | 58 +++++++++++++++++++ .../source/ProgressiveMediaPeriodTest.java | 26 ++++++--- 6 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 libraries/exoplayer/src/main/java/androidx/media3/exoplayer/util/ReleasableExecutor.java diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java index 9bc5c39c73..ef5421c443 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriod.java @@ -54,6 +54,7 @@ import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo; import androidx.media3.exoplayer.upstream.Loader; import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction; import androidx.media3.exoplayer.upstream.Loader.Loadable; +import androidx.media3.exoplayer.util.ReleasableExecutor; import androidx.media3.extractor.DiscardingTrackOutput; import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorOutput; @@ -70,7 +71,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Executor; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -173,8 +173,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each * invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. * @param singleSampleDurationUs The duration of media with a single sample in microseconds. - * @param downloadExecutor An optional externally provided {@link Executor} for loading and - * extracting media. + * @param downloadExecutor An optional externally provided {@link ReleasableExecutor} for loading + * and extracting media. */ // maybeFinishPrepare is not posted to the handler until initialization completes. @SuppressWarnings({"nullness:argument", "nullness:methodref.receiver.bound"}) @@ -191,7 +191,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Nullable String customCacheKey, int continueLoadingCheckIntervalBytes, long singleSampleDurationUs, - @Nullable Executor downloadExecutor) { + @Nullable ReleasableExecutor downloadExecutor) { this.uri = uri; this.dataSource = dataSource; this.drmSessionManager = drmSessionManager; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java index 0710960667..fbcf0e4a03 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ProgressiveMediaSource.java @@ -24,6 +24,7 @@ import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.Timeline; +import androidx.media3.common.util.Consumer; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; @@ -34,6 +35,7 @@ import androidx.media3.exoplayer.drm.DrmSessionManagerProvider; import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; +import androidx.media3.exoplayer.util.ReleasableExecutor; import androidx.media3.extractor.DefaultExtractorsFactory; import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorsFactory; @@ -66,7 +68,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource private DrmSessionManagerProvider drmSessionManagerProvider; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private int continueLoadingCheckIntervalBytes; - @Nullable private Supplier downloadExecutor; + @Nullable private Supplier downloadExecutorSupplier; /** * Creates a new factory for {@link ProgressiveMediaSource}s. @@ -203,13 +205,17 @@ public final class ProgressiveMediaSource extends BaseMediaSource /** * Sets a supplier for an {@link Executor} that is used for loading the media. * - * @param downloadExecutor A {@link Supplier} that provides an externally managed - * {@link Executor} for downloading and extraction. + * @param downloadExecutor A {@link Supplier} that provides an externally managed {@link + * Executor} for downloading and extraction. + * @param downloadExecutorReleaser A callback triggered once a load task is finished and a + * supplied executor is no longer required. * @return This factory, for convenience. */ @CanIgnoreReturnValue - public Factory setDownloadExecutor(Supplier downloadExecutor) { - this.downloadExecutor = downloadExecutor; + public Factory setDownloadExecutor( + Supplier downloadExecutor, Consumer downloadExecutorReleaser) { + this.downloadExecutorSupplier = + () -> ReleasableExecutor.from(downloadExecutor.get(), downloadExecutorReleaser); return this; } @@ -230,7 +236,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, continueLoadingCheckIntervalBytes, - downloadExecutor); + downloadExecutorSupplier); } @Override @@ -250,7 +256,9 @@ public final class ProgressiveMediaSource extends BaseMediaSource private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; private final int continueLoadingCheckIntervalBytes; - @Nullable private final Supplier downloadExecutor; + + @Nullable private final Supplier downloadExecutorSupplier; + private boolean timelineIsPlaceholder; private long timelineDurationUs; private boolean timelineIsSeekable; @@ -266,7 +274,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, int continueLoadingCheckIntervalBytes, - @Nullable Supplier downloadExecutor) { + @Nullable Supplier downloadExecutorSupplier) { this.mediaItem = mediaItem; this.dataSourceFactory = dataSourceFactory; this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; @@ -275,7 +283,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.timelineIsPlaceholder = true; this.timelineDurationUs = C.TIME_UNSET; - this.downloadExecutor = downloadExecutor; + this.downloadExecutorSupplier = downloadExecutorSupplier; } @Override @@ -332,7 +340,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource localConfiguration.customCacheKey, continueLoadingCheckIntervalBytes, Util.msToUs(localConfiguration.imageDurationMs), - downloadExecutor != null ? downloadExecutor.get() : null); + downloadExecutorSupplier != null ? downloadExecutorSupplier.get() : null); } @Override diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java index 80aed38b96..e2ee724462 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java @@ -45,11 +45,11 @@ import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy.LoadErrorInfo; import androidx.media3.exoplayer.upstream.Loader; import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction; +import androidx.media3.exoplayer.util.ReleasableExecutor; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.Executor; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -120,8 +120,8 @@ public class ChunkSampleStream * events. * @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if * the first chunk can't start at the beginning and needs to preroll data. - * @param downloadExecutor An optional externally provided {@link Executor} for loading and - * extracting media. + * @param downloadExecutor An optional externally provided {@link ReleasableExecutor} for loading + * and extracting media. */ public ChunkSampleStream( @C.TrackType int primaryTrackType, @@ -136,7 +136,7 @@ public class ChunkSampleStream LoadErrorHandlingPolicy loadErrorHandlingPolicy, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, boolean canReportInitialDiscontinuity, - @Nullable Executor downloadExecutor) { + @Nullable ReleasableExecutor downloadExecutor) { this.primaryTrackType = primaryTrackType; this.embeddedTrackTypes = embeddedTrackTypes == null ? new int[0] : embeddedTrackTypes; this.embeddedTrackFormats = embeddedTrackFormats == null ? new Format[0] : embeddedTrackFormats; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/Loader.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/Loader.java index 96610b2ad0..308619abea 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/Loader.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/upstream/Loader.java @@ -31,12 +31,12 @@ import androidx.media3.common.util.Log; import androidx.media3.common.util.TraceUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.util.ReleasableExecutor; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; @@ -218,8 +218,7 @@ public final class Loader implements LoaderErrorThrower { } } - private final Executor downloadExecutor; - private final Runnable downloadExecutorReleaser; + private final ReleasableExecutor downloadExecutor; @Nullable private LoadTask currentTask; @Nullable private IOException fatalError; @@ -231,20 +230,21 @@ public final class Loader implements LoaderErrorThrower { * component using the loader. */ public Loader(String threadNameSuffix) { - ExecutorService executorService = - Util.newSingleThreadExecutor(THREAD_NAME_PREFIX + threadNameSuffix); - this.downloadExecutor = executorService; - this.downloadExecutorReleaser = executorService::shutdown; + this( + /* downloadExecutor= */ ReleasableExecutor.from( + Util.newSingleThreadExecutor(THREAD_NAME_PREFIX + threadNameSuffix), + ExecutorService::shutdown)); } /** * Constructs an instance. * - * @param downloadExecutor An {@link Executor} for supplying the loader's thread. + * @param downloadExecutor A {@link ReleasableExecutor} to run the load task. The {@link + * ReleasableExecutor} will be {@linkplain ReleasableExecutor#release() released} once the + * loader no longer requires it for new load tasks. */ - public Loader(Executor downloadExecutor) { + public Loader(ReleasableExecutor downloadExecutor) { this.downloadExecutor = downloadExecutor; - this.downloadExecutorReleaser = () -> {}; } /** @@ -328,7 +328,7 @@ public final class Loader implements LoaderErrorThrower { if (callback != null) { downloadExecutor.execute(new ReleaseTask(callback)); } - downloadExecutorReleaser.run(); + downloadExecutor.release(); } // LoaderErrorThrower implementation. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/util/ReleasableExecutor.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/util/ReleasableExecutor.java new file mode 100644 index 0000000000..2aa980d8fa --- /dev/null +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/util/ReleasableExecutor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.exoplayer.util; + +import androidx.media3.common.util.Consumer; +import androidx.media3.common.util.UnstableApi; +import java.util.concurrent.Executor; + +/** + * An {@link Executor} with a dedicated {@link #release} method to signal when it is not longer + * needed. + */ +@UnstableApi +public interface ReleasableExecutor extends Executor { + + /** + * Releases the {@link Executor}, indicating that the caller no longer requires it for executing + * new commands. + * + *

When calling this method, there may still be pending commands that are currently executed. + */ + void release(); + + /** + * Creates a {@link ReleasableExecutor} from an {@link Executor} and a release callback. + * + * @param executor The {@link Executor} + * @param releaseCallback The release callback, accepting the {@code executor} as an argument. + * @return The releasable executor. + * @param The type of {@link Executor}. + */ + static ReleasableExecutor from(T executor, Consumer releaseCallback) { + return new ReleasableExecutor() { + @Override + public void execute(Runnable command) { + executor.execute(command); + } + + @Override + public void release() { + releaseCallback.accept(executor); + } + }; + } +} diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java index 4800de048e..b66afe4a11 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ProgressiveMediaPeriodTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.util.Consumer; import androidx.media3.datasource.AssetDataSource; import androidx.media3.exoplayer.LoadingInfo; import androidx.media3.exoplayer.analytics.PlayerId; @@ -29,10 +30,12 @@ import androidx.media3.exoplayer.drm.DrmSessionManager; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.upstream.DefaultAllocator; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; +import androidx.media3.exoplayer.util.ReleasableExecutor; import androidx.media3.extractor.Extractor; import androidx.media3.extractor.ExtractorsFactory; import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.png.PngExtractor; +import androidx.media3.extractor.text.SubtitleParser; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.concurrent.Executor; @@ -72,25 +75,32 @@ public final class ProgressiveMediaPeriodTest { @Test public void supplyingCustomDownloadExecutor_downloadsOnCustomThread() throws TimeoutException { - AtomicBoolean hasThreadRunBefore = new AtomicBoolean(false); + AtomicBoolean hasThreadRun = new AtomicBoolean(false); + AtomicBoolean hasReleaseCallbackRun = new AtomicBoolean(false); Executor executor = - Executors.newSingleThreadExecutor( - (r) -> new ExecutionTrackingThread(r, hasThreadRunBefore)); + Executors.newSingleThreadExecutor(r -> new ExecutionTrackingThread(r, hasThreadRun)); testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( - new BundledExtractorsAdapter(Mp4Extractor.FACTORY), C.TIME_UNSET, executor); + new BundledExtractorsAdapter(Mp4Extractor.newFactory(SubtitleParser.Factory.UNSUPPORTED)), + C.TIME_UNSET, + executor, + e -> hasReleaseCallbackRun.set(true)); - assertThat(hasThreadRunBefore.get()).isTrue(); + assertThat(hasThreadRun.get()).isTrue(); + assertThat(hasReleaseCallbackRun.get()).isTrue(); } private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( ProgressiveMediaExtractor extractor, long imageDurationUs) throws TimeoutException { testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( - extractor, imageDurationUs, /* executor= */ null); + extractor, imageDurationUs, /* executor= */ null, /* executorReleased= */ null); } private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback( - ProgressiveMediaExtractor extractor, long imageDurationUs, @Nullable Executor executor) + ProgressiveMediaExtractor extractor, + long imageDurationUs, + @Nullable Executor executor, + @Nullable Consumer executorReleased) throws TimeoutException { AtomicBoolean sourceInfoRefreshCalled = new AtomicBoolean(false); ProgressiveMediaPeriod.Listener sourceInfoRefreshListener = @@ -112,7 +122,7 @@ public final class ProgressiveMediaPeriodTest { /* customCacheKey= */ null, ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES, imageDurationUs, - executor); + executor != null ? ReleasableExecutor.from(executor, executorReleased) : null); AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false); AtomicBoolean sourceInfoRefreshCalledBeforeOnPrepared = new AtomicBoolean(false);