mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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:
parent
08470140ac
commit
5336d71c22
9 changed files with 117 additions and 110 deletions
|
|
@ -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.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.END_OF_STREAM_ITEM;
|
||||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
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.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.runUntilError;
|
||||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilIsLoading;
|
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.runUntilPlaybackState;
|
||||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilTimelineChanged;
|
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilTimelineChanged;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
@ -477,11 +478,10 @@ public final class DefaultAnalyticsCollectorTest {
|
||||||
player.setMediaSources(ImmutableList.of(mediaSource1, mediaSource2));
|
player.setMediaSources(ImmutableList.of(mediaSource1, mediaSource2));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Wait until second period has fully loaded to assert loading events.
|
// Wait until second period has fully loaded to assert loading events.
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
|
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED))
|
assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED))
|
||||||
|
|
@ -975,16 +975,15 @@ public final class DefaultAnalyticsCollectorTest {
|
||||||
|
|
||||||
player.setMediaSource(fakeMediaSource);
|
player.setMediaSource(fakeMediaSource);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
run(player).untilState(Player.STATE_READY);
|
||||||
player.addMediaSource(fakeMediaSource);
|
player.addMediaSource(fakeMediaSource);
|
||||||
// Wait until second period has fully loaded to assert loading events.
|
// Wait until second period has fully loaded to assert loading events.
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
player.removeMediaItem(/* index= */ 0);
|
player.removeMediaItem(/* index= */ 0);
|
||||||
runUntilPlaybackState(player, Player.STATE_BUFFERING);
|
run(player).untilState(Player.STATE_BUFFERING);
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
run(player).untilState(Player.STATE_READY);
|
||||||
player.play();
|
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.
|
// Populate event ids with second to last timeline that still contained both periods.
|
||||||
populateEventIds(listener.reportedTimelines.get(listener.reportedTimelines.size() - 2));
|
populateEventIds(listener.reportedTimelines.get(listener.reportedTimelines.size() - 2));
|
||||||
|
|
@ -1140,18 +1139,17 @@ public final class DefaultAnalyticsCollectorTest {
|
||||||
player.setMediaSource(fakeMediaSource);
|
player.setMediaSource(fakeMediaSource);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure everything is preloaded.
|
// Ensure everything is preloaded.
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
run(player).untilState(Player.STATE_READY);
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
|
||||||
// Wait in each content part to ensure previously triggered events get a chance to be delivered.
|
// Wait in each content part to ensure previously triggered events get a chance to be delivered.
|
||||||
playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 3_000);
|
play(player).untilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 3_000);
|
||||||
runUntilPendingCommandsAreFullyHandled(player);
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 8_000);
|
play(player).untilPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 8_000);
|
||||||
runUntilPendingCommandsAreFullyHandled(player);
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
// Wait for final timeline change that marks post-roll played.
|
// Wait for final timeline change that marks post-roll played.
|
||||||
runUntilTimelineChanged(player);
|
run(player).untilTimelineChanges();
|
||||||
|
|
||||||
Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
EventWindowAndPeriodId prerollAd =
|
EventWindowAndPeriodId prerollAd =
|
||||||
|
|
@ -1343,14 +1341,13 @@ public final class DefaultAnalyticsCollectorTest {
|
||||||
player.setMediaSource(fakeMediaSource);
|
player.setMediaSource(fakeMediaSource);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure everything is preloaded.
|
// Ensure everything is preloaded.
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
// Seek behind the midroll.
|
// Seek behind the midroll.
|
||||||
player.seekTo(/* positionMs= */ 6_000);
|
player.seekTo(/* positionMs= */ 6_000);
|
||||||
// Wait until loading started again to assert loading events.
|
// Wait until loading started again to assert loading events.
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilLoadingIs(true);
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
|
|
||||||
Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
EventWindowAndPeriodId midrollAd =
|
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
|
// 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
|
// ensures both periods report the same load event (because period1's DRM session is
|
||||||
// already preacquired by the time the key load completes).
|
// already preacquired by the time the key load completes).
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
run(player).untilFullyBuffered();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
mediaDrmCallback.keyCondition.open();
|
mediaDrmCallback.keyCondition.open();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).isEmpty();
|
||||||
|
|
@ -1590,9 +1585,9 @@ public final class DefaultAnalyticsCollectorTest {
|
||||||
player.play();
|
player.play();
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSource);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
run(player).untilFullyBuffered();
|
||||||
mediaDrmCallback.keyCondition.open();
|
mediaDrmCallback.keyCondition.open();
|
||||||
runUntilError(player);
|
run(player).untilPlayerError();
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_DRM_SESSION_MANAGER_ERROR)).containsExactly(period0);
|
||||||
|
|
|
||||||
|
|
@ -108,13 +108,7 @@ public final class MergingPlaylistPlaybackTest {
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Load all content prior to play to reduce flaky-ness resulting from the playback advancement
|
// Load all content prior to play to reduce flaky-ness resulting from the playback advancement
|
||||||
// speed and handling of discontinuities.
|
// speed and handling of discontinuities.
|
||||||
long durationToBufferMs =
|
run(player).untilFullyBuffered();
|
||||||
(firstItemVideoClipped || firstItemAudioClipped ? 300L : 1024L)
|
|
||||||
+ (secondItemVideoClipped || secondItemAudioClipped ? 300L : 1024L);
|
|
||||||
run(player)
|
|
||||||
.untilBackgroundThreadCondition(
|
|
||||||
() -> player.getTotalBufferedDuration() >= durationToBufferMs);
|
|
||||||
run(player).untilPendingCommandsAreFullyHandled();
|
|
||||||
// Reset the listener to avoid verifying the onIsLoadingChanged events from prepare().
|
// Reset the listener to avoid verifying the onIsLoadingChanged events from prepare().
|
||||||
reset(listener);
|
reset(listener);
|
||||||
player.play();
|
player.play();
|
||||||
|
|
@ -160,12 +154,8 @@ public final class MergingPlaylistPlaybackTest {
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Load all content prior to play to reduce flaky-ness resulting from the playback advancement
|
// Load all content prior to play to reduce flaky-ness resulting from the playback advancement
|
||||||
// speed and handling of discontinuities.
|
// speed and handling of discontinuities.
|
||||||
long durationToBufferMs = (firstItemVideoClipped || firstItemAudioClipped ? 300L : 1024L) * 5;
|
run(player).untilFullyBuffered();
|
||||||
run(player)
|
|
||||||
.untilBackgroundThreadCondition(
|
|
||||||
() -> player.getTotalBufferedDuration() >= durationToBufferMs);
|
|
||||||
// Reset the listener to avoid verifying the onIsLoadingChanged events from prepare().
|
// Reset the listener to avoid verifying the onIsLoadingChanged events from prepare().
|
||||||
run(player).untilPendingCommandsAreFullyHandled();
|
|
||||||
reset(listener);
|
reset(listener);
|
||||||
player.play();
|
player.play();
|
||||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.e2etest;
|
package androidx.media3.exoplayer.e2etest;
|
||||||
|
|
||||||
|
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.robolectric.annotation.GraphicsMode.Mode.NATIVE;
|
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.addMediaItem(MediaItem.fromUri("asset:///media/mka/bear-opus.mka"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
player.addMediaItem(MediaItem.fromUri("asset:///media/wav/sample.wav"));
|
player.addMediaItem(MediaItem.fromUri("asset:///media/wav/sample.wav"));
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
// Wait until second period has fully loaded to start the playback.
|
// Wait until second period has fully loaded to start the playback.
|
||||||
player.play();
|
player.play();
|
||||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
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.addMediaItem(MediaItem.fromUri("asset:///media/mp4/preroll-5s.mp4"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
MediaItem mediaItemWithSubtitle =
|
MediaItem mediaItemWithSubtitle =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setUri("asset:///media/mp4/preroll-5s.mp4")
|
.setUri("asset:///media/mp4/preroll-5s.mp4")
|
||||||
|
|
@ -130,8 +128,7 @@ public final class PlaylistPlaybackTest {
|
||||||
.build()))
|
.build()))
|
||||||
.build();
|
.build();
|
||||||
player.addMediaItem(mediaItemWithSubtitle);
|
player.addMediaItem(mediaItemWithSubtitle);
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ true);
|
run(player).untilFullyBuffered();
|
||||||
TestPlayerRunHelper.runUntilIsLoading(player, /* expectedIsLoading= */ false);
|
|
||||||
// Wait until second period has fully loaded to start the playback.
|
// Wait until second period has fully loaded to start the playback.
|
||||||
player.play();
|
player.play();
|
||||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ public class SubtitlePlaybackTest {
|
||||||
player.setMediaItem(mediaItem);
|
player.setMediaItem(mediaItem);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY);
|
||||||
run(player).untilLoadingIs(false);
|
run(player).untilFullyBuffered();
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -160,7 +160,7 @@ public class SubtitlePlaybackTest {
|
||||||
player.setMediaItem(mediaItem);
|
player.setMediaItem(mediaItem);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_READY);
|
||||||
run(player).untilLoadingIs(false);
|
run(player).untilFullyBuffered();
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class WebvttPlaybackTest {
|
||||||
player.setMediaItem(mediaItem);
|
player.setMediaItem(mediaItem);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
run(player).untilState(Player.STATE_READY);
|
run(player).untilState(Player.STATE_READY);
|
||||||
run(player).untilLoadingIs(false);
|
run(player).untilFullyBuffered();
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -139,7 +139,7 @@ public class WebvttPlaybackTest {
|
||||||
player.setMediaItem(mediaItem);
|
player.setMediaItem(mediaItem);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
run(player).untilState(Player.STATE_READY);
|
run(player).untilState(Player.STATE_READY);
|
||||||
run(player).untilLoadingIs(false);
|
run(player).untilFullyBuffered();
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
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.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.runUntilPendingCommandsAreFullyHandled;
|
||||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
|
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.
|
// Add ad at the current playback position during playback.
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
runUntilIsLoading(player, false);
|
run(player).untilFullyBuffered();
|
||||||
runMainLooperUntil(() -> player.getBufferedPercentage() == 100);
|
|
||||||
AdPlaybackState secondAdPlaybackState =
|
AdPlaybackState secondAdPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
firstAdPlaybackState,
|
firstAdPlaybackState,
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -136,10 +135,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -178,10 +174,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -218,8 +211,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-ttml/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-ttml/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -250,8 +242,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/webvtt-in-mp4/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/webvtt-in-mp4/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -281,8 +272,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -322,10 +312,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -364,10 +351,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/ttml-in-mp4/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -411,8 +395,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -452,8 +435,7 @@ public final class DashPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/cea608/manifest.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -690,8 +672,7 @@ public final class DashPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/dash/multi-period-with-offset/sample.mpd"));
|
MediaItem.fromUri("asset:///media/dash/multi-period-with-offset/sample.mpd"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered to avoid flakiness from loading second period too late.
|
// Ensure media is fully buffered to avoid flakiness from loading second period too late.
|
||||||
run(player).untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
run(player).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -124,10 +123,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -164,10 +160,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/standalone-webvtt/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -198,8 +191,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -236,10 +228,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -275,10 +264,7 @@ public final class HlsPlaybackTest {
|
||||||
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
MediaItem.fromUri("asset:///media/hls/ttml-in-mp4/multivariant_playlist.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
||||||
run(player)
|
run(player).ignoringNonFatalErrors().untilFullyBuffered();
|
||||||
.ignoringNonFatalErrors()
|
|
||||||
.untilBackgroundThreadCondition(() -> player.getBufferedPercentage() == 100);
|
|
||||||
run(player).ignoringNonFatalErrors().untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
run(player).ignoringNonFatalErrors().untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -318,8 +304,7 @@ public final class HlsPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
@ -355,8 +340,7 @@ public final class HlsPlaybackTest {
|
||||||
player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8"));
|
player.setMediaItem(MediaItem.fromUri("asset:///media/hls/cea608/manifest.m3u8"));
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Ensure media is fully buffered so that the first subtitle is ready at the start of playback.
|
// 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).untilFullyBuffered();
|
||||||
run(player).untilLoadingIs(false);
|
|
||||||
player.play();
|
player.play();
|
||||||
run(player).untilState(Player.STATE_ENDED);
|
run(player).untilState(Player.STATE_ENDED);
|
||||||
player.release();
|
player.release();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLoop
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
|
|
@ -504,6 +505,31 @@ public final class TestPlayerRunHelper {
|
||||||
runUntil(conditionTrue::get);
|
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
|
@Override
|
||||||
public ExoPlayerRunResult ignoringNonFatalErrors() {
|
public ExoPlayerRunResult ignoringNonFatalErrors() {
|
||||||
checkState(!hasBeenUsed);
|
checkState(!hasBeenUsed);
|
||||||
|
|
@ -854,6 +880,41 @@ public final class TestPlayerRunHelper {
|
||||||
"Playback thread is not alive, has the player been released?");
|
"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.
|
* A {@link Player.Listener} and {@link AnalyticsListener} that records errors.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue