Fix some EventListener logic in ExoPlayerImpl

We currently have multiple places in ExoPlayerImpl that assign PlaybackInfo
instances and then inform listeners of all current changes. This is not ideal
because it causes multiple issues:
 1. Some changes may easily be forgotten, e.g. there are clearly some checks
    missing to see if isPlaying changed (e.g. in seekTo or setMediaSources)
 2. Some callbacks didn't check if the value actually changed before sending
    the callback (e.g. for the timeline change in setMediaSources - if the
    timeline is still the same, we shouldn't send a onTimelineChanged event).
 3. Having multiple callbacks in a single Runnable changes the order of
    listener invocations slightly: Currently all events for one listener will
    be send first before moving to the next listener. It should however send
    a single event to all listeners first before moving to the next event.

All these issues can be solved by always using updatePlaybackInfo and never
assigning playbackInfo directly in another place.

Some tests needed to be updated as well because of issues (2) and (3). Also
added a new test to cover issue (1).

PiperOrigin-RevId: 302844981
This commit is contained in:
tonihei 2020-03-25 09:13:04 +00:00 committed by Oliver Woodman
parent c3cbccbbf8
commit 46a10ec01a
2 changed files with 180 additions and 114 deletions

View File

@ -432,11 +432,16 @@ import java.util.concurrent.TimeoutException;
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++; pendingOperationAcks++;
List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources); List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
Timeline timeline = PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
internalPlayer.addMediaSources(index, holders, shuffleOrder); internalPlayer.addMediaSources(index, holders, shuffleOrder);
notifyListeners( updatePlaybackInfo(
listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); playbackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
} }
@Override @Override
@ -469,11 +474,16 @@ import java.util.concurrent.TimeoutException;
pendingOperationAcks++; pendingOperationAcks++;
newFromIndex = Math.min(newFromIndex, mediaSourceHolders.size() - (toIndex - fromIndex)); newFromIndex = Math.min(newFromIndex, mediaSourceHolders.size() - (toIndex - fromIndex));
Playlist.moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex); Playlist.moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
Timeline timeline = PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder); internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
notifyListeners( updatePlaybackInfo(
listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); playbackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
} }
@Override @Override
@ -486,13 +496,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
Timeline timeline = maskTimeline(); PlaybackInfo playbackInfo = maskTimeline();
maskWithCurrentPosition(); maskWithCurrentPosition();
pendingOperationAcks++; pendingOperationAcks++;
this.shuffleOrder = shuffleOrder; this.shuffleOrder = shuffleOrder;
internalPlayer.setShuffleOrder(shuffleOrder); internalPlayer.setShuffleOrder(shuffleOrder);
notifyListeners( updatePlaybackInfo(
listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)); playbackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
} }
@Override @Override
@ -517,38 +532,26 @@ import java.util.concurrent.TimeoutException;
return pauseAtEndOfMediaItems; return pauseAtEndOfMediaItems;
} }
@SuppressWarnings("deprecation")
public void setPlayWhenReady( public void setPlayWhenReady(
boolean playWhenReady, boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason, @PlaybackSuppressionReason int playbackSuppressionReason,
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) { @PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
boolean oldIsPlaying = isPlaying(); if (playbackInfo.playWhenReady == playWhenReady
boolean playWhenReadyChanged = playbackInfo.playWhenReady != playWhenReady; && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) {
boolean suppressionReasonChanged =
playbackInfo.playbackSuppressionReason != playbackSuppressionReason;
if (!playWhenReadyChanged && !suppressionReasonChanged) {
return; return;
} }
maskWithCurrentPosition(); maskWithCurrentPosition();
pendingOperationAcks++; pendingOperationAcks++;
playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); PlaybackInfo playbackInfo =
this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason); internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
boolean isPlaying = isPlaying(); updatePlaybackInfo(
boolean isPlayingChanged = oldIsPlaying != isPlaying; playbackInfo,
int playbackState = playbackInfo.playbackState; /* positionDiscontinuity= */ false,
notifyListeners( /* ignored */ DISCONTINUITY_REASON_INTERNAL,
listener -> { /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
if (playWhenReadyChanged) { playWhenReadyChangeReason,
listener.onPlayerStateChanged(playWhenReady, playbackState); /* seekProcessed= */ false);
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
}
if (suppressionReasonChanged) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);
}
if (isPlayingChanged) {
listener.onIsPlayingChanged(isPlaying);
}
});
} }
@Override @Override
@ -589,7 +592,6 @@ import java.util.concurrent.TimeoutException;
return playbackInfo.isLoading; return playbackInfo.isLoading;
} }
@SuppressWarnings("deprecation")
@Override @Override
public void seekTo(int windowIndex, long positionMs) { public void seekTo(int windowIndex, long positionMs) {
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
@ -597,8 +599,6 @@ import java.util.concurrent.TimeoutException;
throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); throw new IllegalSeekPositionException(timeline, windowIndex, positionMs);
} }
hasPendingSeek = true; hasPendingSeek = true;
boolean playWhenReady = getPlayWhenReady();
@Player.State int playbackState = getPlaybackState();
pendingOperationAcks++; pendingOperationAcks++;
if (isPlayingAd()) { if (isPlayingAd()) {
// TODO: Investigate adding support for seeking during ads. This is complicated to do in // TODO: Investigate adding support for seeking during ads. This is complicated to do in
@ -617,18 +617,16 @@ import java.util.concurrent.TimeoutException;
maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs); maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs);
@Player.State @Player.State
int newPlaybackState = int newPlaybackState =
playbackState == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING; getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING;
boolean playbackStateChanged = playbackState != newPlaybackState; PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackState(newPlaybackState);
playbackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs));
notifyListeners( updatePlaybackInfo(
listener -> { playbackInfo,
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK); /* positionDiscontinuity= */ true,
if (playbackStateChanged) { /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
listener.onPlayerStateChanged(playWhenReady, newPlaybackState); /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
listener.onPlaybackStateChanged(newPlaybackState); /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
} /* seekProcessed= */ false);
});
} }
/** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */ /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
@ -1011,7 +1009,6 @@ import java.util.concurrent.TimeoutException;
seekProcessed)); seekProcessed));
} }
@SuppressWarnings("deprecation")
private void setMediaSourcesInternal( private void setMediaSourcesInternal(
List<MediaSource> mediaItems, List<MediaSource> mediaItems,
int startWindowIndex, int startWindowIndex,
@ -1019,14 +1016,14 @@ import java.util.concurrent.TimeoutException;
boolean resetToDefaultPosition) { boolean resetToDefaultPosition) {
int currentWindowIndex = getCurrentWindowIndexInternal(); int currentWindowIndex = getCurrentWindowIndexInternal();
long currentPositionMs = getCurrentPosition(); long currentPositionMs = getCurrentPosition();
boolean currentPlayWhenReady = getPlayWhenReady();
pendingOperationAcks++; pendingOperationAcks++;
if (!mediaSourceHolders.isEmpty()) { if (!mediaSourceHolders.isEmpty()) {
removeMediaSourceHolders( removeMediaSourceHolders(
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size()); /* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
} }
List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(/* index= */ 0, mediaItems); List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(/* index= */ 0, mediaItems);
Timeline timeline = maskTimeline(); PlaybackInfo playbackInfo = maskTimeline();
Timeline timeline = playbackInfo.timeline;
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) { if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs); throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs);
} }
@ -1040,9 +1037,9 @@ import java.util.concurrent.TimeoutException;
} }
maskWindowIndexAndPositionForSeek( maskWindowIndexAndPositionForSeek(
timeline, startWindowIndex == C.INDEX_UNSET ? 0 : startWindowIndex, startPositionMs); timeline, startWindowIndex == C.INDEX_UNSET ? 0 : startWindowIndex, startPositionMs);
// mask the playback state // Mask the playback state.
int maskingPlaybackState = playbackInfo.playbackState; int maskingPlaybackState = playbackInfo.playbackState;
if (startWindowIndex != C.INDEX_UNSET) { if (startWindowIndex != C.INDEX_UNSET && playbackInfo.playbackState != STATE_IDLE) {
// Position reset to startWindowIndex (results in pending initial seek). // Position reset to startWindowIndex (results in pending initial seek).
if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) { if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) {
// Setting an empty timeline or invalid seek transitions to ended. // Setting an empty timeline or invalid seek transitions to ended.
@ -1051,23 +1048,16 @@ import java.util.concurrent.TimeoutException;
maskingPlaybackState = STATE_BUFFERING; maskingPlaybackState = STATE_BUFFERING;
} }
} }
boolean playbackStateChanged = playbackInfo = playbackInfo.copyWithPlaybackState(maskingPlaybackState);
playbackInfo.playbackState != STATE_IDLE
&& playbackInfo.playbackState != maskingPlaybackState;
int finalMaskingPlaybackState = maskingPlaybackState;
if (playbackStateChanged) {
playbackInfo = playbackInfo.copyWithPlaybackState(finalMaskingPlaybackState);
}
internalPlayer.setMediaSources( internalPlayer.setMediaSources(
holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder); holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder);
notifyListeners( updatePlaybackInfo(
listener -> { playbackInfo,
listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); /* positionDiscontinuity= */ false,
if (playbackStateChanged) { /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
listener.onPlayerStateChanged(currentPlayWhenReady, finalMaskingPlaybackState); /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
listener.onPlaybackStateChanged(finalMaskingPlaybackState); /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
} /* seekProcessed= */ false);
});
} }
private List<Playlist.MediaSourceHolder> addMediaSourceHolders( private List<Playlist.MediaSourceHolder> addMediaSourceHolders(
@ -1085,18 +1075,16 @@ import java.util.concurrent.TimeoutException;
return holders; return holders;
} }
@SuppressWarnings("deprecation")
private void removeMediaItemsInternal(int fromIndex, int toIndex) { private void removeMediaItemsInternal(int fromIndex, int toIndex) {
Assertions.checkArgument( Assertions.checkArgument(
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolders.size()); fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolders.size());
int currentWindowIndex = getCurrentWindowIndex(); int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition(); long currentPositionMs = getCurrentPosition();
boolean currentPlayWhenReady = getPlayWhenReady();
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
int currentMediaSourceCount = mediaSourceHolders.size(); int currentMediaSourceCount = mediaSourceHolders.size();
pendingOperationAcks++; pendingOperationAcks++;
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex); removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
Timeline timeline = PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
// Player transitions to STATE_ENDED if the current index is part of the removed tail. // Player transitions to STATE_ENDED if the current index is part of the removed tail.
final boolean transitionsToEnded = final boolean transitionsToEnded =
@ -1104,19 +1092,18 @@ import java.util.concurrent.TimeoutException;
&& playbackInfo.playbackState != STATE_ENDED && playbackInfo.playbackState != STATE_ENDED
&& fromIndex < toIndex && fromIndex < toIndex
&& toIndex == currentMediaSourceCount && toIndex == currentMediaSourceCount
&& currentWindowIndex >= timeline.getWindowCount(); && currentWindowIndex >= playbackInfo.timeline.getWindowCount();
if (transitionsToEnded) { if (transitionsToEnded) {
playbackInfo = playbackInfo.copyWithPlaybackState(STATE_ENDED); playbackInfo = playbackInfo.copyWithPlaybackState(STATE_ENDED);
} }
internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder); internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder);
notifyListeners( updatePlaybackInfo(
listener -> { playbackInfo,
listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); /* positionDiscontinuity= */ false,
if (transitionsToEnded) { /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
listener.onPlayerStateChanged(currentPlayWhenReady, STATE_ENDED); /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
listener.onPlaybackStateChanged(STATE_ENDED); /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
} /* seekProcessed= */ false);
});
} }
private List<Playlist.MediaSourceHolder> removeMediaSourceHolders( private List<Playlist.MediaSourceHolder> removeMediaSourceHolders(
@ -1129,18 +1116,17 @@ import java.util.concurrent.TimeoutException;
return removed; return removed;
} }
private Timeline maskTimeline() { private PlaybackInfo maskTimeline() {
playbackInfo = return playbackInfo.copyWithTimeline(
playbackInfo.copyWithTimeline( mediaSourceHolders.isEmpty()
mediaSourceHolders.isEmpty() ? Timeline.EMPTY
? Timeline.EMPTY : new Playlist.PlaylistTimeline(mediaSourceHolders, shuffleOrder));
: new Playlist.PlaylistTimeline(mediaSourceHolders, shuffleOrder));
return playbackInfo.timeline;
} }
private Timeline maskTimelineAndWindowIndex( private PlaybackInfo maskTimelineAndWindowIndex(
int currentWindowIndex, long currentPositionMs, Timeline oldTimeline) { int currentWindowIndex, long currentPositionMs, Timeline oldTimeline) {
Timeline maskingTimeline = maskTimeline(); PlaybackInfo playbackInfo = maskTimeline();
Timeline maskingTimeline = playbackInfo.timeline;
if (oldTimeline.isEmpty()) { if (oldTimeline.isEmpty()) {
// The index is the default index or was set by a seek in the empty old timeline. // The index is the default index or was set by a seek in the empty old timeline.
maskingWindowIndex = currentWindowIndex; maskingWindowIndex = currentWindowIndex;
@ -1148,7 +1134,7 @@ import java.util.concurrent.TimeoutException;
// The seek is not valid in the new timeline. // The seek is not valid in the new timeline.
maskWithDefaultPosition(maskingTimeline); maskWithDefaultPosition(maskingTimeline);
} }
return maskingTimeline; return playbackInfo;
} }
@Nullable @Nullable
Pair<Object, Long> periodPosition = Pair<Object, Long> periodPosition =
@ -1186,7 +1172,7 @@ import java.util.concurrent.TimeoutException;
maskWithDefaultPosition(maskingTimeline); maskWithDefaultPosition(maskingTimeline);
} }
} }
return maskingTimeline; return playbackInfo;
} }
private void maskWindowIndexAndPositionForSeek( private void maskWindowIndexAndPositionForSeek(

View File

@ -3391,7 +3391,6 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
assertArrayEquals(new long[] {2}, windowCounts); assertArrayEquals(new long[] {2}, windowCounts);
assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices);
@ -4157,7 +4156,7 @@ public final class ExoPlayerTest {
int seekToWindowIndex = 1; int seekToWindowIndex = 1;
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.waitForTimelineChanged() .waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
@ -4168,12 +4167,11 @@ public final class ExoPlayerTest {
} }
}) })
.executeRunnable( .executeRunnable(
() -> { () ->
concatenatingMediaSource.addMediaSource( concatenatingMediaSource.addMediaSources(
new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)); Arrays.asList(
concatenatingMediaSource.addMediaSource( new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)); new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT))))
})
.waitForTimelineChanged() .waitForTimelineChanged()
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@ -4203,7 +4201,7 @@ public final class ExoPlayerTest {
ConcatenatingMediaSource concatenatingMediaSource = ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(/* isAtomic= */ false); new ConcatenatingMediaSource(/* isAtomic= */ false);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG).waitForTimelineChanged().build(); new ActionSchedule.Builder(TAG).waitForPlaybackState(Player.STATE_ENDED).build();
ExoPlayerTestRunner exoPlayerTestRunner = ExoPlayerTestRunner exoPlayerTestRunner =
new Builder() new Builder()
.setMediaSources(concatenatingMediaSource) .setMediaSources(concatenatingMediaSource)
@ -4229,7 +4227,7 @@ public final class ExoPlayerTest {
int seekToWindowIndex = 1; int seekToWindowIndex = 1;
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.waitForTimelineChanged() .waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
@ -4240,12 +4238,11 @@ public final class ExoPlayerTest {
} }
}) })
.executeRunnable( .executeRunnable(
() -> { () ->
concatenatingMediaSource.addMediaSource( concatenatingMediaSource.addMediaSources(
new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)); Arrays.asList(
concatenatingMediaSource.addMediaSource( new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)); new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT))))
})
.waitForTimelineChanged() .waitForTimelineChanged()
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@ -4727,7 +4724,6 @@ public final class ExoPlayerTest {
new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE}, new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE},
maskingPlaybackStates); maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
@ -4768,7 +4764,6 @@ public final class ExoPlayerTest {
Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
} }
@ -4841,7 +4836,6 @@ public final class ExoPlayerTest {
Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED); Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates); assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
} }
@ -4924,9 +4918,6 @@ public final class ExoPlayerTest {
}, },
maskingPlaybackStates); maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -4969,7 +4960,6 @@ public final class ExoPlayerTest {
exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED); exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
} }
@ -5050,7 +5040,6 @@ public final class ExoPlayerTest {
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
} }
@ -5213,7 +5202,6 @@ public final class ExoPlayerTest {
Player.STATE_ENDED); Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual( exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -6161,6 +6149,7 @@ public final class ExoPlayerTest {
public void loading_withLargeAllocationCausingOom_playsRemainingMediaAndThenThrows() { public void loading_withLargeAllocationCausingOom_playsRemainingMediaAndThenThrows() {
Loader.Loadable loadable = Loader.Loadable loadable =
new Loader.Loadable() { new Loader.Loadable() {
@SuppressWarnings("UnusedVariable")
@Override @Override
public void load() throws IOException { public void load() throws IOException {
@SuppressWarnings("unused") // This test needs the allocation to cause an OOM. @SuppressWarnings("unused") // This test needs the allocation to cause an OOM.
@ -6229,6 +6218,97 @@ public final class ExoPlayerTest {
assertThat(renderer.sampleBufferReadCount).isEqualTo(3); assertThat(renderer.sampleBufferReadCount).isEqualTo(3);
} }
@Test
public void seekTo_whileReady_callsOnIsPlayingChanged() throws Exception {
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.seek(/* positionMs= */ 0)
.waitForPlaybackState(Player.STATE_ENDED)
.build();
List<Boolean> onIsPlayingChanges = new ArrayList<>();
Player.EventListener eventListener =
new Player.EventListener() {
@Override
public void onIsPlayingChanged(boolean isPlaying) {
onIsPlayingChanges.add(isPlaying);
}
};
new ExoPlayerTestRunner.Builder()
.setEventListener(eventListener)
.setActionSchedule(actionSchedule)
.build(context)
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(onIsPlayingChanges).containsExactly(true, false, true, false).inOrder();
}
@Test
public void multipleListenersAndMultipleCallbacks_callbacksAreOrderedByType() throws Exception {
String playWhenReadyChange1 = "playWhenReadyChange1";
String playWhenReadyChange2 = "playWhenReadyChange2";
String isPlayingChange1 = "isPlayingChange1";
String isPlayingChange2 = "isPlayingChange2";
ArrayList<String> events = new ArrayList<>();
Player.EventListener eventListener1 =
new Player.EventListener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
events.add(playWhenReadyChange1);
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
events.add(isPlayingChange1);
}
};
Player.EventListener eventListener2 =
new Player.EventListener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
events.add(playWhenReadyChange2);
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
events.add(isPlayingChange2);
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addListener(eventListener1);
player.addListener(eventListener2);
}
})
.waitForPlaybackState(Player.STATE_READY)
.play()
.waitForPlaybackState(Player.STATE_ENDED)
.build();
new ExoPlayerTestRunner.Builder()
.setActionSchedule(actionSchedule)
.build(context)
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(events)
.containsExactly(
playWhenReadyChange1,
playWhenReadyChange2,
isPlayingChange1,
isPlayingChange2,
isPlayingChange1,
isPlayingChange2)
.inOrder();
}
// Internal methods. // Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {