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.Timeline;
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.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TestUtil;
@ -34,26 +35,30 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
}
timeline = getConcatenatedTimeline(true, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0);
}
}
public void testMultipleMediaSources() {
@ -70,18 +75,36 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2);
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);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0);
for (boolean shuffled : new boolean[] { false, true }) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled,
2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 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() {
@ -100,6 +123,16 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
1, 2, 3, C.INDEX_UNSET);
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.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++) {
mediaSources[i] = new FakeMediaSource(timelines[i], null);
}
return TestUtil.extractTimelineFromMediaSource(
new ConcatenatingMediaSource(isRepeatOneAtomic, mediaSources));
ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(isRepeatOneAtomic,
new FakeShuffleOrder(mediaSources.length), mediaSources);
return TestUtil.extractTimelineFromMediaSource(mediaSource);
}
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.Player;
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.util.Assertions;
import com.google.android.exoplayer2.util.Util;
@ -39,10 +39,11 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private final boolean[] duplicateFlags;
private final boolean isRepeatOneAtomic;
private final boolean isAtomic;
private Listener listener;
private ConcatenatedTimeline timeline;
private ShuffleOrder shuffleOrder;
/**
* @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
* (i.e., repeated in its entirety) when repeat mode is set to {@code Player.REPEAT_MODE_ONE}.
* @param isAtomic Whether the concatenated media source shall be treated as atomic,
* 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
* {@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) {
Assertions.checkNotNull(mediaSource);
}
Assertions.checkArgument(shuffleOrder.getLength() == mediaSources.length);
this.mediaSources = mediaSources;
this.isRepeatOneAtomic = isRepeatOneAtomic;
this.isAtomic = isAtomic;
this.shuffleOrder = shuffleOrder;
timelines = new Timeline[mediaSources.length];
manifests = new Object[mediaSources.length];
sourceIndexByMediaPeriod = new HashMap<>();
@ -139,7 +156,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
return;
}
}
timeline = new ConcatenatedTimeline(timelines.clone(), isRepeatOneAtomic);
timeline = new ConcatenatedTimeline(timelines.clone(), isAtomic, shuffleOrder);
listener.onSourceInfoRefreshed(timeline, manifests.clone());
}
@ -165,10 +182,10 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Timeline[] timelines;
private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets;
private final boolean isRepeatOneAtomic;
private final boolean isAtomic;
public ConcatenatedTimeline(Timeline[] timelines, boolean isRepeatOneAtomic) {
super(new UnshuffledShuffleOrder(timelines.length));
public ConcatenatedTimeline(Timeline[] timelines, boolean isAtomic, ShuffleOrder shuffleOrder) {
super(shuffleOrder);
int[] sourcePeriodOffsets = new int[timelines.length];
int[] sourceWindowOffsets = new int[timelines.length];
long periodCount = 0;
@ -185,7 +202,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
this.isRepeatOneAtomic = isRepeatOneAtomic;
this.isAtomic = isAtomic;
}
@Override
@ -201,19 +218,29 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled);
return super.getNextWindowIndex(windowIndex, repeatMode, !isAtomic && shuffleModeEnabled);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
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
@ -257,3 +284,4 @@ public final class ConcatenatingMediaSource implements MediaSource {
}
}