Clear one-off events from state as soon as they are triggered.

This ensures they are not accidentally triggered again when
the state is rebuilt with a buildUpon method.

PiperOrigin-RevId: 495280711
This commit is contained in:
tonihei 2022-12-14 12:42:43 +00:00 committed by Ian Baker
parent 21ae403049
commit a123134892
2 changed files with 88 additions and 3 deletions

View File

@ -2888,6 +2888,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
// Assign new state immediately such that all getters return the right values, but use a
// snapshot of the previous and new state so that listener invocations are triggered correctly.
this.state = newState;
if (newState.hasPositionDiscontinuity || newState.newlyRenderedFirstFrame) {
// Clear one-time events to avoid signalling them again later.
this.state =
this.state
.buildUpon()
.clearPositionDiscontinuity()
.setNewlyRenderedFirstFrame(false)
.build();
}
boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady;
boolean playbackStateChanged = previousState.playbackState != newState.playbackState;
@ -2914,7 +2923,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
PositionInfo positionInfo =
getPositionInfo(
newState,
/* useDiscontinuityPosition= */ state.hasPositionDiscontinuity,
/* useDiscontinuityPosition= */ newState.hasPositionDiscontinuity,
window,
period);
listeners.queueEvent(
@ -2928,9 +2937,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
if (mediaItemTransitionReason != C.INDEX_UNSET) {
@Nullable
MediaItem mediaItem =
state.timeline.isEmpty()
newState.timeline.isEmpty()
? null
: state.playlist.get(state.currentMediaItemIndex).mediaItem;
: newState.playlist.get(state.currentMediaItemIndex).mediaItem;
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason));

View File

@ -1697,6 +1697,82 @@ public class SimpleBasePlayerTest {
verify(listener, never()).onMediaItemTransition(any(), anyInt());
}
@SuppressWarnings("deprecation") // Verifying deprecated listener call.
@Test
public void invalidateStateAndOtherOperation_withDiscontinuity_reportsDiscontinuityOnlyOnce() {
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(
ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0).build()))
.setPositionDiscontinuity(
Player.DISCONTINUITY_REASON_INTERNAL, /* discontinuityPositionMs= */ 2000)
.build();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handlePrepare() {
// We just care about the placeholder state, so return an unfulfilled future.
return SettableFuture.create();
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.invalidateState();
player.prepare();
// Assert listener calls (in particular getting only a single discontinuity).
verify(listener)
.onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_INTERNAL));
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING);
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING);
verifyNoMoreInteractions(listener);
}
@SuppressWarnings("deprecation") // Verifying deprecated listener call.
@Test
public void
invalidateStateAndOtherOperation_withRenderedFirstFrame_reportsRenderedFirstFrameOnlyOnce() {
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(
ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 0).build()))
.setNewlyRenderedFirstFrame(true)
.build();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handlePrepare() {
// We just care about the placeholder state, so return an unfulfilled future.
return SettableFuture.create();
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.invalidateState();
player.prepare();
// Assert listener calls (in particular getting only a single rendered first frame).
verify(listener).onRenderedFirstFrame();
verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING);
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING);
verifyNoMoreInteractions(listener);
}
@Test
public void invalidateState_duringAsyncMethodHandling_isIgnored() {
State state1 =