Add Player.Listener.onPlayerErrorChanged

Equivalent to onPlayerError, except it's also called for null.

PiperOrigin-RevId: 381015336
This commit is contained in:
aquilescanta 2021-06-23 14:14:07 +01:00 committed by Oliver Woodman
parent 620ab67fee
commit 1a8592c5c0
5 changed files with 64 additions and 4 deletions

View File

@ -273,7 +273,9 @@ public final class ImaAdsLoaderTest {
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
videoAdPlayer.pauseAd(TEST_AD_MEDIA_INFO);
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException()));
ExoPlaybackException anException = ExoPlaybackException.createForSource(new IOException());
imaAdsLoader.onPlayerErrorChanged(anException);
imaAdsLoader.onPlayerError(anException);
imaAdsLoader.onPositionDiscontinuity(
new Player.PositionInfo(
/* windowUid= */ new Object(),

View File

@ -685,6 +685,11 @@ public class ForwardingPlayer implements Player {
eventListener.onPlayerError(error);
}
@Override
public void onPlayerErrorChanged(@Nullable PlaybackException error) {
eventListener.onPlayerErrorChanged(error);
}
@Override
public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
eventListener.onPositionDiscontinuity(reason);

View File

@ -274,6 +274,19 @@ public interface Player {
*/
default void onPlayerError(PlaybackException error) {}
/**
* Called when the {@link PlaybackException} returned by {@link #getPlayerError()} changes.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*
* <p>Implementations of Player may pass an instance of a subclass of {@link PlaybackException}
* to this method in order to include more information about the error.
*
* @param error The new error, or null if the error is being cleared.
*/
default void onPlayerErrorChanged(@Nullable PlaybackException error) {}
/**
* @deprecated Use {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} instead.
*/

View File

@ -1314,12 +1314,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason));
}
if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError
&& newPlaybackInfo.playbackError != null) {
if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) {
listeners.queueEvent(
Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError));
if (newPlaybackInfo.playbackError != null) {
listeners.queueEvent(
Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
}
}
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection =

View File

@ -44,6 +44,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME;
import static com.google.android.exoplayer2.Player.DEFAULT_FAST_FORWARD_INCREMENT_MS;
import static com.google.android.exoplayer2.Player.DEFAULT_REWIND_INCREMENT_MS;
import static com.google.android.exoplayer2.Player.STATE_ENDED;
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow;
@ -69,6 +70,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
@ -161,6 +163,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.robolectric.shadows.ShadowAudioManager;
@ -2855,6 +2858,38 @@ public final class ExoPlayerTest {
assertThat(position[0]).isEqualTo(0L);
}
@Test
public void onPlayerErrorChanged_isNotifiedForNullError() throws Exception {
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addMediaSource(
new FakeMediaSource(/* timeline= */ null) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
}
});
Player.Listener mockListener = mock(Player.Listener.class);
player.addListener(mockListener);
player.prepare();
player.play();
ExoPlaybackException error = TestPlayerRunHelper.runUntilError(player);
// The media source fails preparation, so we expect both methods to be called.
verify(mockListener).onPlayerErrorChanged(error);
verify(mockListener).onPlayerError(error);
reset(mockListener);
player.setMediaSource(new FakeMediaSource());
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, STATE_ENDED);
// Now the player, which had a playback error, was re-prepared causing the error to be cleared.
// We expect the change to null to be notified, but not onPlayerError.
verify(mockListener).onPlayerErrorChanged(ArgumentMatchers.isNull());
verify(mockListener, never()).onPlayerError(any());
}
@Test
public void recursivePlayerChangesReportConsistentValuesForAllListeners() throws Exception {
// We add two listeners to the player. The first stops the player as soon as it's ready and both
@ -9408,6 +9443,7 @@ public final class ExoPlayerTest {
verify(listener, atLeastOnce()).onStaticMetadataChanged(any());
verify(listener, atLeastOnce()).onPlayWhenReadyChanged(anyBoolean(), anyInt());
verify(listener, atLeastOnce()).onIsPlayingChanged(anyBoolean());
verify(listener, atLeastOnce()).onPlayerErrorChanged(any());
verify(listener, atLeastOnce()).onPlayerError(any());
// Verify all the same events have been recorded with onEvents.