Move playback error into PlaybackInfo.

The error is closely related to the playback state IDLE and should be updated
in sync with the state to prevent unexpected event ordering and/or keeping the
error after re-preparation.

Issue:#5407
PiperOrigin-RevId: 265014630
This commit is contained in:
tonihei 2019-08-23 10:18:26 +01:00 committed by Toni
parent 7883eabb38
commit 29af6899fe
5 changed files with 77 additions and 27 deletions

View file

@ -74,7 +74,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
private int pendingSetPlaybackParametersAcks; private int pendingSetPlaybackParametersAcks;
private PlaybackParameters playbackParameters; private PlaybackParameters playbackParameters;
private SeekParameters seekParameters; private SeekParameters seekParameters;
@Nullable private ExoPlaybackException playbackError;
// Playback information when there is no pending seek/set source operation. // Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
@ -202,13 +201,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
@Override @Override
@Nullable @Nullable
public ExoPlaybackException getPlaybackError() { public ExoPlaybackException getPlaybackError() {
return playbackError; return playbackInfo.playbackError;
} }
@Override @Override
public void retry() { public void retry() {
if (mediaSource != null if (mediaSource != null && playbackInfo.playbackState == Player.STATE_IDLE) {
&& (playbackError != null || playbackInfo.playbackState == Player.STATE_IDLE)) {
prepare(mediaSource, /* resetPosition= */ false, /* resetState= */ false); prepare(mediaSource, /* resetPosition= */ false, /* resetState= */ false);
} }
} }
@ -220,11 +218,13 @@ import java.util.concurrent.CopyOnWriteArrayList;
@Override @Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
playbackError = null;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
PlaybackInfo playbackInfo = PlaybackInfo playbackInfo =
getResetPlaybackInfo( getResetPlaybackInfo(
resetPosition, resetState, /* playbackState= */ Player.STATE_BUFFERING); resetPosition,
resetState,
/* resetError= */ true,
/* playbackState= */ Player.STATE_BUFFERING);
// Trigger internal prepare first before updating the playback info and notifying external // Trigger internal prepare first before updating the playback info and notifying external
// listeners to ensure that new operations issued in the listener notifications reach the // listeners to ensure that new operations issued in the listener notifications reach the
// player after this prepare. The internal player can't change the playback info immediately // player after this prepare. The internal player can't change the playback info immediately
@ -381,13 +381,13 @@ import java.util.concurrent.CopyOnWriteArrayList;
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
if (reset) { if (reset) {
playbackError = null;
mediaSource = null; mediaSource = null;
} }
PlaybackInfo playbackInfo = PlaybackInfo playbackInfo =
getResetPlaybackInfo( getResetPlaybackInfo(
/* resetPosition= */ reset, /* resetPosition= */ reset,
/* resetState= */ reset, /* resetState= */ reset,
/* resetError= */ reset,
/* playbackState= */ Player.STATE_IDLE); /* playbackState= */ Player.STATE_IDLE);
// Trigger internal stop first before updating the playback info and notifying external // Trigger internal stop first before updating the playback info and notifying external
// listeners to ensure that new operations issued in the listener notifications reach the // listeners to ensure that new operations issued in the listener notifications reach the
@ -415,6 +415,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
getResetPlaybackInfo( getResetPlaybackInfo(
/* resetPosition= */ false, /* resetPosition= */ false,
/* resetState= */ false, /* resetState= */ false,
/* resetError= */ false,
/* playbackState= */ Player.STATE_IDLE); /* playbackState= */ Player.STATE_IDLE);
} }
@ -572,11 +573,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED:
handlePlaybackParameters((PlaybackParameters) msg.obj, /* operationAck= */ msg.arg1 != 0); handlePlaybackParameters((PlaybackParameters) msg.obj, /* operationAck= */ msg.arg1 != 0);
break; break;
case ExoPlayerImplInternal.MSG_ERROR:
ExoPlaybackException playbackError = (ExoPlaybackException) msg.obj;
this.playbackError = playbackError;
notifyListeners(listener -> listener.onPlayerError(playbackError));
break;
default: default:
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -635,7 +631,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
} }
private PlaybackInfo getResetPlaybackInfo( private PlaybackInfo getResetPlaybackInfo(
boolean resetPosition, boolean resetState, @Player.State int playbackState) { boolean resetPosition,
boolean resetState,
boolean resetError,
@Player.State int playbackState) {
if (resetPosition) { if (resetPosition) {
maskingWindowIndex = 0; maskingWindowIndex = 0;
maskingPeriodIndex = 0; maskingPeriodIndex = 0;
@ -659,6 +658,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
resetError ? null : playbackInfo.playbackError,
/* isLoading= */ false, /* isLoading= */ false,
resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
@ -728,6 +728,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
private final @Player.TimelineChangeReason int timelineChangeReason; private final @Player.TimelineChangeReason int timelineChangeReason;
private final boolean seekProcessed; private final boolean seekProcessed;
private final boolean playbackStateChanged; private final boolean playbackStateChanged;
private final boolean playbackErrorChanged;
private final boolean timelineChanged; private final boolean timelineChanged;
private final boolean isLoadingChanged; private final boolean isLoadingChanged;
private final boolean trackSelectorResultChanged; private final boolean trackSelectorResultChanged;
@ -752,6 +753,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
this.seekProcessed = seekProcessed; this.seekProcessed = seekProcessed;
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState; playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState;
playbackErrorChanged =
previousPlaybackInfo.playbackError != playbackInfo.playbackError
&& playbackInfo.playbackError != null;
timelineChanged = previousPlaybackInfo.timeline != playbackInfo.timeline; timelineChanged = previousPlaybackInfo.timeline != playbackInfo.timeline;
isLoadingChanged = previousPlaybackInfo.isLoading != playbackInfo.isLoading; isLoadingChanged = previousPlaybackInfo.isLoading != playbackInfo.isLoading;
trackSelectorResultChanged = trackSelectorResultChanged =
@ -770,6 +774,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
listenerSnapshot, listenerSnapshot,
listener -> listener.onPositionDiscontinuity(positionDiscontinuityReason)); listener -> listener.onPositionDiscontinuity(positionDiscontinuityReason));
} }
if (playbackErrorChanged) {
invokeAll(listenerSnapshot, listener -> listener.onPlayerError(playbackInfo.playbackError));
}
if (trackSelectorResultChanged) { if (trackSelectorResultChanged) {
trackSelector.onSelectionActivated(playbackInfo.trackSelectorResult.info); trackSelector.onSelectionActivated(playbackInfo.trackSelectorResult.info);
invokeAll( invokeAll(

View file

@ -61,7 +61,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
// External messages // External messages
public static final int MSG_PLAYBACK_INFO_CHANGED = 0; public static final int MSG_PLAYBACK_INFO_CHANGED = 0;
public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 1; public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 1;
public static final int MSG_ERROR = 2;
// Internal messages // Internal messages
private static final int MSG_PREPARE = 0; private static final int MSG_PREPARE = 0;
@ -374,19 +373,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
maybeNotifyPlaybackInfoChanged(); maybeNotifyPlaybackInfoChanged();
} catch (ExoPlaybackException e) { } catch (ExoPlaybackException e) {
Log.e(TAG, "Playback error.", e); Log.e(TAG, "Playback error.", e);
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
stopInternal( stopInternal(
/* forceResetRenderers= */ true, /* forceResetRenderers= */ true,
/* resetPositionAndState= */ false, /* resetPositionAndState= */ false,
/* acknowledgeStop= */ false); /* acknowledgeStop= */ false);
playbackInfo = playbackInfo.copyWithPlaybackError(e);
maybeNotifyPlaybackInfoChanged(); maybeNotifyPlaybackInfoChanged();
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Source error.", e); Log.e(TAG, "Source error.", e);
eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget();
stopInternal( stopInternal(
/* forceResetRenderers= */ false, /* forceResetRenderers= */ false,
/* resetPositionAndState= */ false, /* resetPositionAndState= */ false,
/* acknowledgeStop= */ false); /* acknowledgeStop= */ false);
playbackInfo = playbackInfo.copyWithPlaybackError(ExoPlaybackException.createForSource(e));
maybeNotifyPlaybackInfoChanged(); maybeNotifyPlaybackInfoChanged();
} catch (RuntimeException | OutOfMemoryError e) { } catch (RuntimeException | OutOfMemoryError e) {
Log.e(TAG, "Internal runtime error.", e); Log.e(TAG, "Internal runtime error.", e);
@ -394,11 +393,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
e instanceof OutOfMemoryError e instanceof OutOfMemoryError
? ExoPlaybackException.createForOutOfMemoryError((OutOfMemoryError) e) ? ExoPlaybackException.createForOutOfMemoryError((OutOfMemoryError) e)
: ExoPlaybackException.createForUnexpected((RuntimeException) e); : ExoPlaybackException.createForUnexpected((RuntimeException) e);
eventHandler.obtainMessage(MSG_ERROR, error).sendToTarget();
stopInternal( stopInternal(
/* forceResetRenderers= */ true, /* forceResetRenderers= */ true,
/* resetPositionAndState= */ false, /* resetPositionAndState= */ false,
/* acknowledgeStop= */ false); /* acknowledgeStop= */ false);
playbackInfo = playbackInfo.copyWithPlaybackError(error);
maybeNotifyPlaybackInfoChanged(); maybeNotifyPlaybackInfoChanged();
} }
return true; return true;
@ -436,7 +435,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
pendingPrepareCount++; pendingPrepareCount++;
resetInternal( resetInternal(
/* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); /* resetRenderers= */ false,
/* releaseMediaSource= */ true,
resetPosition,
resetState,
/* resetError= */ true);
loadControl.onPrepared(); loadControl.onPrepared();
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
@ -688,7 +691,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetRenderers= */ false, /* resetRenderers= */ false,
/* releaseMediaSource= */ false, /* releaseMediaSource= */ false,
/* resetPosition= */ true, /* resetPosition= */ true,
/* resetState= */ false); /* resetState= */ false,
/* resetError= */ true);
} else { } else {
// Execute the seek in the current media periods. // Execute the seek in the current media periods.
long newPeriodPositionUs = periodPositionUs; long newPeriodPositionUs = periodPositionUs;
@ -834,7 +838,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetRenderers= */ forceResetRenderers || !foregroundMode, /* resetRenderers= */ forceResetRenderers || !foregroundMode,
/* releaseMediaSource= */ true, /* releaseMediaSource= */ true,
/* resetPosition= */ resetPositionAndState, /* resetPosition= */ resetPositionAndState,
/* resetState= */ resetPositionAndState); /* resetState= */ resetPositionAndState,
/* resetError= */ resetPositionAndState);
playbackInfoUpdate.incrementPendingOperationAcks( playbackInfoUpdate.incrementPendingOperationAcks(
pendingPrepareCount + (acknowledgeStop ? 1 : 0)); pendingPrepareCount + (acknowledgeStop ? 1 : 0));
pendingPrepareCount = 0; pendingPrepareCount = 0;
@ -847,7 +852,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetRenderers= */ true, /* resetRenderers= */ true,
/* releaseMediaSource= */ true, /* releaseMediaSource= */ true,
/* resetPosition= */ true, /* resetPosition= */ true,
/* resetState= */ true); /* resetState= */ true,
/* resetError= */ false);
loadControl.onReleased(); loadControl.onReleased();
setState(Player.STATE_IDLE); setState(Player.STATE_IDLE);
internalPlaybackThread.quit(); internalPlaybackThread.quit();
@ -861,7 +867,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
boolean resetRenderers, boolean resetRenderers,
boolean releaseMediaSource, boolean releaseMediaSource,
boolean resetPosition, boolean resetPosition,
boolean resetState) { boolean resetState,
boolean resetError) {
handler.removeMessages(MSG_DO_SOME_WORK); handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false; rebuffering = false;
mediaClock.stop(); mediaClock.stop();
@ -924,6 +931,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackInfo.playbackState, playbackInfo.playbackState,
resetError ? null : playbackInfo.playbackError,
/* isLoading= */ false, /* isLoading= */ false,
resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
@ -1382,7 +1390,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetRenderers= */ false, /* resetRenderers= */ false,
/* releaseMediaSource= */ false, /* releaseMediaSource= */ false,
/* resetPosition= */ true, /* resetPosition= */ true,
/* resetState= */ false); /* resetState= */ false,
/* resetError= */ true);
} }
/** /**

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import androidx.annotation.CheckResult; import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
@ -51,6 +52,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public final long contentPositionUs; public final long contentPositionUs;
/** The current playback state. One of the {@link Player}.STATE_ constants. */ /** The current playback state. One of the {@link Player}.STATE_ constants. */
@Player.State public final int playbackState; @Player.State public final int playbackState;
/** The current playback error, or null if this is not an error state. */
@Nullable public final ExoPlaybackException playbackError;
/** Whether the player is currently loading. */ /** Whether the player is currently loading. */
public final boolean isLoading; public final boolean isLoading;
/** The currently available track groups. */ /** The currently available track groups. */
@ -93,6 +96,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
/* contentPositionUs= */ C.TIME_UNSET, /* contentPositionUs= */ C.TIME_UNSET,
Player.STATE_IDLE, Player.STATE_IDLE,
/* playbackError= */ null,
/* isLoading= */ false, /* isLoading= */ false,
TrackGroupArray.EMPTY, TrackGroupArray.EMPTY,
emptyTrackSelectorResult, emptyTrackSelectorResult,
@ -124,6 +128,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
long startPositionUs, long startPositionUs,
long contentPositionUs, long contentPositionUs,
@Player.State int playbackState, @Player.State int playbackState,
@Nullable ExoPlaybackException playbackError,
boolean isLoading, boolean isLoading,
TrackGroupArray trackGroups, TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult, TrackSelectorResult trackSelectorResult,
@ -136,6 +141,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
this.startPositionUs = startPositionUs; this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs; this.contentPositionUs = contentPositionUs;
this.playbackState = playbackState; this.playbackState = playbackState;
this.playbackError = playbackError;
this.isLoading = isLoading; this.isLoading = isLoading;
this.trackGroups = trackGroups; this.trackGroups = trackGroups;
this.trackSelectorResult = trackSelectorResult; this.trackSelectorResult = trackSelectorResult;
@ -194,6 +200,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs, positionUs,
periodId.isAd() ? contentPositionUs : C.TIME_UNSET, periodId.isAd() ? contentPositionUs : C.TIME_UNSET,
playbackState, playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,
@ -217,6 +224,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,
@ -240,6 +248,31 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
/**
* Copies playback info with a playback error.
*
* @param playbackError The error. See {@link #playbackError}.
* @return Copied playback info with the playback error.
*/
@CheckResult
public PlaybackInfo copyWithPlaybackError(@Nullable ExoPlaybackException playbackError) {
return new PlaybackInfo(
timeline,
periodId,
startPositionUs,
contentPositionUs,
playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,
@ -263,6 +296,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,
@ -288,6 +322,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,
@ -311,6 +346,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs, startPositionUs,
contentPositionUs, contentPositionUs,
playbackState, playbackState,
playbackError,
isLoading, isLoading,
trackGroups, trackGroups,
trackSelectorResult, trackSelectorResult,

View file

@ -465,10 +465,7 @@ public class AnalyticsCollector
@Override @Override
public final void onPlayerError(ExoPlaybackException error) { public final void onPlayerError(ExoPlaybackException error) {
EventTime eventTime = EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
error.type == ExoPlaybackException.TYPE_SOURCE
? generateLoadingMediaPeriodEventTime()
: generatePlayingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) { for (AnalyticsListener listener : listeners) {
listener.onPlayerError(eventTime, error); listener.onPlayerError(eventTime, error);
} }

View file

@ -359,6 +359,7 @@ public final class MediaPeriodQueueTest {
/* startPositionUs= */ 0, /* startPositionUs= */ 0,
/* contentPositionUs= */ 0, /* contentPositionUs= */ 0,
Player.STATE_READY, Player.STATE_READY,
/* playbackError= */ null,
/* isLoading= */ false, /* isLoading= */ false,
/* trackGroups= */ null, /* trackGroups= */ null,
/* trackSelectorResult= */ null, /* trackSelectorResult= */ null,