diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java index 916bb1421e..2e8a941f9d 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java @@ -50,10 +50,11 @@ import static androidx.media3.exoplayer.analytics.AnalyticsListener.EVENT_VIDEO_ import static androidx.media3.exoplayer.analytics.AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; +import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.play; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition; +import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilError; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilIsLoading; -import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilTimelineChanged; import static com.google.common.truth.Truth.assertThat; @@ -477,11 +478,10 @@ public final class DefaultAnalyticsCollectorTest { player.setMediaSources(ImmutableList.of(mediaSource1, mediaSource2)); player.prepare(); // Wait until second period has fully loaded to assert loading events. - runUntilIsLoading(player, /* expectedIsLoading= */ true); - runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0); player.play(); - runUntilPlaybackState(player, Player.STATE_ENDED); + run(player).untilState(Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) @@ -975,16 +975,15 @@ public final class DefaultAnalyticsCollectorTest { player.setMediaSource(fakeMediaSource); player.prepare(); - runUntilPlaybackState(player, Player.STATE_READY); + run(player).untilState(Player.STATE_READY); player.addMediaSource(fakeMediaSource); // Wait until second period has fully loaded to assert loading events. - runUntilIsLoading(player, /* expectedIsLoading= */ true); - runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); player.removeMediaItem(/* index= */ 0); - runUntilPlaybackState(player, Player.STATE_BUFFERING); - runUntilPlaybackState(player, Player.STATE_READY); + run(player).untilState(Player.STATE_BUFFERING); + run(player).untilState(Player.STATE_READY); player.play(); - runUntilPlaybackState(player, Player.STATE_ENDED); + run(player).untilState(Player.STATE_ENDED); // Populate event ids with second to last timeline that still contained both periods. populateEventIds(listener.reportedTimelines.get(listener.reportedTimelines.size() - 2)); @@ -1140,18 +1139,17 @@ public final class DefaultAnalyticsCollectorTest { player.setMediaSource(fakeMediaSource); player.prepare(); // Ensure everything is preloaded. - runUntilIsLoading(player, /* expectedIsLoading= */ true); - runUntilIsLoading(player, /* expectedIsLoading= */ false); - runUntilPlaybackState(player, Player.STATE_READY); + run(player).untilFullyBuffered(); + run(player).untilState(Player.STATE_READY); // Wait in each content part to ensure previously triggered events get a chance to be delivered. - playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 3_000); - runUntilPendingCommandsAreFullyHandled(player); - playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 8_000); - runUntilPendingCommandsAreFullyHandled(player); + play(player).untilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 3_000); + run(player).untilPendingCommandsAreFullyHandled(); + play(player).untilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 8_000); + run(player).untilPendingCommandsAreFullyHandled(); player.play(); - runUntilPlaybackState(player, Player.STATE_ENDED); + run(player).untilState(Player.STATE_ENDED); // Wait for final timeline change that marks post-roll played. - runUntilTimelineChanged(player); + run(player).untilTimelineChanges(); Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); EventWindowAndPeriodId prerollAd = @@ -1343,14 +1341,13 @@ public final class DefaultAnalyticsCollectorTest { player.setMediaSource(fakeMediaSource); player.prepare(); // Ensure everything is preloaded. - runUntilIsLoading(player, /* expectedIsLoading= */ true); - runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); // Seek behind the midroll. player.seekTo(/* positionMs= */ 6_000); // Wait until loading started again to assert loading events. - runUntilIsLoading(player, /* expectedIsLoading= */ true); + run(player).untilLoadingIs(true); player.play(); - runUntilPlaybackState(player, Player.STATE_ENDED); + run(player).untilState(Player.STATE_ENDED); Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); EventWindowAndPeriodId midrollAd = @@ -1516,11 +1513,9 @@ public final class DefaultAnalyticsCollectorTest { // Wait for the media to be fully buffered before unblocking the DRM key request. This // ensures both periods report the same load event (because period1's DRM session is // already preacquired by the time the key load completes). - runUntilIsLoading(player, /* expectedIsLoading= */ false); - runUntilIsLoading(player, /* expectedIsLoading= */ true); - runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); mediaDrmCallback.keyCondition.open(); - runUntilPlaybackState(player, Player.STATE_ENDED); + run(player).untilState(Player.STATE_ENDED); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty(); @@ -1590,9 +1585,9 @@ public final class DefaultAnalyticsCollectorTest { player.play(); player.setMediaSource(mediaSource); player.prepare(); - runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); mediaDrmCallback.keyCondition.open(); - runUntilError(player); + run(player).untilPlayerError(); populateEventIds(listener.lastReportedTimeline); assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/MergingPlaylistPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/MergingPlaylistPlaybackTest.java index 84e874d1ec..454417165d 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/MergingPlaylistPlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/MergingPlaylistPlaybackTest.java @@ -108,13 +108,7 @@ public final class MergingPlaylistPlaybackTest { player.prepare(); // Load all content prior to play to reduce flaky-ness resulting from the playback advancement // speed and handling of discontinuities. - long durationToBufferMs = - (firstItemVideoClipped || firstItemAudioClipped ? 300L : 1024L) - + (secondItemVideoClipped || secondItemAudioClipped ? 300L : 1024L); - run(player) - .untilBackgroundThreadCondition( - () -> player.getTotalBufferedDuration() >= durationToBufferMs); - run(player).untilPendingCommandsAreFullyHandled(); + run(player).untilFullyBuffered(); // Reset the listener to avoid verifying the onIsLoadingChanged events from prepare(). reset(listener); player.play(); @@ -160,12 +154,8 @@ public final class MergingPlaylistPlaybackTest { player.prepare(); // Load all content prior to play to reduce flaky-ness resulting from the playback advancement // speed and handling of discontinuities. - long durationToBufferMs = (firstItemVideoClipped || firstItemAudioClipped ? 300L : 1024L) * 5; - run(player) - .untilBackgroundThreadCondition( - () -> player.getTotalBufferedDuration() >= durationToBufferMs); + run(player).untilFullyBuffered(); // Reset the listener to avoid verifying the onIsLoadingChanged events from prepare(). - run(player).untilPendingCommandsAreFullyHandled(); reset(listener); player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java index c594ca91f5..7fd5f4cd5a 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.e2etest; +import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.annotation.GraphicsMode.Mode.NATIVE; @@ -86,11 +87,9 @@ public final class PlaylistPlaybackTest { player.addMediaItem(MediaItem.fromUri("asset:///media/mka/bear-opus.mka")); player.prepare(); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); player.addMediaItem(MediaItem.fromUri("asset:///media/wav/sample.wav")); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); // Wait until second period has fully loaded to start the playback. player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); @@ -115,8 +114,7 @@ public final class PlaylistPlaybackTest { player.addMediaItem(MediaItem.fromUri("asset:///media/mp4/preroll-5s.mp4")); player.prepare(); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); MediaItem mediaItemWithSubtitle = new MediaItem.Builder() .setUri("asset:///media/mp4/preroll-5s.mp4") @@ -130,8 +128,7 @@ public final class PlaylistPlaybackTest { .build())) .build(); player.addMediaItem(mediaItemWithSubtitle); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true); - TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false); + run(player).untilFullyBuffered(); // Wait until second period has fully loaded to start the playback. player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/SubtitlePlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/SubtitlePlaybackTest.java index cff92a3fcb..e2fec4d0f9 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/SubtitlePlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/SubtitlePlaybackTest.java @@ -98,7 +98,7 @@ public class SubtitlePlaybackTest { player.setMediaItem(mediaItem); player.prepare(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -160,7 +160,7 @@ public class SubtitlePlaybackTest { player.setMediaItem(mediaItem); player.prepare(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java index 3579519754..403c6cd04a 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java @@ -96,7 +96,7 @@ public class WebvttPlaybackTest { player.setMediaItem(mediaItem); player.prepare(); run(player).untilState(Player.STATE_READY); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -139,7 +139,7 @@ public class WebvttPlaybackTest { player.setMediaItem(mediaItem); player.prepare(); run(player).untilState(Player.STATE_READY); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java index a3501c03fd..6d0d449ab9 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java @@ -22,7 +22,7 @@ import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.E import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition; -import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilIsLoading; +import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static com.google.common.truth.Truth.assertThat; @@ -521,8 +521,7 @@ public final class ServerSideAdInsertionMediaSourceTest { // Add ad at the current playback position during playback. runUntilPlaybackState(player, Player.STATE_READY); - runUntilIsLoading(player, false); - runMainLooperUntil(() -> player.getBufferedPercentage() == 100); + run(player).untilFullyBuffered(); AdPlaybackState secondAdPlaybackState = addAdGroupToAdPlaybackState( firstAdPlaybackState, diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java index 6f71632ea1..8d21de4e47 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java @@ -95,8 +95,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -136,10 +135,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -178,10 +174,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -218,8 +211,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-ttml/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -250,8 +242,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/webvtt-in-mp4/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -281,8 +272,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -322,10 +312,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -364,10 +351,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -411,8 +395,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -452,8 +435,7 @@ public final class DashPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -690,8 +672,7 @@ public final class DashPlaybackTest { MediaItem.fromUri("asset:///media/dash/multi-period-with-offset/sample.mpd")); player.prepare(); // Ensure media is fully buffered to avoid flakiness from loading second period too late. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/e2etest/HlsPlaybackTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/e2etest/HlsPlaybackTest.java index 2ff17f3e05..6e802c6ebc 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/e2etest/HlsPlaybackTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/e2etest/HlsPlaybackTest.java @@ -85,8 +85,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -124,10 +123,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -164,10 +160,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -198,8 +191,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -236,10 +228,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -275,10 +264,7 @@ public final class HlsPlaybackTest { MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player) - .ignoringNonFatalErrors() - .untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).ignoringNonFatalErrors().untilLoadingIs(false); + run(player).ignoringNonFatalErrors().untilFullyBuffered(); player.play(); run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED); player.release(); @@ -318,8 +304,7 @@ public final class HlsPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); @@ -355,8 +340,7 @@ public final class HlsPlaybackTest { player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8")); player.prepare(); // Ensure media is fully buffered so that the first subtitle is ready at the start of playback. - run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100); - run(player).untilLoadingIs(false); + run(player).untilFullyBuffered(); player.play(); run(player).untilState(Player.STATE_ENDED); player.release(); diff --git a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/TestPlayerRunHelper.java b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/TestPlayerRunHelper.java index b4aabdb6d2..dce3217d51 100644 --- a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/TestPlayerRunHelper.java +++ b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/TestPlayerRunHelper.java @@ -22,6 +22,7 @@ import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLoop import android.os.Looper; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; import androidx.media3.common.Timeline; @@ -504,6 +505,31 @@ public final class TestPlayerRunHelper { runUntil(conditionTrue::get); } + /** + * Runs tasks of the main {@link Looper} until the player has fully buffered its entire playlist + * and stopped reporting {@link Player#isLoading()}. + * + *

Note that this method won't succeed if the player is configured with a {@link + * androidx.media3.exoplayer.LoadControl} that prevents loading the playlist fully before + * playback resumes. + * + *

If a {@link Player.RepeatMode} setting results in an endless playlist, this method only + * waits until all items have been buffered at least once. + * + * @throws PlaybackException If a playback error occurs. + * @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is + * exceeded. + */ + public void untilFullyBuffered() throws PlaybackException, TimeoutException { + untilBackgroundThreadCondition( + () -> { + long remainingDurationMs = getRemainingPlaybackDuration(player); + return remainingDurationMs != C.TIME_UNSET + && player.getTotalBufferedDuration() >= remainingDurationMs + && !player.isLoading(); + }); + } + @Override public ExoPlayerRunResult ignoringNonFatalErrors() { checkState(!hasBeenUsed); @@ -854,6 +880,41 @@ public final class TestPlayerRunHelper { "Playback thread is not alive, has the player been released?"); } + private static long getRemainingPlaybackDuration(Player player) { + if (player.getCurrentTimeline().isEmpty()) { + return 0; + } + int currentMediaItemIndex = player.getCurrentMediaItemIndex(); + long currentMediaItemDurationMs = getMediaItemDurationMs(player, currentMediaItemIndex); + if (currentMediaItemDurationMs == C.TIME_UNSET) { + return C.TIME_UNSET; + } + long totalDurationMs = currentMediaItemDurationMs - player.getCurrentPosition(); + int mediaItemIndex = currentMediaItemIndex; + while ((mediaItemIndex = getNextMediaItemIndex(player, mediaItemIndex)) != C.INDEX_UNSET + && mediaItemIndex != currentMediaItemIndex) { + currentMediaItemDurationMs = getMediaItemDurationMs(player, mediaItemIndex); + if (currentMediaItemDurationMs == C.TIME_UNSET) { + return C.TIME_UNSET; + } + totalDurationMs += currentMediaItemDurationMs; + } + return totalDurationMs; + } + + private static long getMediaItemDurationMs(Player player, int mediaItemIndex) { + return player + .getCurrentTimeline() + .getWindow(mediaItemIndex, new Timeline.Window()) + .getDurationMs(); + } + + private static int getNextMediaItemIndex(Player player, int mediaItemIndex) { + return player + .getCurrentTimeline() + .getNextWindowIndex(mediaItemIndex, player.getRepeatMode(), player.getShuffleModeEnabled()); + } + /** * A {@link Player.Listener} and {@link AnalyticsListener} that records errors. *