Add shuffle support to ConcatenatingMediaSource.

The media source is initialized with a DefaultShuffleOrder which can be changed at
any time. This shuffle order is then used within the corresponding timeline.

The isRepeatOneAtomic flag is extended to also suppress shuffling (now called
isAtomic only).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=166197184
This commit is contained in:
tonihei 2017-08-23 07:15:41 -07:00 committed by Oliver Woodman
parent 8115e11489
commit e15633e906
2 changed files with 103 additions and 41 deletions

View File

@ -19,6 +19,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeMediaSource;
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 com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
@ -34,26 +35,30 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111)); Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3); TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
}
timeline = getConcatenatedTimeline(true, createFakeTimeline(3, 111)); timeline = getConcatenatedTimeline(true, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3); TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
}
} }
public void testMultipleMediaSources() { public void testMultipleMediaSources() {
@ -70,18 +75,36 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
1, 2, C.INDEX_UNSET); 1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1);
assertEquals(0, timeline.getFirstWindowIndex(false));
assertEquals(2, timeline.getLastWindowIndex(false));
assertEquals(2, timeline.getFirstWindowIndex(true));
assertEquals(0, timeline.getLastWindowIndex(true));
timeline = getConcatenatedTimeline(true, timelines); timeline = getConcatenatedTimeline(true, timelines);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3); TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET, 0, 1); C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 2, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled,
2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
1, 2, C.INDEX_UNSET); 1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0);
assertEquals(0, timeline.getFirstWindowIndex(shuffled));
assertEquals(2, timeline.getLastWindowIndex(shuffled));
}
} }
public void testNestedMediaSources() { public void testNestedMediaSources() {
@ -100,6 +123,16 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
1, 2, 3, C.INDEX_UNSET); 1, 2, 3, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 3, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 3, 2);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 3, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 3, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
1, 3, C.INDEX_UNSET, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true,
0, 1, 3, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true,
1, 3, 0, 2);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
C.INDEX_UNSET, 0, 3, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 3, 2);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 3, 1);
} }
/** /**
@ -112,8 +145,9 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
for (int i = 0; i < timelines.length; i++) { for (int i = 0; i < timelines.length; i++) {
mediaSources[i] = new FakeMediaSource(timelines[i], null); mediaSources[i] = new FakeMediaSource(timelines[i], null);
} }
return TestUtil.extractTimelineFromMediaSource( ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(isRepeatOneAtomic,
new ConcatenatingMediaSource(isRepeatOneAtomic, mediaSources)); new FakeShuffleOrder(mediaSources.length), mediaSources);
return TestUtil.extractTimelineFromMediaSource(mediaSource);
} }
private static FakeTimeline createFakeTimeline(int periodCount, int windowId) { private static FakeTimeline createFakeTimeline(int periodCount, int windowId) {

View File

@ -19,7 +19,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -39,10 +39,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Object[] manifests; private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod; private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private final boolean[] duplicateFlags; private final boolean[] duplicateFlags;
private final boolean isRepeatOneAtomic; private final boolean isAtomic;
private Listener listener; private Listener listener;
private ConcatenatedTimeline timeline; private ConcatenatedTimeline timeline;
private ShuffleOrder shuffleOrder;
/** /**
* @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
@ -53,17 +54,33 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
/** /**
* @param isRepeatOneAtomic Whether the concatenated media source shall be treated as atomic * @param isAtomic Whether the concatenated media source shall be treated as atomic,
* (i.e., repeated in its entirety) when repeat mode is set to {@code Player.REPEAT_MODE_ONE}. * i.e., treated as a single item for repeating and shuffling.
* @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
* {@link MediaSource} instance to be present more than once in the array. * {@link MediaSource} instance to be present more than once in the array.
*/ */
public ConcatenatingMediaSource(boolean isRepeatOneAtomic, MediaSource... mediaSources) { public ConcatenatingMediaSource(boolean isAtomic, MediaSource... mediaSources) {
this(isAtomic, new DefaultShuffleOrder(mediaSources.length), mediaSources);
}
/**
* @param isAtomic Whether the concatenated media source shall be treated as atomic,
* i.e., treated as a single item for repeating and shuffling.
* @param shuffleOrder The {@link ShuffleOrder} to use when shuffling the child media sources. The
* number of elements in the shuffle order must match the number of concatenated
* {@link MediaSource}s.
* @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
* {@link MediaSource} instance to be present more than once in the array.
*/
public ConcatenatingMediaSource(boolean isAtomic, ShuffleOrder shuffleOrder,
MediaSource... mediaSources) {
for (MediaSource mediaSource : mediaSources) { for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource); Assertions.checkNotNull(mediaSource);
} }
Assertions.checkArgument(shuffleOrder.getLength() == mediaSources.length);
this.mediaSources = mediaSources; this.mediaSources = mediaSources;
this.isRepeatOneAtomic = isRepeatOneAtomic; this.isAtomic = isAtomic;
this.shuffleOrder = shuffleOrder;
timelines = new Timeline[mediaSources.length]; timelines = new Timeline[mediaSources.length];
manifests = new Object[mediaSources.length]; manifests = new Object[mediaSources.length];
sourceIndexByMediaPeriod = new HashMap<>(); sourceIndexByMediaPeriod = new HashMap<>();
@ -139,7 +156,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
return; return;
} }
} }
timeline = new ConcatenatedTimeline(timelines.clone(), isRepeatOneAtomic); timeline = new ConcatenatedTimeline(timelines.clone(), isAtomic, shuffleOrder);
listener.onSourceInfoRefreshed(timeline, manifests.clone()); listener.onSourceInfoRefreshed(timeline, manifests.clone());
} }
@ -165,10 +182,10 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Timeline[] timelines; private final Timeline[] timelines;
private final int[] sourcePeriodOffsets; private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets; private final int[] sourceWindowOffsets;
private final boolean isRepeatOneAtomic; private final boolean isAtomic;
public ConcatenatedTimeline(Timeline[] timelines, boolean isRepeatOneAtomic) { public ConcatenatedTimeline(Timeline[] timelines, boolean isAtomic, ShuffleOrder shuffleOrder) {
super(new UnshuffledShuffleOrder(timelines.length)); super(shuffleOrder);
int[] sourcePeriodOffsets = new int[timelines.length]; int[] sourcePeriodOffsets = new int[timelines.length];
int[] sourceWindowOffsets = new int[timelines.length]; int[] sourceWindowOffsets = new int[timelines.length];
long periodCount = 0; long periodCount = 0;
@ -185,7 +202,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
this.timelines = timelines; this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets; this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets; this.sourceWindowOffsets = sourceWindowOffsets;
this.isRepeatOneAtomic = isRepeatOneAtomic; this.isAtomic = isAtomic;
} }
@Override @Override
@ -201,19 +218,29 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override @Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) { if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL; repeatMode = Player.REPEAT_MODE_ALL;
} }
return super.getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); return super.getNextWindowIndex(windowIndex, repeatMode, !isAtomic && shuffleModeEnabled);
} }
@Override @Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) { if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL; repeatMode = Player.REPEAT_MODE_ALL;
} }
return super.getPreviousWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); return super.getPreviousWindowIndex(windowIndex, repeatMode, !isAtomic && shuffleModeEnabled);
}
@Override
public int getLastWindowIndex(boolean shuffleModeEnabled) {
return super.getLastWindowIndex(!isAtomic && shuffleModeEnabled);
}
@Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) {
return super.getFirstWindowIndex(!isAtomic && shuffleModeEnabled);
} }
@Override @Override
@ -257,3 +284,4 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
} }