Implement shuffle mode logic in ExoPlayerImplInternal.

This is mostly connecting the already stored shuffleMode with the timeline queries
for the playback order.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=166199330
This commit is contained in:
tonihei 2017-08-23 07:40:05 -07:00 committed by Oliver Woodman
parent 1305b1155b
commit eeebb3968b
5 changed files with 79 additions and 14 deletions

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
@ -24,6 +25,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import java.util.concurrent.CountDownLatch;
@ -234,4 +236,27 @@ public final class ExoPlayerTest extends TestCase {
assertTrue(renderer.isEnded);
}
public void testShuffleModeEnabledChanges() throws Exception {
Timeline fakeTimeline = new FakeTimeline(new TimelineWindowDefinition(true, false, 100000));
MediaSource[] fakeMediaSources = {
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT)
};
ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(false,
new FakeShuffleOrder(3), fakeMediaSources);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testShuffleModeEnabled")
.setRepeatMode(Player.REPEAT_MODE_ALL).waitForPositionDiscontinuity() // 0 -> 1
.setShuffleModeEnabled(true).waitForPositionDiscontinuity() // 1 -> 0
.waitForPositionDiscontinuity().waitForPositionDiscontinuity() // 0 -> 2 -> 1
.setShuffleModeEnabled(false).setRepeatMode(Player.REPEAT_MODE_OFF) // 1 -> 2 -> end
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource).setRenderers(renderer).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 0, 2, 1, 2);
assertTrue(renderer.isEnded);
}
}

View File

@ -488,7 +488,7 @@ import java.io.IOException;
}
while (true) {
int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex,
period, window, repeatMode, false);
period, window, repeatMode, shuffleModeEnabled);
while (lastValidPeriodHolder.next != null
&& !lastValidPeriodHolder.info.isLastInTimelinePeriod) {
lastValidPeriodHolder = lastValidPeriodHolder.next;
@ -686,13 +686,15 @@ import java.io.IOException;
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
if (periodPosition == null) {
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed and a suitable seek position could not be resolved in the new one.
playbackInfo = new PlaybackInfo(0, 0);
playbackInfo = new PlaybackInfo(firstPeriodIndex, 0);
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't
// ignored.
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
// (firstPeriodIndex,0) isn't ignored.
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
@ -1029,7 +1031,8 @@ import java.io.IOException;
if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(manifest);
} else {
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
Pair<Integer, Long> defaultPosition = getPeriodPosition(
timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET);
int periodIndex = defaultPosition.first;
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
@ -1122,7 +1125,8 @@ import java.io.IOException;
while (periodHolder.next != null) {
MediaPeriodHolder previousPeriodHolder = periodHolder;
periodHolder = periodHolder.next;
periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode, false);
periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode,
shuffleModeEnabled);
if (periodIndex != C.INDEX_UNSET
&& periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) {
// The holder is consistent with the new timeline. Update its index and continue.
@ -1170,11 +1174,14 @@ import java.io.IOException;
private void handleSourceInfoRefreshEndedPlayback(Object manifest,
int processedInitialSeekCount) {
// Set the playback position to (0,0) for notifying the eventHandler.
playbackInfo = new PlaybackInfo(0, 0);
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
// Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler.
playbackInfo = new PlaybackInfo(firstPeriodIndex, 0);
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored.
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
// (firstPeriodIndex,0) isn't ignored.
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
@ -1205,7 +1212,7 @@ import java.io.IOException;
int maxIterations = oldTimeline.getPeriodCount();
for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) {
oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, repeatMode,
false);
shuffleModeEnabled);
if (oldPeriodIndex == C.INDEX_UNSET) {
// We've reached the end of the old timeline.
break;

View File

@ -162,7 +162,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
// timeline is updated, to avoid repeatedly checking the same timeline.
if (currentMediaPeriodInfo.isLastInTimelinePeriod) {
int nextPeriodIndex = timeline.getNextPeriodIndex(currentMediaPeriodInfo.id.periodIndex,
period, window, repeatMode, false);
period, window, repeatMode, shuffleModeEnabled);
if (nextPeriodIndex == C.INDEX_UNSET) {
// We can't create a next period yet.
return null;
@ -353,7 +353,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex;
return !timeline.getWindow(windowIndex, window).isDynamic
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, false)
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, shuffleModeEnabled)
&& isLastMediaPeriodInPeriod;
}

View File

@ -284,6 +284,29 @@ public abstract class Action {
}
/**
* Calls {@link Player#setShuffleModeEnabled(boolean)}.
*/
public static final class SetShuffleModeEnabled extends Action {
private final boolean shuffleModeEnabled;
/**
* @param tag A tag to use for logging.
*/
public SetShuffleModeEnabled(String tag, boolean shuffleModeEnabled) {
super(tag, "SetShuffleModeEnabled:" + shuffleModeEnabled);
this.shuffleModeEnabled = shuffleModeEnabled;
}
@Override
protected void doActionImpl(SimpleExoPlayer player, MappingTrackSelector trackSelector,
Surface surface) {
player.setShuffleModeEnabled(shuffleModeEnabled);
}
}
/**
* Waits for {@link Player.EventListener#onTimelineChanged(Timeline, Object)}.
*/

View File

@ -30,6 +30,7 @@ import com.google.android.exoplayer2.testutil.Action.Seek;
import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady;
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
import com.google.android.exoplayer2.testutil.Action.SetVideoSurface;
import com.google.android.exoplayer2.testutil.Action.Stop;
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
@ -217,6 +218,15 @@ public final class ActionSchedule {
return apply(new SetRepeatMode(tag, repeatMode));
}
/**
* Schedules a shuffle setting action to be executed.
*
* @return The builder, for convenience.
*/
public Builder setShuffleModeEnabled(boolean shuffleModeEnabled) {
return apply(new SetShuffleModeEnabled(tag, shuffleModeEnabled));
}
/**
* Schedules a delay until the timeline changed to a specified expected timeline.
*