Fix issue where ProgressiveMediaPeriod fails assertPrepared

In `PreloadMediaSource` we accesses the `MediaPeriod.getBufferedPositionUs()` when we receive `onContinueLoadingRequested()` from the period. For `ProgressiveMediaPeriod` which requires loading a portion of media file to get it prepared, there is a chance that it needs more than one round of `onContinueLoadingRequested()` -> `continueLoading()` to complete preparation, for example, when the  `setContinueLoadingIntervalBytes()` is small enough to not include the full information for preparation. Thus we should avoid `MediaPeriod.getBufferedPositionUs()` being called before period is prepared, as it will fail at `assertPrepared`.

Issue: androidx/media#2315

#cherrypick

PiperOrigin-RevId: 746490375
This commit is contained in:
tianyifeng 2025-04-11 09:32:59 -07:00 committed by Copybara-Service
parent df8763ae0d
commit 344f249511
3 changed files with 40 additions and 102 deletions

View File

@ -21,6 +21,10 @@
groups have been removed. The user still needs to pass in an array of groups have been removed. The user still needs to pass in an array of
durations for removed ad groups which can be empty or null durations for removed ad groups which can be empty or null
([#2267](https://github.com/androidx/media/issues/2267)). ([#2267](https://github.com/androidx/media/issues/2267)).
* Fix issue where `ProgressiveMediaPeriod` throws an
`IllegalStateException` as `PreloadMediaSource` attempts to call its
`getBufferedDurationUs()` before it is prepared
([#2315](https://github.com/androidx/media/issues/2315)).
* Transformer: * Transformer:
* Filling an initial gap (added via `addGap()`) with silent audio now * Filling an initial gap (added via `addGap()`) with silent audio now
requires explicitly setting `experimentalSetForceAudioTrack(true)` in requires explicitly setting `experimentalSetForceAudioTrack(true)` in

View File

@ -505,17 +505,18 @@ public final class PreloadMediaSource extends WrappingMediaSource {
return; return;
} }
PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod; PreloadMediaPeriod preloadMediaPeriod = (PreloadMediaPeriod) mediaPeriod;
long bufferedPositionUs = mediaPeriod.getBufferedPositionUs(); if (prepared) {
if (prepared && bufferedPositionUs == C.TIME_END_OF_SOURCE) { long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this); if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
stopPreloading(); preloadControl.onLoadedToTheEndOfSource(PreloadMediaSource.this);
return; stopPreloading();
} return;
if (prepared }
&& !preloadControl.onContinueLoadingRequested( if (!preloadControl.onContinueLoadingRequested(
PreloadMediaSource.this, bufferedPositionUs - periodStartPositionUs)) { PreloadMediaSource.this, bufferedPositionUs - periodStartPositionUs)) {
stopPreloading(); stopPreloading();
return; return;
}
} }
preloadMediaPeriod.continueLoading( preloadMediaPeriod.continueLoading(
new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build()); new LoadingInfo.Builder().setPlaybackPositionUs(periodStartPositionUs).build());

View File

@ -90,12 +90,13 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class PreloadMediaSourceTest { public final class PreloadMediaSourceTest {
private static final int LOADING_CHECK_INTERVAL_BYTES = 10 * 1024; private static final int LOADING_CHECK_INTERVAL_BYTES = 32;
private static final int TARGET_PRELOAD_DURATION_US = 10000; private static final int TARGET_PRELOAD_DURATION_US = 10000;
private Allocator allocator; private Allocator allocator;
private BandwidthMeter bandwidthMeter; private BandwidthMeter bandwidthMeter;
private RenderersFactory renderersFactory; private RenderersFactory renderersFactory;
private MediaItem mediaItem;
@Before @Before
public void setUp() { public void setUp() {
@ -112,6 +113,10 @@ public final class PreloadMediaSourceTest {
SystemClock.DEFAULT.createHandler(handler.getLooper(), /* callback= */ null), SystemClock.DEFAULT.createHandler(handler.getLooper(), /* callback= */ null),
audioListener) audioListener)
}; };
mediaItem =
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/long_1080p_lowbitrate.mp4"))
.build();
} }
@Test @Test
@ -146,11 +151,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null); runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
@ -191,11 +192,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null); runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
@ -235,11 +232,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -266,11 +259,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
AtomicReference<MediaSource> externalCallerMediaSourceReference = new AtomicReference<>(); AtomicReference<MediaSource> externalCallerMediaSourceReference = new AtomicReference<>();
MediaSource.MediaSourceCaller externalCaller = MediaSource.MediaSourceCaller externalCaller =
@ -315,11 +304,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadMediaSourceReference.get() != null); runMainLooperUntil(() -> preloadMediaSourceReference.get() != null);
@ -388,11 +373,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadExceptionReference.get() != null); runMainLooperUntil(() -> preloadExceptionReference.get() != null);
@ -472,11 +453,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadExceptionReference.get() != null); runMainLooperUntil(() -> preloadExceptionReference.get() != null);
@ -583,11 +560,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
runMainLooperUntil(() -> preloadExceptionReference.get() != null); runMainLooperUntil(() -> preloadExceptionReference.get() != null);
@ -615,11 +588,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
FakeMediaSource wrappedMediaSource = mediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource = mediaSourceFactory.getLastCreatedSource();
wrappedMediaSource.setAllowPreparation(false); wrappedMediaSource.setAllowPreparation(false);
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
@ -653,11 +622,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -727,11 +692,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -808,11 +769,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -876,11 +833,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -923,11 +876,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean(); AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean();
MediaSource.MediaSourceCaller externalCaller = MediaSource.MediaSourceCaller externalCaller =
(source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true); (source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true);
@ -976,11 +925,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean(); AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean();
MediaSource.MediaSourceCaller externalCaller = MediaSource.MediaSourceCaller externalCaller =
(source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true); (source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true);
@ -1031,11 +976,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
AtomicBoolean externalCaller1SourceInfoRefreshedCalled = new AtomicBoolean(); AtomicBoolean externalCaller1SourceInfoRefreshedCalled = new AtomicBoolean();
AtomicBoolean externalCaller2SourceInfoRefreshedCalled = new AtomicBoolean(); AtomicBoolean externalCaller2SourceInfoRefreshedCalled = new AtomicBoolean();
MediaSource.MediaSourceCaller externalCaller1 = MediaSource.MediaSourceCaller externalCaller1 =
@ -1090,11 +1031,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
preloadMediaSource.preload(/* startPositionUs= */ 0L); preloadMediaSource.preload(/* startPositionUs= */ 0L);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
preloadMediaSource.releasePreloadMediaSource(); preloadMediaSource.releasePreloadMediaSource();
@ -1140,11 +1077,7 @@ public final class PreloadMediaSourceTest {
getRendererCapabilities(renderersFactory), getRendererCapabilities(renderersFactory),
allocator, allocator,
Util.getCurrentOrMainLooper()); Util.getCurrentOrMainLooper());
PreloadMediaSource preloadMediaSource = PreloadMediaSource preloadMediaSource = preloadMediaSourceFactory.createMediaSource(mediaItem);
preloadMediaSourceFactory.createMediaSource(
new MediaItem.Builder()
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.build());
AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean(); AtomicBoolean externalCallerSourceInfoRefreshedCalled = new AtomicBoolean();
MediaSource.MediaSourceCaller externalCaller = MediaSource.MediaSourceCaller externalCaller =
(source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true); (source, timeline) -> externalCallerSourceInfoRefreshedCalled.set(true);