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

View file

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

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
@ -51,6 +52,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public final long contentPositionUs;
/** The current playback state. One of the {@link Player}.STATE_ constants. */
@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. */
public final boolean isLoading;
/** The currently available track groups. */
@ -93,6 +96,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
/* contentPositionUs= */ C.TIME_UNSET,
Player.STATE_IDLE,
/* playbackError= */ null,
/* isLoading= */ false,
TrackGroupArray.EMPTY,
emptyTrackSelectorResult,
@ -124,6 +128,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
long startPositionUs,
long contentPositionUs,
@Player.State int playbackState,
@Nullable ExoPlaybackException playbackError,
boolean isLoading,
TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult,
@ -136,6 +141,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs;
this.playbackState = playbackState;
this.playbackError = playbackError;
this.isLoading = isLoading;
this.trackGroups = trackGroups;
this.trackSelectorResult = trackSelectorResult;
@ -194,6 +200,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs,
periodId.isAd() ? contentPositionUs : C.TIME_UNSET,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
@ -217,6 +224,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
contentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
@ -240,6 +248,31 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
contentPositionUs,
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,
trackGroups,
trackSelectorResult,
@ -263,6 +296,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
contentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
@ -288,6 +322,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
contentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
@ -311,6 +346,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs,
contentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,

View file

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

View file

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