Add TestPlayerRunHelper run(player).untilFullyBuffered

This simplifies some common test setup steps that rely on
a fully buffered player before making further test progress.

PiperOrigin-RevId: 693651493
This commit is contained in:
tonihei 2024-11-06 02:43:09 -08:00 committed by Copybara-Service
parent 08470140ac
commit 5336d71c22
9 changed files with 117 additions and 110 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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,

View File

@ -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();

View File

@ -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();

View File

@ -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()}.
*
* <p>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.
*
* <p>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.
*