Commit 216f74ec by bachinger Committed by Oliver Woodman

avoid unexpected state changes with certain playlist states

With the internal playlist some new situation may happen that were not possible before:

- handlePlaylistInfoRefreshed in EPII called in IDLE state
- handlePlaylistInfoRefreshed in EPII called in ENDED state with an empty playlist
- seeks in ENDED state with an empty playlist

PiperOrigin-RevId: 270316681
parent d67926ef
......@@ -690,7 +690,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
long operationStartTimeMs = clock.uptimeMillis();
updatePeriods();
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playbackInfo.playbackState == Player.STATE_IDLE
|| playbackInfo.playbackState == Player.STATE_ENDED) {
// Remove all messages. Prepare (in case of IDLE) or seek (in case of ENDED) will resume.
handler.removeMessages(MSG_DO_SOME_WORK);
return;
}
@Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder == null) {
// We're still waiting until the playing period is available.
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
......@@ -870,7 +877,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
throws ExoPlaybackException {
stopRenderers();
rebuffering = false;
setState(Player.STATE_BUFFERING);
if (playbackInfo.playbackState != Player.STATE_IDLE && !playbackInfo.timeline.isEmpty()) {
setState(Player.STATE_BUFFERING);
}
// Clear the timeline, but keep the requested period if it is already prepared.
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
......@@ -1511,7 +1520,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void handleSourceInfoRefreshEndedPlayback() {
setState(Player.STATE_ENDED);
if (playbackInfo.playbackState != Player.STATE_IDLE) {
setState(Player.STATE_ENDED);
}
// Reset, but retain the playlist so that it can still be used should a seek occur.
resetInternal(
/* resetRenderers= */ false,
......
......@@ -192,6 +192,27 @@ public abstract class Action {
}
}
/** Calls {@link SimpleExoPlayer#addMediaItems(List)}. */
public static final class AddMediaItems extends Action {
private final MediaSource[] mediaSources;
/**
* @param tag A tag to use for logging.
* @param mediaSources The media sources to be added to the playlist.
*/
public AddMediaItems(String tag, MediaSource... mediaSources) {
super(tag, /* description= */ "AddMediaItems");
this.mediaSources = mediaSources;
}
@Override
protected void doActionImpl(
SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
player.addMediaItems(Arrays.asList(mediaSources));
}
}
/** Calls {@link SimpleExoPlayer#setMediaItems(List, boolean)}. */
public static final class SetMediaItemsResetPosition extends Action {
......
......@@ -341,12 +341,22 @@ public final class ActionSchedule {
/**
* Schedules a set media items action to be executed.
*
* @param mediaSources The media sources to add.
* @return The builder, for convenience.
*/
public Builder setMediaItems(MediaSource... sources) {
public Builder setMediaItems(MediaSource... mediaSources) {
return apply(
new Action.SetMediaItems(
tag, /* windowIndex */ C.INDEX_UNSET, /* positionUs */ C.TIME_UNSET, sources));
tag, /* windowIndex= */ C.INDEX_UNSET, /* positionMs= */ C.TIME_UNSET, mediaSources));
}
/**
* Schedules a add media items action to be executed.
*
* @param mediaSources The media sources to add.
* @return The builder, for convenience.
*/
public Builder addMediaItems(MediaSource... mediaSources) {
return apply(new Action.AddMediaItems(tag, mediaSources));
}
/**
......@@ -595,9 +605,7 @@ public final class ActionSchedule {
}
}
/**
* Wraps an {@link Action}, allowing a delay and a next {@link Action} to be specified.
*/
/** Wraps an {@link Action}, allowing a delay and a next {@link Action} to be specified. */
/* package */ static final class ActionNode implements Runnable {
private final Action action;
......
......@@ -400,12 +400,23 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
/**
* Starts the test runner on its own thread. This will trigger the creation of the player, the
* listener registration, the start of the action schedule, and the preparation of the player
* with the provided media source.
* listener registration, the start of the action schedule, the initial set of media items and the
* preparation of the player.
*
* @return This test runner.
*/
public ExoPlayerTestRunner start() {
return start(/* doPrepare= */ true);
}
/**
* Starts the test runner on its own thread. This will trigger the creation of the player, the
* listener registration, the start of the action schedule and the initial set of media items.
*
* @param doPrepare Whether the player should be prepared.
* @return This test runner.
*/
public ExoPlayerTestRunner start(boolean doPrepare) {
handler.post(
() -> {
try {
......@@ -424,7 +435,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this);
}
player.setMediaItems(mediaSources, /* resetPosition= */ false);
player.prepare();
if (doPrepare) {
player.prepare();
}
} catch (Exception e) {
handleException(e);
}
......
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