Update TestPlayerRunHelper
to fail on non-fatal errors by default
Also introduce a fluent API that allows callers to ignore non-fatal errors (while avoiding adding boolean overloads for every method). **Most** tests want to fail on non-fatal errors (since they likely indicate user-visible issues like codec errors etc), only tests explicitly testing fallback in error scenarios should want to ignore them. Before this change there were a few `playUntilXXX` methods. These can now all be triggered via `play(player).untilXXX`, which means effectively every 'until' condition is available in a 'play until' variant that calls `play` just before waiting for the condition. PiperOrigin-RevId: 608988234
This commit is contained in:
parent
2e9cc2784f
commit
bb5c688543
@ -50,6 +50,11 @@
|
||||
* Cast Extension:
|
||||
* Test Utilities:
|
||||
* Implement `onInit()` and `onRelease()` in `FakeRenderer`.
|
||||
* Change `TestPlayerRunHelper.runUntil/playUntil` methods to fail on
|
||||
non-fatal errors (e.g. those reported to
|
||||
`AnalyticsListener.onVideoCodecError`). Use the new
|
||||
`TestPlayerRunHelper.run(player).ignoringNonFatalErrors().untilXXX()`
|
||||
method chain to disable this behavior.
|
||||
* Remove deprecated symbols:
|
||||
* Demo app:
|
||||
|
||||
|
@ -21,6 +21,8 @@ import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
||||
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
@ -29,25 +31,451 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||
import androidx.media3.exoplayer.source.LoadEventInfo;
|
||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||
import androidx.media3.test.utils.ThreadTestUtil;
|
||||
import com.google.common.base.Supplier;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Helper methods to block the calling thread until the provided {@link ExoPlayer} instance reaches
|
||||
* a particular state.
|
||||
*
|
||||
* <p>This class has two usage modes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Fluent method chaining, e.g. {@code
|
||||
* run(player).ignoringNonFatalErrors().untilState(STATE_ENDED)}.
|
||||
* <li>Single method call, e.g. {@code runUntilPlaybackState(player, STATE_ENDED)}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>New usages should prefer the fluent method chaining, and new functionality will only be added
|
||||
* to this form. The older single methods will be kept for backwards compatibility.
|
||||
*/
|
||||
@UnstableApi
|
||||
public class TestPlayerRunHelper {
|
||||
public final class TestPlayerRunHelper {
|
||||
|
||||
private TestPlayerRunHelper() {}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getPlaybackState()} matches the
|
||||
* expected state or a playback error occurs.
|
||||
* Intermediate type that allows callers to run the main {@link Looper} until certain conditions
|
||||
* are met.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If an error occurs while a {@code untilXXX(...)} method is waiting for the condition to
|
||||
* become true, most methods will throw that error (exceptions to this are documented on specific
|
||||
* methods below). Use {@link #ignoringNonFatalErrors()} to ignore non-fatal errors and only fail
|
||||
* on {@linkplain Player.Listener#getPlayerError() fatal playback errors}.
|
||||
*
|
||||
* <p>Instances of this class should only be used for a single {@code untilXXX()} invocation and
|
||||
* not be re-used.
|
||||
*/
|
||||
public static class PlayerRunResult {
|
||||
private final Player player;
|
||||
private final boolean throwNonFatalErrors;
|
||||
|
||||
protected final boolean playBeforeWaiting;
|
||||
|
||||
protected boolean hasBeenUsed;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param player The player to interact with.
|
||||
* @param playBeforeWaiting Whether to call {@link Player#play()} before waiting for the chosen
|
||||
* condition.
|
||||
* @param throwNonFatalErrors Whether to throw non-fatal errors passed to {@link
|
||||
* AnalyticsListener}.
|
||||
*/
|
||||
// This constructor is deliberately private to prevent subclassing outside TestPlayerRunHelper.
|
||||
private PlayerRunResult(Player player, boolean playBeforeWaiting, boolean throwNonFatalErrors) {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
this.player = player;
|
||||
this.playBeforeWaiting = playBeforeWaiting;
|
||||
this.throwNonFatalErrors = throwNonFatalErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getPlaybackState()} matches the
|
||||
* expected state or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@linkplain RobolectricUtil#DEFAULT_TIMEOUT_MS default
|
||||
* timeout} is exceeded.
|
||||
*/
|
||||
public final void untilState(@Player.State int expectedState) throws Exception {
|
||||
runUntil(() -> player.getPlaybackState() == expectedState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getPlayWhenReady()} matches the
|
||||
* expected value or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@linkplain RobolectricUtil#DEFAULT_TIMEOUT_MS default
|
||||
* timeout} is exceeded.
|
||||
*/
|
||||
public final void untilPlayWhenReadyIs(boolean expectedPlayWhenReady) throws Exception {
|
||||
runUntil(() -> player.getPlayWhenReady() == expectedPlayWhenReady);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#isLoading()} matches the expected
|
||||
* value or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@linkplain RobolectricUtil#DEFAULT_TIMEOUT_MS default
|
||||
* timeout} is exceeded.
|
||||
*/
|
||||
public final void untilLoadingIs(boolean expectedIsLoading) throws Exception {
|
||||
runUntil(() -> player.isLoading() == expectedIsLoading);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until a timeline change or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public final Timeline untilTimelineChanges() throws Exception {
|
||||
AtomicReference<@NullableType Timeline> receivedTimeline = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
receivedTimeline.set(timeline);
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
try {
|
||||
runUntil(() -> receivedTimeline.get() != null);
|
||||
return checkNotNull(receivedTimeline.get());
|
||||
} finally {
|
||||
player.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getCurrentTimeline()} matches the
|
||||
* expected timeline or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public final void untilTimelineChangesTo(Timeline expectedTimeline) throws Exception {
|
||||
runUntil(() -> expectedTimeline.equals(player.getCurrentTimeline()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link
|
||||
* Player.Listener#onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int)} is
|
||||
* called with the specified {@link Player.DiscontinuityReason} or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public final void untilPositionDiscontinuityWithReason(
|
||||
@Player.DiscontinuityReason int expectedReason) throws Exception {
|
||||
AtomicBoolean receivedExpectedDiscontinuityReason = new AtomicBoolean(false);
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPositionDiscontinuity(
|
||||
Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
|
||||
if (reason == expectedReason) {
|
||||
receivedExpectedDiscontinuityReason.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
try {
|
||||
runUntil(receivedExpectedDiscontinuityReason::get);
|
||||
} finally {
|
||||
player.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until a player error occurs.
|
||||
*
|
||||
* <p>Non-fatal errors are always ignored.
|
||||
*
|
||||
* @return The raised {@link PlaybackException}.
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public PlaybackException untilPlayerError() throws TimeoutException {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
runMainLooperUntil(() -> player.getPlayerError() != null);
|
||||
return checkNotNull(player.getPlayerError());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player.Listener#onRenderedFirstFrame} is
|
||||
* called or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public void untilFirstFrameIsRendered() throws Exception {
|
||||
AtomicBoolean receivedFirstFrameRenderedCallback = new AtomicBoolean(false);
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
receivedFirstFrameRenderedCallback.set(true);
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
try {
|
||||
runUntil(receivedFirstFrameRenderedCallback::get);
|
||||
} finally {
|
||||
player.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance where the {@code untilXXX(...)} methods ignore non-fatal errors.
|
||||
*
|
||||
* <p>A fatal error is defined as an error that is passed to {@link
|
||||
* Player.Listener#onPlayerError(PlaybackException)} and results in the player transitioning to
|
||||
* {@link Player#STATE_IDLE}. A non-fatal error is defined as an error that is passed to any
|
||||
* other callback (e.g. {@link AnalyticsListener#onLoadError}).
|
||||
*/
|
||||
public PlayerRunResult ignoringNonFatalErrors() {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
return new PlayerRunResult(player, playBeforeWaiting, /* throwNonFatalErrors= */ false);
|
||||
}
|
||||
|
||||
/** Runs the main {@link Looper} until {@code predicate} returns true or an error occurs. */
|
||||
protected final void runUntil(Supplier<Boolean> predicate) throws Exception {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
ErrorListener errorListener = new ErrorListener(throwNonFatalErrors);
|
||||
if (player instanceof ExoPlayer) {
|
||||
ExoPlayer exoplayer = (ExoPlayer) player;
|
||||
exoplayer.addAnalyticsListener(errorListener);
|
||||
}
|
||||
player.addListener(errorListener);
|
||||
if (playBeforeWaiting) {
|
||||
player.play();
|
||||
}
|
||||
try {
|
||||
runMainLooperUntil(() -> predicate.get() || errorListener.hasFatalError());
|
||||
} finally {
|
||||
player.removeListener(errorListener);
|
||||
if (player instanceof ExoPlayer) {
|
||||
((ExoPlayer) player).removeAnalyticsListener(errorListener);
|
||||
}
|
||||
}
|
||||
errorListener.maybeThrow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link ExoPlayer} specific subclass of {@link PlayerRunResult}, giving access to conditions
|
||||
* that only make sense for the {@link ExoPlayer} interface.
|
||||
*/
|
||||
public static final class ExoPlayerRunResult extends PlayerRunResult {
|
||||
|
||||
private final ExoPlayer player;
|
||||
|
||||
private ExoPlayerRunResult(
|
||||
ExoPlayer player, boolean playBeforeWaiting, boolean throwNonFatalErrors) {
|
||||
super(player, playBeforeWaiting, throwNonFatalErrors);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExoPlaybackException untilPlayerError() throws TimeoutException {
|
||||
return (ExoPlaybackException) super.untilPlayerError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link ExoPlayer#isSleepingForOffload()} matches
|
||||
* the expected value, or an error occurs.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public void untilSleepingForOffloadBecomes(boolean expectedSleepingForOffload)
|
||||
throws Exception {
|
||||
AtomicBoolean receivedExpectedValue = new AtomicBoolean(false);
|
||||
ExoPlayer.AudioOffloadListener listener =
|
||||
new ExoPlayer.AudioOffloadListener() {
|
||||
@Override
|
||||
public void onSleepingForOffloadChanged(boolean sleepingForOffload) {
|
||||
if (sleepingForOffload == expectedSleepingForOffload) {
|
||||
receivedExpectedValue.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
player.addAudioOffloadListener(listener);
|
||||
try {
|
||||
runUntil(receivedExpectedValue::get);
|
||||
} finally {
|
||||
player.removeAudioOffloadListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until playback reaches the specified position or an
|
||||
* error occurs.
|
||||
*
|
||||
* <p>The playback thread is automatically blocked from making further progress after reaching
|
||||
* this position and will only be unblocked by other {@code run()/play().untilXXX(...)} method
|
||||
* chains, custom {@link RobolectricUtil#runMainLooperUntil} conditions, or an explicit {@link
|
||||
* ThreadTestUtil#unblockThreadsWaitingForProgressOnCurrentLooper()} on the main thread.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public void untilPosition(int mediaItemIndex, long positionMs) throws Exception {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
Looper applicationLooper = Util.getCurrentOrMainLooper();
|
||||
AtomicBoolean messageHandled = new AtomicBoolean(false);
|
||||
player
|
||||
.createMessage(
|
||||
(messageType, payload) -> {
|
||||
// Block playback thread until the main app thread is able to trigger further
|
||||
// actions.
|
||||
ConditionVariable blockPlaybackThreadCondition = new ConditionVariable();
|
||||
ThreadTestUtil.registerThreadIsBlockedUntilProgressOnLooper(
|
||||
blockPlaybackThreadCondition, applicationLooper);
|
||||
player
|
||||
.getClock()
|
||||
.createHandler(applicationLooper, /* callback= */ null)
|
||||
.post(() -> messageHandled.set(true));
|
||||
try {
|
||||
player.getClock().onThreadBlocked();
|
||||
blockPlaybackThreadCondition.block();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
})
|
||||
.setPosition(mediaItemIndex, positionMs)
|
||||
.send();
|
||||
player.play();
|
||||
runMainLooperUntil(() -> messageHandled.get() || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until playback reaches the specified media item or a
|
||||
* playback error occurs.
|
||||
*
|
||||
* <p>The playback thread is automatically blocked from making further progress after reaching
|
||||
* the media item and will only be unblocked by other {@code run()/play().untilXXX(...)} method
|
||||
* chains, custom {@link RobolectricUtil#runMainLooperUntil} conditions, or an explicit {@link
|
||||
* ThreadTestUtil#unblockThreadsWaitingForProgressOnCurrentLooper()} on the main thread.
|
||||
*
|
||||
* @param mediaItemIndex The index of the media item.
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public void untilStartOfMediaItem(int mediaItemIndex) throws Exception {
|
||||
untilPosition(mediaItemIndex, /* positionMs= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until the player completely handled all previously
|
||||
* issued commands on the internal playback thread.
|
||||
*
|
||||
* <p>Both fatal and non-fatal errors are always ignored.
|
||||
*
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public void untilPendingCommandsAreFullyHandled() throws Exception {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
// Send message to player that will arrive after all other pending commands. Thus, the message
|
||||
// execution on the app thread will also happen after all other pending command
|
||||
// acknowledgements have arrived back on the app thread.
|
||||
AtomicBoolean receivedMessageCallback = new AtomicBoolean(false);
|
||||
player
|
||||
.createMessage((type, data) -> receivedMessageCallback.set(true))
|
||||
.setLooper(Util.getCurrentOrMainLooper())
|
||||
.send();
|
||||
runMainLooperUntil(receivedMessageCallback::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExoPlayerRunResult ignoringNonFatalErrors() {
|
||||
checkState(!hasBeenUsed);
|
||||
hasBeenUsed = true;
|
||||
return new ExoPlayerRunResult(player, playBeforeWaiting, /* throwNonFatalErrors= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a fluent "wait for condition X" assertion.
|
||||
*
|
||||
* <p>Callers can use the returned {@link PlayerRunResult} to run the main {@link Looper} until
|
||||
* certain conditions are met.
|
||||
*/
|
||||
public static PlayerRunResult run(Player player) {
|
||||
return new PlayerRunResult(
|
||||
player, /* playBeforeWaiting= */ false, /* throwNonFatalErrors= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a fluent "wait for condition X" assertion.
|
||||
*
|
||||
* <p>Callers can use the returned {@link ExoPlayerRunResult} to run the main {@link Looper} until
|
||||
* certain conditions are met.
|
||||
*/
|
||||
public static ExoPlayerRunResult run(ExoPlayer player) {
|
||||
return new ExoPlayerRunResult(
|
||||
player, /* playBeforeWaiting= */ false, /* throwNonFatalErrors= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a fluent "start playback and wait for condition X" assertion.
|
||||
*
|
||||
* <p>Callers can use the returned {@link PlayerRunResult} to run the main {@link Looper} until
|
||||
* certain conditions are met.
|
||||
*
|
||||
* <p>This is the same as {@link #run(Player)} but ensures {@link Player#play()} is called before
|
||||
* waiting in subsequent {@code untilXXX(...)} methods.
|
||||
*/
|
||||
public static PlayerRunResult play(Player player) {
|
||||
return new PlayerRunResult(
|
||||
player, /* playBeforeWaiting= */ true, /* throwNonFatalErrors= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a fluent "start playback and wait for condition X" assertion.
|
||||
*
|
||||
* <p>Callers can use the returned {@link ExoPlayerRunResult} to run the main {@link Looper} until
|
||||
* certain conditions are met.
|
||||
*
|
||||
* <p>This is the same as {@link #run(ExoPlayer)} but ensures {@link ExoPlayer#play()} is called
|
||||
* before waiting in subsequent {@code untilXXX(...)} methods.
|
||||
*/
|
||||
public static ExoPlayerRunResult play(ExoPlayer player) {
|
||||
return new ExoPlayerRunResult(
|
||||
player, /* playBeforeWaiting= */ true, /* throwNonFatalErrors= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getPlaybackState()} matches the
|
||||
* expected state or an error occurs.
|
||||
*
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link PlayerRunResult#untilState(int)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedState The expected {@link Player.State}.
|
||||
@ -56,22 +484,23 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilPlaybackState(Player player, @Player.State int expectedState)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
runMainLooperUntil(
|
||||
() -> player.getPlaybackState() == expectedState || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilState(expectedState);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getPlayWhenReady()} matches the
|
||||
* expected value or a playback error occurs.
|
||||
* expected value or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilPlayWhenReadyIs(boolean)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedPlayWhenReady The expected value for {@link Player#getPlayWhenReady()}.
|
||||
@ -80,23 +509,23 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilPlayWhenReady(Player player, boolean expectedPlayWhenReady)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
runMainLooperUntil(
|
||||
() ->
|
||||
player.getPlayWhenReady() == expectedPlayWhenReady || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilPlayWhenReadyIs(expectedPlayWhenReady);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#isLoading()} matches the expected
|
||||
* value or a playback error occurs.
|
||||
* value or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilLoadingIs(boolean)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedIsLoading The expected value for {@link Player#isLoading()}.
|
||||
@ -105,22 +534,23 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilIsLoading(Player player, boolean expectedIsLoading)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
runMainLooperUntil(
|
||||
() -> player.isLoading() == expectedIsLoading || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilLoadingIs(expectedIsLoading);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link Player#getCurrentTimeline()} matches the
|
||||
* expected timeline or a playback error occurs.
|
||||
* expected timeline or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilTimelineChangesTo(Timeline)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedTimeline The expected {@link Timeline}.
|
||||
@ -129,23 +559,22 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilTimelineChanged(Player player, Timeline expectedTimeline)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
runMainLooperUntil(
|
||||
() ->
|
||||
expectedTimeline.equals(player.getCurrentTimeline())
|
||||
|| player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilTimelineChangesTo(expectedTimeline);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until a timeline change or a playback error occurs.
|
||||
* Runs tasks of the main {@link Looper} until a timeline change or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilTimelineChanges()}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @return The new {@link Timeline}.
|
||||
@ -153,30 +582,24 @@ public class TestPlayerRunHelper {
|
||||
* exceeded.
|
||||
*/
|
||||
public static Timeline runUntilTimelineChanged(Player player) throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
AtomicReference<@NullableType Timeline> receivedTimeline = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
receivedTimeline.set(timeline);
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
runMainLooperUntil(() -> receivedTimeline.get() != null || player.getPlayerError() != null);
|
||||
player.removeListener(listener);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
return run(player).untilTimelineChanges();
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return checkNotNull(receivedTimeline.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link
|
||||
* Player.Listener#onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int)} is
|
||||
* called with the specified {@link Player.DiscontinuityReason} or a playback error occurs.
|
||||
* called with the specified {@link Player.DiscontinuityReason} or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilPositionDiscontinuityWithReason(int)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedReason The expected {@link Player.DiscontinuityReason}.
|
||||
@ -185,51 +608,40 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilPositionDiscontinuity(
|
||||
Player player, @Player.DiscontinuityReason int expectedReason) throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
if (player instanceof ExoPlayer) {
|
||||
verifyPlaybackThreadIsAlive((ExoPlayer) player);
|
||||
}
|
||||
AtomicBoolean receivedCallback = new AtomicBoolean(false);
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPositionDiscontinuity(
|
||||
Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
|
||||
if (reason == expectedReason) {
|
||||
receivedCallback.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
runMainLooperUntil(() -> receivedCallback.get() || player.getPlayerError() != null);
|
||||
player.removeListener(listener);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilPositionDiscontinuityWithReason(expectedReason);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until a player error occurs.
|
||||
*
|
||||
* <p>Non-fatal errors are ignored.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(ExoPlayer)} and {@link
|
||||
* ExoPlayerRunResult#untilPlayerError()}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @return The raised {@link ExoPlaybackException}.
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public static ExoPlaybackException runUntilError(ExoPlayer player) throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
verifyPlaybackThreadIsAlive(player);
|
||||
|
||||
runMainLooperUntil(() -> player.getPlayerError() != null);
|
||||
return checkNotNull(player.getPlayerError());
|
||||
return run(player).untilPlayerError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until {@link
|
||||
* ExoPlayer.AudioOffloadListener#onSleepingForOffloadChanged(boolean)} is called or a playback
|
||||
* error occurs.
|
||||
* Runs tasks of the main {@link Looper} until {@link ExoPlayer#isSleepingForOffload()} matches
|
||||
* the expected value, or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(ExoPlayer)} and {@link
|
||||
* ExoPlayerRunResult#untilSleepingForOffloadBecomes(boolean)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedSleepForOffload The expected sleep of offload state.
|
||||
@ -238,66 +650,51 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void runUntilSleepingForOffload(ExoPlayer player, boolean expectedSleepForOffload)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
verifyPlaybackThreadIsAlive(player);
|
||||
|
||||
AtomicBoolean receiverCallback = new AtomicBoolean(false);
|
||||
ExoPlayer.AudioOffloadListener listener =
|
||||
new ExoPlayer.AudioOffloadListener() {
|
||||
@Override
|
||||
public void onSleepingForOffloadChanged(boolean sleepingForOffload) {
|
||||
if (sleepingForOffload == expectedSleepForOffload) {
|
||||
receiverCallback.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
player.addAudioOffloadListener(listener);
|
||||
runMainLooperUntil(() -> receiverCallback.get() || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilSleepingForOffloadBecomes(expectedSleepForOffload);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until the {@link Player.Listener#onRenderedFirstFrame}
|
||||
* callback is called or a playback error occurs.
|
||||
* Runs tasks of the main {@link Looper} until {@link Player.Listener#onRenderedFirstFrame} is
|
||||
* called or an error occurs.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}..
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(Player)} and {@link
|
||||
* PlayerRunResult#untilFirstFrameIsRendered()}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public static void runUntilRenderedFirstFrame(ExoPlayer player) throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
verifyPlaybackThreadIsAlive(player);
|
||||
|
||||
AtomicBoolean receivedCallback = new AtomicBoolean(false);
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
receivedCallback.set(true);
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
runMainLooperUntil(() -> receivedCallback.get() || player.getPlayerError() != null);
|
||||
player.removeListener(listener);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
run(player).untilFirstFrameIsRendered();
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player}
|
||||
* reaches the specified position or a playback error occurs.
|
||||
* Calls {@link Player#play()} then runs tasks of the main {@link Looper} until the {@code player}
|
||||
* reaches the specified position or an error occurs.
|
||||
*
|
||||
* <p>The playback thread is automatically blocked from making further progress after reaching
|
||||
* this position and will only be unblocked by other {@code run/playUntil...} methods, custom
|
||||
* this position and will only be unblocked by other {@code runUntil/playUntil...} methods, custom
|
||||
* {@link RobolectricUtil#runMainLooperUntil} conditions or an explicit {@link
|
||||
* ThreadTestUtil#unblockThreadsWaitingForProgressOnCurrentLooper()} on the main thread.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(ExoPlayer)} and {@link
|
||||
* ExoPlayerRunResult#untilPosition(int, long)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param mediaItemIndex The index of the media item.
|
||||
@ -307,47 +704,28 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void playUntilPosition(ExoPlayer player, int mediaItemIndex, long positionMs)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
verifyPlaybackThreadIsAlive(player);
|
||||
Looper applicationLooper = Util.getCurrentOrMainLooper();
|
||||
AtomicBoolean messageHandled = new AtomicBoolean(false);
|
||||
player
|
||||
.createMessage(
|
||||
(messageType, payload) -> {
|
||||
// Block playback thread until the main app thread is able to trigger further actions.
|
||||
ConditionVariable blockPlaybackThreadCondition = new ConditionVariable();
|
||||
ThreadTestUtil.registerThreadIsBlockedUntilProgressOnLooper(
|
||||
blockPlaybackThreadCondition, applicationLooper);
|
||||
player
|
||||
.getClock()
|
||||
.createHandler(applicationLooper, /* callback= */ null)
|
||||
.post(() -> messageHandled.set(true));
|
||||
try {
|
||||
player.getClock().onThreadBlocked();
|
||||
blockPlaybackThreadCondition.block();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
})
|
||||
.setPosition(mediaItemIndex, positionMs)
|
||||
.send();
|
||||
player.play();
|
||||
runMainLooperUntil(() -> messageHandled.get() || player.getPlayerError() != null);
|
||||
if (player.getPlayerError() != null) {
|
||||
throw new IllegalStateException(player.getPlayerError());
|
||||
try {
|
||||
play(player).untilPosition(mediaItemIndex, positionMs);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Player#play()}, runs tasks of the main {@link Looper} until the {@code player}
|
||||
* Calls {@link Player#play()} then runs tasks of the main {@link Looper} until the {@code player}
|
||||
* reaches the specified media item or a playback error occurs.
|
||||
*
|
||||
* <p>The playback thread is automatically blocked from making further progress after reaching the
|
||||
* media item and will only be unblocked by other {@code run/playUntil...} methods, custom {@link
|
||||
* RobolectricUtil#runMainLooperUntil} conditions or an explicit {@link
|
||||
* media item and will only be unblocked by other {@code runUntil/playUntil...} methods, custom
|
||||
* {@link RobolectricUtil#runMainLooperUntil} conditions or an explicit {@link
|
||||
* ThreadTestUtil#unblockThreadsWaitingForProgressOnCurrentLooper()} on the main thread.
|
||||
*
|
||||
* <p>If a playback error occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
* <p>If a checked exception occurs it will be thrown wrapped in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>New usages should prefer {@link #run(ExoPlayer)} and {@link
|
||||
* ExoPlayerRunResult#untilStartOfMediaItem(int)}.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param mediaItemIndex The index of the media item.
|
||||
@ -356,31 +734,34 @@ public class TestPlayerRunHelper {
|
||||
*/
|
||||
public static void playUntilStartOfMediaItem(ExoPlayer player, int mediaItemIndex)
|
||||
throws TimeoutException {
|
||||
playUntilPosition(player, mediaItemIndex, /* positionMs= */ 0);
|
||||
try {
|
||||
play(player).untilStartOfMediaItem(mediaItemIndex);
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until the player completely handled all previously issued
|
||||
* commands on the internal playback thread.
|
||||
*
|
||||
* <p>Both fatal and non-fatal errors are ignored.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public static void runUntilPendingCommandsAreFullyHandled(ExoPlayer player)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
verifyPlaybackThreadIsAlive(player);
|
||||
|
||||
// Send message to player that will arrive after all other pending commands. Thus, the message
|
||||
// execution on the app thread will also happen after all other pending command
|
||||
// acknowledgements have arrived back on the app thread.
|
||||
AtomicBoolean receivedMessageCallback = new AtomicBoolean(false);
|
||||
player
|
||||
.createMessage((type, data) -> receivedMessageCallback.set(true))
|
||||
.setLooper(Util.getCurrentOrMainLooper())
|
||||
.send();
|
||||
runMainLooperUntil(receivedMessageCallback::get);
|
||||
try {
|
||||
run(player).untilPendingCommandsAreFullyHandled();
|
||||
} catch (RuntimeException | TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyMainTestThread(Player player) {
|
||||
@ -395,4 +776,92 @@ public class TestPlayerRunHelper {
|
||||
player.getPlaybackLooper().getThread().isAlive(),
|
||||
"Playback thread is not alive, has the player been released?");
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Player.Listener} and {@link AnalyticsListener} that records errors.
|
||||
*
|
||||
* <p>All methods must be called on {@link Player#getApplicationLooper()}.
|
||||
*/
|
||||
private static final class ErrorListener implements AnalyticsListener, Player.Listener {
|
||||
|
||||
@Nullable private final List<Exception> nonFatalErrors;
|
||||
private @MonotonicNonNull Exception fatalError;
|
||||
|
||||
public ErrorListener(boolean throwNonFatalErrors) {
|
||||
if (throwNonFatalErrors) {
|
||||
nonFatalErrors = new ArrayList<>();
|
||||
} else {
|
||||
nonFatalErrors = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasFatalError() {
|
||||
return fatalError != null;
|
||||
}
|
||||
|
||||
public void maybeThrow() throws Exception {
|
||||
if (fatalError != null) {
|
||||
throw fatalError;
|
||||
}
|
||||
if (nonFatalErrors != null && !nonFatalErrors.isEmpty()) {
|
||||
IllegalStateException ise =
|
||||
new IllegalStateException(
|
||||
"Non-fatal errors detected. Attach an EventLogger and redirect logcat with"
|
||||
+ " ShadowLog.stream to see full details.");
|
||||
for (Exception nonFatalError : nonFatalErrors) {
|
||||
ise.addSuppressed(nonFatalError);
|
||||
}
|
||||
throw ise;
|
||||
}
|
||||
}
|
||||
|
||||
// Player.Listener impl
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
fatalError = error;
|
||||
}
|
||||
|
||||
// AnalyticsListener impl
|
||||
|
||||
@Override
|
||||
public void onLoadError(
|
||||
EventTime eventTime,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData,
|
||||
IOException error,
|
||||
boolean wasCanceled) {
|
||||
if (nonFatalErrors != null) {
|
||||
nonFatalErrors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioSinkError(EventTime eventTime, Exception audioSinkError) {
|
||||
if (nonFatalErrors != null) {
|
||||
nonFatalErrors.add(audioSinkError);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioCodecError(EventTime eventTime, Exception audioCodecError) {
|
||||
if (nonFatalErrors != null) {
|
||||
nonFatalErrors.add(audioCodecError);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoCodecError(EventTime eventTime, Exception videoCodecError) {
|
||||
if (nonFatalErrors != null) {
|
||||
nonFatalErrors.add(videoCodecError);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrmSessionManagerError(EventTime eventTime, Exception error) {
|
||||
if (nonFatalErrors != null) {
|
||||
nonFatalErrors.add(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user