mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
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:
parent
1305b1155b
commit
eeebb3968b
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2;
|
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.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
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.FakeMediaClockRenderer;
|
||||||
import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
||||||
import com.google.android.exoplayer2.testutil.FakeRenderer;
|
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;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@ -234,4 +236,27 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
assertTrue(renderer.isEnded);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -488,7 +488,7 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex,
|
int nextPeriodIndex = timeline.getNextPeriodIndex(lastValidPeriodHolder.info.id.periodIndex,
|
||||||
period, window, repeatMode, false);
|
period, window, repeatMode, shuffleModeEnabled);
|
||||||
while (lastValidPeriodHolder.next != null
|
while (lastValidPeriodHolder.next != null
|
||||||
&& !lastValidPeriodHolder.info.isLastInTimelinePeriod) {
|
&& !lastValidPeriodHolder.info.isLastInTimelinePeriod) {
|
||||||
lastValidPeriodHolder = lastValidPeriodHolder.next;
|
lastValidPeriodHolder = lastValidPeriodHolder.next;
|
||||||
@ -686,13 +686,15 @@ import java.io.IOException;
|
|||||||
|
|
||||||
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
|
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
|
||||||
if (periodPosition == null) {
|
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
|
// 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.
|
// 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();
|
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
|
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||||
// ignored.
|
// (firstPeriodIndex,0) isn't ignored.
|
||||||
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
|
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||||
setState(Player.STATE_ENDED);
|
setState(Player.STATE_ENDED);
|
||||||
// Reset, but retain the source so that it can still be used should a seek occur.
|
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||||
resetInternal(false);
|
resetInternal(false);
|
||||||
@ -1029,7 +1031,8 @@ import java.io.IOException;
|
|||||||
if (timeline.isEmpty()) {
|
if (timeline.isEmpty()) {
|
||||||
handleSourceInfoRefreshEndedPlayback(manifest);
|
handleSourceInfoRefreshEndedPlayback(manifest);
|
||||||
} else {
|
} 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;
|
int periodIndex = defaultPosition.first;
|
||||||
long startPositionUs = defaultPosition.second;
|
long startPositionUs = defaultPosition.second;
|
||||||
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
|
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
|
||||||
@ -1122,7 +1125,8 @@ import java.io.IOException;
|
|||||||
while (periodHolder.next != null) {
|
while (periodHolder.next != null) {
|
||||||
MediaPeriodHolder previousPeriodHolder = periodHolder;
|
MediaPeriodHolder previousPeriodHolder = periodHolder;
|
||||||
periodHolder = periodHolder.next;
|
periodHolder = periodHolder.next;
|
||||||
periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode, false);
|
periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode,
|
||||||
|
shuffleModeEnabled);
|
||||||
if (periodIndex != C.INDEX_UNSET
|
if (periodIndex != C.INDEX_UNSET
|
||||||
&& periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) {
|
&& periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) {
|
||||||
// The holder is consistent with the new timeline. Update its index and continue.
|
// 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,
|
private void handleSourceInfoRefreshEndedPlayback(Object manifest,
|
||||||
int processedInitialSeekCount) {
|
int processedInitialSeekCount) {
|
||||||
// Set the playback position to (0,0) for notifying the eventHandler.
|
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
|
||||||
playbackInfo = new PlaybackInfo(0, 0);
|
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
|
||||||
|
// Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler.
|
||||||
|
playbackInfo = new PlaybackInfo(firstPeriodIndex, 0);
|
||||||
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
|
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
|
||||||
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored.
|
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
|
||||||
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
|
// (firstPeriodIndex,0) isn't ignored.
|
||||||
|
playbackInfo = new PlaybackInfo(firstPeriodIndex, C.TIME_UNSET);
|
||||||
setState(Player.STATE_ENDED);
|
setState(Player.STATE_ENDED);
|
||||||
// Reset, but retain the source so that it can still be used should a seek occur.
|
// Reset, but retain the source so that it can still be used should a seek occur.
|
||||||
resetInternal(false);
|
resetInternal(false);
|
||||||
@ -1205,7 +1212,7 @@ import java.io.IOException;
|
|||||||
int maxIterations = oldTimeline.getPeriodCount();
|
int maxIterations = oldTimeline.getPeriodCount();
|
||||||
for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) {
|
for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) {
|
||||||
oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, repeatMode,
|
oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, repeatMode,
|
||||||
false);
|
shuffleModeEnabled);
|
||||||
if (oldPeriodIndex == C.INDEX_UNSET) {
|
if (oldPeriodIndex == C.INDEX_UNSET) {
|
||||||
// We've reached the end of the old timeline.
|
// We've reached the end of the old timeline.
|
||||||
break;
|
break;
|
||||||
|
@ -162,7 +162,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
|||||||
// timeline is updated, to avoid repeatedly checking the same timeline.
|
// timeline is updated, to avoid repeatedly checking the same timeline.
|
||||||
if (currentMediaPeriodInfo.isLastInTimelinePeriod) {
|
if (currentMediaPeriodInfo.isLastInTimelinePeriod) {
|
||||||
int nextPeriodIndex = timeline.getNextPeriodIndex(currentMediaPeriodInfo.id.periodIndex,
|
int nextPeriodIndex = timeline.getNextPeriodIndex(currentMediaPeriodInfo.id.periodIndex,
|
||||||
period, window, repeatMode, false);
|
period, window, repeatMode, shuffleModeEnabled);
|
||||||
if (nextPeriodIndex == C.INDEX_UNSET) {
|
if (nextPeriodIndex == C.INDEX_UNSET) {
|
||||||
// We can't create a next period yet.
|
// We can't create a next period yet.
|
||||||
return null;
|
return null;
|
||||||
@ -353,7 +353,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
|||||||
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
|
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
|
||||||
int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex;
|
int windowIndex = timeline.getPeriod(id.periodIndex, period).windowIndex;
|
||||||
return !timeline.getWindow(windowIndex, window).isDynamic
|
return !timeline.getWindow(windowIndex, window).isDynamic
|
||||||
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, false)
|
&& timeline.isLastPeriod(id.periodIndex, period, window, repeatMode, shuffleModeEnabled)
|
||||||
&& isLastMediaPeriodInPeriod;
|
&& isLastMediaPeriodInPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)}.
|
* Waits for {@link Player.EventListener#onTimelineChanged(Timeline, Object)}.
|
||||||
*/
|
*/
|
||||||
|
@ -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.SetPlayWhenReady;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
|
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
|
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.SetVideoSurface;
|
||||||
import com.google.android.exoplayer2.testutil.Action.Stop;
|
import com.google.android.exoplayer2.testutil.Action.Stop;
|
||||||
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
|
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
|
||||||
@ -217,6 +218,15 @@ public final class ActionSchedule {
|
|||||||
return apply(new SetRepeatMode(tag, repeatMode));
|
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.
|
* Schedules a delay until the timeline changed to a specified expected timeline.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user