Release internal components on preload thread in DefaultPreloadManager

The `RendererCapabilities` and `TrackSelector` objects are accessed on the preload thread during the preloading, when releasing them, they need to be released on the same thread. Otherwise, it is possible that they have already released on the application thread, while the PreloadMediaSource still tries to access them on the preload thread before the source is released.

#cherrypick

PiperOrigin-RevId: 694173131
(cherry picked from commit 2b54b1ebbeb6f13abb6944b7ff593c168c927192)
This commit is contained in:
tianyifeng 2024-11-07 10:47:02 -08:00 committed by Ian Baker
parent 461a1fa037
commit dba31108a3
2 changed files with 27 additions and 12 deletions

View File

@ -22,6 +22,7 @@ import static java.lang.Math.abs;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.IntDef;
@ -327,6 +328,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
private final TrackSelector trackSelector;
private final PlaybackLooperProvider preloadLooperProvider;
private final PreloadMediaSource.Factory preloadMediaSourceFactory;
private final Handler preloadHandler;
private final boolean deprecatedConstructorCalled;
private DefaultPreloadManager(Builder builder) {
@ -341,6 +343,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
trackSelector = builder.trackSelectorFactory.createTrackSelector(builder.context);
BandwidthMeter bandwidthMeter = builder.bandwidthMeterSupplier.get();
trackSelector.init(() -> {}, bandwidthMeter);
Looper preloadLooper = preloadLooperProvider.obtainLooper();
preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
builder.mediaSourceFactorySupplier.get(),
@ -349,7 +352,8 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
bandwidthMeter,
rendererCapabilitiesList.getRendererCapabilities(),
builder.loadControlSupplier.get().getAllocator(),
preloadLooperProvider.obtainLooper());
preloadLooper);
preloadHandler = Util.createHandler(preloadLooper, /* callback= */ null);
deprecatedConstructorCalled = false;
}
@ -370,6 +374,7 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
rendererCapabilitiesListFactory.createRendererCapabilitiesList();
this.preloadLooperProvider = new PlaybackLooperProvider(preloadLooper);
this.trackSelector = trackSelector;
Looper obtainedPreloadLooper = preloadLooperProvider.obtainLooper();
preloadMediaSourceFactory =
new PreloadMediaSource.Factory(
mediaSourceFactory,
@ -378,7 +383,8 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
bandwidthMeter,
rendererCapabilitiesList.getRendererCapabilities(),
allocator,
preloadLooperProvider.obtainLooper());
obtainedPreloadLooper);
preloadHandler = Util.createHandler(obtainedPreloadLooper, /* callback= */ null);
deprecatedConstructorCalled = true;
}
@ -418,13 +424,16 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Override
protected void releaseInternal() {
rendererCapabilitiesList.release();
preloadLooperProvider.releaseLooper();
if (!deprecatedConstructorCalled) {
// TODO: Remove the property deprecatedConstructorCalled and release the TrackSelector anyway
// after the deprecated constructor is removed.
trackSelector.release();
}
preloadHandler.post(
() -> {
rendererCapabilitiesList.release();
if (!deprecatedConstructorCalled) {
// TODO: Remove the property deprecatedConstructorCalled and release the TrackSelector
// anyway after the deprecated constructor is removed.
trackSelector.release();
}
preloadLooperProvider.releaseLooper();
});
}
private static final class RankingDataComparator implements Comparator<Integer> {

View File

@ -830,7 +830,8 @@ public class DefaultPreloadManagerTest {
}
@Test
public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased() {
public void release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased()
throws Exception {
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> new DefaultPreloadManager.Status(STAGE_SOURCE_PREPARED);
MediaSource.Factory mockMediaSourceFactory = mock(MediaSource.Factory.class);
@ -853,11 +854,13 @@ public class DefaultPreloadManagerTest {
underlyingRenderers.add(fakeAudioRenderer);
return underlyingRenderers.toArray(new Renderer[2]);
};
HandlerThread preloadThread = new HandlerThread("preload");
preloadThread.start();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
.setMediaSourceFactory(mockMediaSourceFactory)
.setRenderersFactory(renderersFactory)
.setPreloadLooper(Util.getCurrentOrMainLooper())
.setPreloadLooper(preloadThread.getLooper())
.build();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem1 =
@ -885,16 +888,19 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem1, /* rankingData= */ 1);
preloadManager.add(mediaItem2, /* rankingData= */ 2);
preloadManager.invalidate();
shadowOf(preloadThread.getLooper()).idle();
shadowOf(Looper.getMainLooper()).idle();
preloadManager.release();
shadowOf(Looper.getMainLooper()).idle();
shadowOf(preloadThread.getLooper()).idle();
assertThat(preloadManager.getSourceCount()).isEqualTo(0);
assertThat(internalSourceToReleaseReferenceByMediaId).containsExactly("mediaId1", "mediaId2");
for (FakeRenderer renderer : underlyingRenderers) {
assertThat(renderer.isReleased).isTrue();
}
preloadThread.quit();
}
private static class TestPreloadManagerListener implements BasePreloadManager.Listener {