Fix divergence of timeline between ExoPlayerImpl and ExoPlayerImplInternal.

The playback state in ExoPlayerImpl and ExoPlayerImplInternal is usually kept
in sync. Only the timeline was so far not updated in the same way with the
internal player using a null timeline while ExoPlayerImpl keeps the previous
timeline.

This change removes the need to keep a null timeline which allows to update
the internal timeline in the same way as the external one. This fixes problems
when retrying a failed playback multiple times.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188034988
This commit is contained in:
tonihei 2018-03-06 09:44:15 -08:00 committed by Oliver Woodman
parent b7d2ae1457
commit 0160b87c43
4 changed files with 55 additions and 24 deletions

View File

@ -170,7 +170,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
// because it uses a callback.
hasPendingPrepare = true;
pendingOperationAcks++;
internalPlayer.prepare(mediaSource, resetPosition);
internalPlayer.prepare(mediaSource, resetPosition, resetState);
updatePlaybackInfo(
playbackInfo,
/* positionDiscontinuity= */ false,
@ -567,10 +567,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
@DiscontinuityReason int positionDiscontinuityReason) {
pendingOperationAcks -= operationAcks;
if (pendingOperationAcks == 0) {
if (playbackInfo.timeline == null) {
// Replace internal null timeline with externally visible empty timeline.
playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, playbackInfo.manifest);
}
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
// Replace internal unset start position with externally visible start position of zero.
playbackInfo =

View File

@ -154,7 +154,7 @@ import java.util.Collections;
seekParameters = SeekParameters.DEFAULT;
playbackInfo =
new PlaybackInfo(
/* timeline= */ null, /* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult);
Timeline.EMPTY, /* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult);
playbackInfoUpdate = new PlaybackInfoUpdate();
rendererCapabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
@ -176,8 +176,9 @@ import java.util.Collections;
handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
}
public void prepare(MediaSource mediaSource, boolean resetPosition) {
handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource)
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
handler
.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, resetState ? 1 : 0, mediaSource)
.sendToTarget();
}
@ -286,7 +287,10 @@ import java.util.Collections;
try {
switch (msg.what) {
case MSG_PREPARE:
prepareInternal((MediaSource) msg.obj, msg.arg1 != 0);
prepareInternal(
(MediaSource) msg.obj,
/* resetPosition= */ msg.arg1 != 0,
/* resetState= */ msg.arg2 != 0);
break;
case MSG_SET_PLAY_WHEN_READY:
setPlayWhenReadyInternal(msg.arg1 != 0);
@ -387,9 +391,9 @@ import java.util.Collections;
}
}
private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
pendingPrepareCount++;
resetInternal(/* releaseMediaSource= */ true, resetPosition, /* resetState= */ true);
resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState);
loadControl.onPrepared();
this.mediaSource = mediaSource;
setState(Player.STATE_BUFFERING);
@ -576,7 +580,6 @@ import java.util.Collections;
}
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
Timeline timeline = playbackInfo.timeline;
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
MediaPeriodId periodId;
@ -607,7 +610,7 @@ import java.util.Collections;
}
try {
if (mediaSource == null || timeline == null) {
if (mediaSource == null || pendingPrepareCount > 0) {
// Save seek position for later, as we are still waiting for a prepared source.
pendingInitialSeekPosition = seekPosition;
} else if (periodPositionUs == C.TIME_UNSET) {
@ -752,7 +755,7 @@ import java.util.Collections;
private int getFirstPeriodIndex() {
Timeline timeline = playbackInfo.timeline;
return timeline == null || timeline.isEmpty()
return timeline.isEmpty()
? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
@ -779,7 +782,7 @@ import java.util.Collections;
pendingInitialSeekPosition = null;
}
if (resetState) {
queue.setTimeline(null);
queue.setTimeline(Timeline.EMPTY);
for (PendingMessageInfo pendingMessageInfo : pendingMessages) {
pendingMessageInfo.message.markAsProcessed(/* isDelivered= */ false);
}
@ -788,7 +791,7 @@ import java.util.Collections;
}
playbackInfo =
new PlaybackInfo(
resetState ? null : playbackInfo.timeline,
resetState ? Timeline.EMPTY : playbackInfo.timeline,
resetState ? null : playbackInfo.manifest,
resetPosition ? new MediaPeriodId(getFirstPeriodIndex()) : playbackInfo.periodId,
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
@ -809,7 +812,7 @@ import java.util.Collections;
if (message.getPositionMs() == C.TIME_UNSET) {
// If no delivery time is specified, trigger immediate message delivery.
sendMessageToTarget(message);
} else if (playbackInfo.timeline == null) {
} else if (mediaSource == null || pendingPrepareCount > 0) {
// Still waiting for initial timeline to resolve position.
pendingMessages.add(new PendingMessageInfo(message));
} else {
@ -1133,7 +1136,7 @@ import java.util.Collections;
playbackInfo = playbackInfo.copyWithTimeline(timeline, manifest);
resolvePendingMessagePositions();
if (oldTimeline == null) {
if (pendingPrepareCount > 0) {
playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount);
pendingPrepareCount = 0;
if (pendingInitialSeekPosition != null) {
@ -1295,8 +1298,8 @@ import java.util.Collections;
SeekPosition seekPosition, boolean trySubsequentPeriods) {
Timeline timeline = playbackInfo.timeline;
Timeline seekTimeline = seekPosition.timeline;
if (timeline == null) {
// We don't have a timeline yet, so we can't resolve the position.
if (timeline.isEmpty()) {
// We don't have a valid timeline yet, so we can't resolve the position.
return null;
}
if (seekTimeline.isEmpty()) {
@ -1352,7 +1355,7 @@ import java.util.Collections;
// The player has no media source yet.
return;
}
if (playbackInfo.timeline == null) {
if (pendingPrepareCount > 0) {
// We're waiting to get information about periods.
mediaSource.maybeThrowSourceInfoRefreshError();
return;

View File

@ -24,7 +24,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
*/
/* package */ final class PlaybackInfo {
public final @Nullable Timeline timeline;
public final Timeline timeline;
public final @Nullable Object manifest;
public final MediaPeriodId periodId;
public final long startPositionUs;
@ -37,7 +37,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public volatile long bufferedPositionUs;
public PlaybackInfo(
@Nullable Timeline timeline, long startPositionUs, TrackSelectorResult trackSelectorResult) {
Timeline timeline, long startPositionUs, TrackSelectorResult trackSelectorResult) {
this(
timeline,
/* manifest= */ null,
@ -50,7 +50,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
}
public PlaybackInfo(
@Nullable Timeline timeline,
Timeline timeline,
@Nullable Object manifest,
MediaPeriodId periodId,
long startPositionUs,

View File

@ -1349,6 +1349,38 @@ public final class ExoPlayerTest {
assertThat(windowIndexHolder[2]).isEqualTo(1);
}
@Test
public void testPlaybackErrorTwiceStillKeepsTimeline() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource2 =
new FakeMediaSource(/* timeline= */ null, /* manifest= */ null);
ActionSchedule actionSchedule =
new ActionSchedule.Builder("testPlaybackErrorDoesNotResetPosition")
.pause()
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
.waitForPlaybackState(Player.STATE_IDLE)
.prepareSource(mediaSource2, /* resetPosition= */ false, /* resetState= */ false)
.waitForPlaybackState(Player.STATE_BUFFERING)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
.waitForPlaybackState(Player.STATE_IDLE)
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
} catch (ExoPlaybackException e) {
// Expected exception.
}
testRunner.assertTimelinesEqual(timeline, timeline);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED);
}
@Test
public void testSendMessagesDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);