diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 34f44b47f7..fd0acf2ab3 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -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) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index f12e1c006f..0c7bcece68 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -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 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 { } } +