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
This commit is contained in:
parent
d67926ef2b
commit
216f74ecc7
@ -690,7 +690,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
long operationStartTimeMs = clock.uptimeMillis();
|
long operationStartTimeMs = clock.uptimeMillis();
|
||||||
updatePeriods();
|
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) {
|
if (playingPeriodHolder == null) {
|
||||||
// We're still waiting until the playing period is available.
|
// We're still waiting until the playing period is available.
|
||||||
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
|
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
|
||||||
@ -870,7 +877,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
rebuffering = false;
|
rebuffering = false;
|
||||||
|
if (playbackInfo.playbackState != Player.STATE_IDLE && !playbackInfo.timeline.isEmpty()) {
|
||||||
setState(Player.STATE_BUFFERING);
|
setState(Player.STATE_BUFFERING);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the timeline, but keep the requested period if it is already prepared.
|
// Clear the timeline, but keep the requested period if it is already prepared.
|
||||||
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
|
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
|
||||||
@ -1511,7 +1520,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleSourceInfoRefreshEndedPlayback() {
|
private void handleSourceInfoRefreshEndedPlayback() {
|
||||||
|
if (playbackInfo.playbackState != Player.STATE_IDLE) {
|
||||||
setState(Player.STATE_ENDED);
|
setState(Player.STATE_ENDED);
|
||||||
|
}
|
||||||
// Reset, but retain the playlist so that it can still be used should a seek occur.
|
// Reset, but retain the playlist so that it can still be used should a seek occur.
|
||||||
resetInternal(
|
resetInternal(
|
||||||
/* resetRenderers= */ false,
|
/* resetRenderers= */ false,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -56,6 +57,7 @@ import com.google.android.exoplayer2.testutil.FakeTrackSelector;
|
|||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Clock;
|
import com.google.android.exoplayer2.util.Clock;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -1129,6 +1131,14 @@ public final class ExoPlayerTest {
|
|||||||
.build(context)
|
.build(context)
|
||||||
.start()
|
.start()
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
testRunner.assertPlaybackStatesEqual(
|
||||||
|
Player.STATE_IDLE,
|
||||||
|
Player.STATE_BUFFERING,
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_IDLE,
|
||||||
|
Player.STATE_BUFFERING,
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_ENDED);
|
||||||
testRunner.assertTimelinesSame(
|
testRunner.assertTimelinesSame(
|
||||||
dummyTimeline, timeline, Timeline.EMPTY, dummyTimeline, secondTimeline);
|
dummyTimeline, timeline, Timeline.EMPTY, dummyTimeline, secondTimeline);
|
||||||
testRunner.assertTimelineChangeReasonsEqual(
|
testRunner.assertTimelineChangeReasonsEqual(
|
||||||
@ -3078,7 +3088,9 @@ public final class ExoPlayerTest {
|
|||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder("testClearMediaItems")
|
new ActionSchedule.Builder("testClearMediaItems")
|
||||||
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
|
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
.clearMediaItems()
|
.clearMediaItems()
|
||||||
|
.waitForPlaybackState(Player.STATE_ENDED)
|
||||||
.build();
|
.build();
|
||||||
ExoPlayerTestRunner exoPlayerTestRunner =
|
ExoPlayerTestRunner exoPlayerTestRunner =
|
||||||
new Builder()
|
new Builder()
|
||||||
@ -3089,6 +3101,8 @@ public final class ExoPlayerTest {
|
|||||||
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
|
||||||
|
exoPlayerTestRunner.assertPlaybackStatesEqual(
|
||||||
|
Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
|
||||||
exoPlayerTestRunner.assertTimelinesSame(dummyTimeline, timeline, Timeline.EMPTY);
|
exoPlayerTestRunner.assertTimelinesSame(dummyTimeline, timeline, Timeline.EMPTY);
|
||||||
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
|
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
|
||||||
@ -3127,6 +3141,213 @@ public final class ExoPlayerTest {
|
|||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyPlaylistUnprepared_remainsInIdle_needsPrepareForBuffering()
|
||||||
|
throws Exception {
|
||||||
|
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
|
||||||
|
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
|
||||||
|
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
|
||||||
|
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
|
||||||
|
int[] playbackStates = new int[4];
|
||||||
|
int[] timelineWindowCounts = new int[4];
|
||||||
|
ActionSchedule actionSchedule =
|
||||||
|
new ActionSchedule.Builder(
|
||||||
|
"testModifyPlaylistUnprepared_remainsInIdle_needsPrepareForBuffering")
|
||||||
|
.waitForTimelineChanged(dummyTimeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 0, playbackStates, timelineWindowCounts))
|
||||||
|
.clearMediaItems()
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 1, playbackStates, timelineWindowCounts))
|
||||||
|
.setMediaItems(/* windowIndex= */ 0, /* positionMs= */ 1000, firstMediaSource)
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 2, playbackStates, timelineWindowCounts))
|
||||||
|
.addMediaItems(secondMediaSource)
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 3, playbackStates, timelineWindowCounts))
|
||||||
|
.seek(/* windowIndex= */ 1, /* positionMs= */ 2000)
|
||||||
|
.waitForSeekProcessed()
|
||||||
|
.prepare()
|
||||||
|
// The first expected buffering state arrives after prepare but not before.
|
||||||
|
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
|
.waitForPlaybackState(Player.STATE_ENDED)
|
||||||
|
.build();
|
||||||
|
ExoPlayerTestRunner exoPlayerTestRunner =
|
||||||
|
new Builder()
|
||||||
|
.setMediaSources(firstMediaSource)
|
||||||
|
.setActionSchedule(actionSchedule)
|
||||||
|
.build(context)
|
||||||
|
.start(/* doPrepare= */ false)
|
||||||
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
|
||||||
|
assertArrayEquals(
|
||||||
|
new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE},
|
||||||
|
playbackStates);
|
||||||
|
assertArrayEquals(new int[] {1, 0, 1, 2}, timelineWindowCounts);
|
||||||
|
exoPlayerTestRunner.assertPlaybackStatesEqual(
|
||||||
|
Player.STATE_IDLE,
|
||||||
|
Player.STATE_BUFFERING /* first buffering state after prepare */,
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_ENDED);
|
||||||
|
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* initial setMediaItems */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* clear */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* set media items */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* add media items */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source update after prepare */);
|
||||||
|
Timeline expectedSecondDummyTimeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ false,
|
||||||
|
/* isDynamic= */ true,
|
||||||
|
/* durationUs= */ C.TIME_UNSET),
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ false,
|
||||||
|
/* isDynamic= */ true,
|
||||||
|
/* durationUs= */ C.TIME_UNSET));
|
||||||
|
Timeline expectedSecondRealTimeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationUs= */ 10_000_000),
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationUs= */ 10_000_000));
|
||||||
|
exoPlayerTestRunner.assertTimelinesSame(
|
||||||
|
dummyTimeline,
|
||||||
|
Timeline.EMPTY,
|
||||||
|
dummyTimeline,
|
||||||
|
expectedSecondDummyTimeline,
|
||||||
|
expectedSecondRealTimeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyPlaylistPrepared_remainsInEnded_needsSeekForBuffering() throws Exception {
|
||||||
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||||
|
FakeMediaSource secondMediaSource = new FakeMediaSource(timeline);
|
||||||
|
ActionSchedule actionSchedule =
|
||||||
|
new ActionSchedule.Builder(
|
||||||
|
"testModifyPlaylistPrepared_remainsInEnded_needsSeekForBuffering")
|
||||||
|
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
|
||||||
|
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
|
.clearMediaItems()
|
||||||
|
.waitForPlaybackState(Player.STATE_ENDED)
|
||||||
|
.addMediaItems(secondMediaSource) // add must not transition to buffering
|
||||||
|
.waitForTimelineChanged()
|
||||||
|
.clearMediaItems() // clear must remain in ended
|
||||||
|
.addMediaItems(secondMediaSource) // add again to be able to test the seek
|
||||||
|
.waitForTimelineChanged()
|
||||||
|
.seek(/* positionMs= */ 2_000) // seek must transition to buffering
|
||||||
|
.waitForSeekProcessed()
|
||||||
|
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
|
.waitForPlaybackState(Player.STATE_ENDED)
|
||||||
|
.build();
|
||||||
|
ExoPlayerTestRunner exoPlayerTestRunner =
|
||||||
|
new Builder()
|
||||||
|
.setTimeline(timeline)
|
||||||
|
.setActionSchedule(actionSchedule)
|
||||||
|
.build(context)
|
||||||
|
.start()
|
||||||
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
|
||||||
|
exoPlayerTestRunner.assertPlaybackStatesEqual(
|
||||||
|
Player.STATE_IDLE,
|
||||||
|
Player.STATE_BUFFERING, // first buffering
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_ENDED, // clear playlist
|
||||||
|
Player.STATE_BUFFERING, // second buffering after seek
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_ENDED);
|
||||||
|
exoPlayerTestRunner.assertTimelinesSame(
|
||||||
|
dummyTimeline,
|
||||||
|
timeline,
|
||||||
|
Timeline.EMPTY,
|
||||||
|
dummyTimeline,
|
||||||
|
timeline,
|
||||||
|
Timeline.EMPTY,
|
||||||
|
dummyTimeline,
|
||||||
|
timeline);
|
||||||
|
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source prepared */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* playlist cleared */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media items added */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source prepared */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* playlist cleared */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media items added */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source prepared */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStopWithNoReset_modifyingPlaylistRemainsInIdleState_needsPrepareForBuffering()
|
||||||
|
throws Exception {
|
||||||
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||||
|
FakeMediaSource secondMediaSource = new FakeMediaSource(timeline);
|
||||||
|
int[] playbackStateHolder = new int[3];
|
||||||
|
int[] windowCountHolder = new int[3];
|
||||||
|
ActionSchedule actionSchedule =
|
||||||
|
new ActionSchedule.Builder(
|
||||||
|
"testStopWithNoReset_modifyingPlaylistRemainsInIdleState_needsPrepareForBuffering")
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
|
.stop(/* reset= */ false)
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 0, playbackStateHolder, windowCountHolder))
|
||||||
|
.clearMediaItems()
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 1, playbackStateHolder, windowCountHolder))
|
||||||
|
.addMediaItems(secondMediaSource)
|
||||||
|
.executeRunnable(
|
||||||
|
new PlaybackStateCollector(/* index= */ 2, playbackStateHolder, windowCountHolder))
|
||||||
|
.prepare()
|
||||||
|
.waitForPlaybackState(Player.STATE_BUFFERING)
|
||||||
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
|
.waitForPlaybackState(Player.STATE_ENDED)
|
||||||
|
.build();
|
||||||
|
ExoPlayerTestRunner exoPlayerTestRunner =
|
||||||
|
new Builder()
|
||||||
|
.setTimeline(timeline)
|
||||||
|
.setActionSchedule(actionSchedule)
|
||||||
|
.build(context)
|
||||||
|
.start()
|
||||||
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
|
||||||
|
assertArrayEquals(
|
||||||
|
new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE}, playbackStateHolder);
|
||||||
|
assertArrayEquals(new int[] {1, 0, 1}, windowCountHolder);
|
||||||
|
exoPlayerTestRunner.assertPlaybackStatesEqual(
|
||||||
|
Player.STATE_IDLE,
|
||||||
|
Player.STATE_BUFFERING, // first buffering
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_IDLE, // stop
|
||||||
|
Player.STATE_BUFFERING,
|
||||||
|
Player.STATE_READY,
|
||||||
|
Player.STATE_ENDED);
|
||||||
|
exoPlayerTestRunner.assertTimelinesSame(
|
||||||
|
dummyTimeline, timeline, Timeline.EMPTY, dummyTimeline, timeline);
|
||||||
|
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, /* source prepared */
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* clear media items */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item add (masked timeline) */,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source prepared */);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPrepareWhenAlreadyPreparedIsANoop() throws Exception {
|
public void testPrepareWhenAlreadyPreparedIsANoop() throws Exception {
|
||||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
|
||||||
@ -3196,4 +3417,39 @@ public final class ExoPlayerTest {
|
|||||||
messageCount++;
|
messageCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a wrapper for a {@link Runnable} which does collect playback states and window counts.
|
||||||
|
* Can be used with {@link ActionSchedule.Builder#executeRunnable(Runnable)} to verify that a
|
||||||
|
* playback state did not change and hence no observable callback is called.
|
||||||
|
*
|
||||||
|
* <p>This is specifically useful in cases when the test may end before a given state arrives or
|
||||||
|
* when an action of the action schedule might execute before a callback is called.
|
||||||
|
*/
|
||||||
|
public static class PlaybackStateCollector extends PlayerRunnable {
|
||||||
|
|
||||||
|
private final int[] playbackStates;
|
||||||
|
private final int[] timelineWindowCount;
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the collector.
|
||||||
|
*
|
||||||
|
* @param index The index to populate.
|
||||||
|
* @param playbackStates An array of playback states to populate.
|
||||||
|
* @param timelineWindowCount An array of window counts to populate.
|
||||||
|
*/
|
||||||
|
public PlaybackStateCollector(int index, int[] playbackStates, int[] timelineWindowCount) {
|
||||||
|
Assertions.checkArgument(playbackStates.length > index && timelineWindowCount.length > index);
|
||||||
|
this.playbackStates = playbackStates;
|
||||||
|
this.timelineWindowCount = timelineWindowCount;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(SimpleExoPlayer player) {
|
||||||
|
playbackStates[index] = player.getPlaybackState();
|
||||||
|
timelineWindowCount[index] = player.getCurrentTimeline().getWindowCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)}. */
|
/** Calls {@link SimpleExoPlayer#setMediaItems(List, boolean)}. */
|
||||||
public static final class SetMediaItemsResetPosition extends Action {
|
public static final class SetMediaItemsResetPosition extends Action {
|
||||||
|
|
||||||
|
@ -341,12 +341,22 @@ public final class ActionSchedule {
|
|||||||
/**
|
/**
|
||||||
* Schedules a set media items action to be executed.
|
* Schedules a set media items action to be executed.
|
||||||
*
|
*
|
||||||
|
* @param mediaSources The media sources to add.
|
||||||
* @return The builder, for convenience.
|
* @return The builder, for convenience.
|
||||||
*/
|
*/
|
||||||
public Builder setMediaItems(MediaSource... sources) {
|
public Builder setMediaItems(MediaSource... mediaSources) {
|
||||||
return apply(
|
return apply(
|
||||||
new Action.SetMediaItems(
|
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 {
|
/* package */ static final class ActionNode implements Runnable {
|
||||||
|
|
||||||
private final Action action;
|
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
|
* 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
|
* listener registration, the start of the action schedule, the initial set of media items and the
|
||||||
* with the provided media source.
|
* preparation of the player.
|
||||||
*
|
*
|
||||||
* @return This test runner.
|
* @return This test runner.
|
||||||
*/
|
*/
|
||||||
public ExoPlayerTestRunner start() {
|
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(
|
handler.post(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
@ -424,7 +435,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this);
|
actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this);
|
||||||
}
|
}
|
||||||
player.setMediaItems(mediaSources, /* resetPosition= */ false);
|
player.setMediaItems(mediaSources, /* resetPosition= */ false);
|
||||||
|
if (doPrepare) {
|
||||||
player.prepare();
|
player.prepare();
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleException(e);
|
handleException(e);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user