Commit 29af6899 by tonihei Committed by Toni

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
parent 7883eabb
......@@ -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(
......
......@@ -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);
}
/**
......
......@@ -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,
......
......@@ -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);
}
......
......@@ -359,6 +359,7 @@ public final class MediaPeriodQueueTest {
/* startPositionUs= */ 0,
/* contentPositionUs= */ 0,
Player.STATE_READY,
/* playbackError= */ null,
/* isLoading= */ false,
/* trackGroups= */ null,
/* trackSelectorResult= */ null,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment