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

PiperOrigin-RevId: 746490375
(cherry picked from commit 344f249511ad67a42d8328e79102f02bbbec5201)
This commit is contained in:
tianyifeng 2025-04-11 09:32:59 -07:00 committed by tonihei
parent 24a8185f4d
commit d7234a6a4e
3 changed files with 41 additions and 224 deletions

View File

@ -1,131 +1,14 @@
# Release notes
* ExoPlayer:
* 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)).
* Cast extension:
* Add support for playlist metadata
([#2235](https://github.com/androidx/media/pull/2235)).
### Unreleased changes
* Common Library:
* Add `PlaybackParameters.withPitch(float)` method for easily copying a
`PlaybackParameters` with a new `pitch` value
([#2257](https://github.com/androidx/media/issues/2257)).
* ExoPlayer:
* Fix sending `CmcdData` in manifest requests for DASH, HLS, and
SmoothStreaming ([#2253](https://github.com/androidx/media/pull/2253)).
<<<<<<< HEAD
=======
* Fix issue where media item transition fails due to recoverable renderer
error during initialization of the next media item
([#2229](https://github.com/androidx/media/issues/2229)).
* Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes
the player for many frequent seeks (for example, from a user dragging a
scrubber bar around).
* `AdPlaybackState.withAdDurationsUs(long[][])` can be used after ad
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
([#2267](https://github.com/androidx/media/issues/2267)).
>>>>>>> d133300627 (Make AdPlaybackState.withAdDurationsUs work with removed ad groups)
* Transformer:
* Track Selection:
* Extractors:
* MP3: Use duration and data size from unseekable Xing, VBRI and similar
variable bitrate metadata when falling back to constant bitrate seeking
due to `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING(_ALWAYS)`
([#2194](https://github.com/androidx/media/issues/2194)).
* MP4: Parse `alternate_group` from the `tkhd` box and expose it as an
`Mp4AlternateGroupData` entry in each track's `Format.metadata`
([#2242](https://github.com/androidx/media/issues/2242)).
* DataSource:
* Audio:
* Allow constant power upmixing/downmixing in DefaultAudioMixer.
* Fix offload issue where position might get stuck when playing a playlist
of short content
([#1920](https://github.com/androidx/media/issues/1920)).
* Video:
* Add experimental `ExoPlayer` API to include the
`MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input
buffers. This flag will signal the decoder to skip the decode-only
buffers thereby resulting in faster seeking. Enable it with
`DefaultRenderersFactory.experimentalSetEnableMediaCodecBufferDecodeOnlyFlag`.
* Text:
* Metadata:
* Image:
* DataSource:
* DRM:
* Effect:
* Add `Presentation.createForShortSide(int)` that creates a `Presentation`
that ensures the shortest side always matches the given value,
regardless of input orientation.
* Muxers:
* `writeSampleData()` API now uses muxer specific `BufferInfo` class
instead of `MediaCodec.BufferInfo`.
* IMA extension:
* Session:
* Lower aggregation timeout for platform `MediaSession` callbacks from 500
to 100 milliseconds and add an experimental setter to allow apps to
configure this value.
* Fix issue where notifications reappear after they have been dismissed by
the user ([#2302](https://github.com/androidx/media/issues/2302)).
* Fix a bug where the `PlayerWrapper` returned a single-item timeline when
the wrapped player is actually empty. This happened when the wrapped
player doesn't have `COMMAND_GET_TIMELINE` available while
`COMMAND_GET_CURRENT_MEDIA_ITEM` is available and the wrapped player is
empty ([#2320](https://github.com/androidx/media/issues/2320)).
* Fix a bug where calling
`MediaSessionService.setMediaNotificationProvider` is silently ignored
after other interactions with the service like
`setForegroundServiceTimeoutMs`
([#2305](https://github.com/androidx/media/issues/2305)).
* UI:
* Enable `PlayerSurface` to work with `ExoPlayer.setVideoEffects` and
`CompositionPlayer`.
* Fix bug where `PlayerSurface` can't be recomposed with a new `Player`.
* Downloads:
* Add partial download support for progressive streams. Apps can prepare a
progressive stream with `DownloadHelper`, and request a
`DownloadRequest` from the helper with specifying the time-based media
start and end positions that the download should cover. The returned
`DownloadRequest` carries the resolved byte range, with which a
`ProgressiveDownloader` can be created and download the content
correspondingly.
* OkHttp extension:
* Cronet extension:
* RTMP extension:
* HLS extension:
* Fix issue where chunk duration wasn't set in `CmcdData` for HLS media,
causing an assertion failure when processing encrypted media segments
([#2312](https://github.com/androidx/media/issues/2312)).
* DASH extension:
* Smooth Streaming extension:
* RTSP extension:
* Add support for URI with RTSPT scheme as a way to configure the RTSP
session to use TCP
([#1484](https://github.com/androidx/media/issues/1484)).
* Decoder extensions (FFmpeg, VP9, AV1, etc.):
* MIDI extension:
* Leanback extension:
* Cast extension:
* Test Utilities:
* Demo app:
* Add `PlaybackSpeedPopUpButton` Composable UI element to be part of
`ExtraControls` in `demo-compose`.
* Remove deprecated symbols:
* Removed deprecated `SegmentDownloader` constructor
`SegmentDownloader(MediaItem, Parser<M>, CacheDataSource.Factory,
Executor)` and the corresponding constructors in its subclasses
`DashDownloader`, `HlsDownloader` and `SsDownloader`.
* Removed deprecated `Player.hasNext()`, `Player.hasNextWindow()`. Use
`Player.hasNextMediaItem()` instead.
* Removed deprecated `Player.next()`. Use `Player.seekToNextMediaItem()`
instead.
* Removed deprecated `Player.seekToPreviousWindow()`. Use
`Player.seekToPreviousMediaItem()` instead.
* Removed deprecated `Player.seekToNextWindow()`. Use
`Player.seekToNextMediaItem()` instead.
* Removed deprecated `BaseAudioProcessor` in `exoplayer` module. Use
`BaseAudioProcessor` under `common` module.
## 1.6
### 1.6.0 (2025-03-26)

View File

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

View File

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